Internacionalización

Next.js te permite configurar el enrutamiento y la renderización de contenido para soportar múltiples idiomas. Hacer que tu sitio se adapte a diferentes configuraciones regionales incluye contenido traducido (localización) y rutas internacionalizadas.

Terminología

  • Configuración regional (Locale): Un identificador para un conjunto de preferencias de idioma y formato. Esto generalmente incluye el idioma preferido del usuario y posiblemente su región geográfica.
    • en-US: Inglés como se habla en Estados Unidos
    • nl-NL: Holandés como se habla en los Países Bajos
    • nl: Holandés, sin región específica

Visión general del enrutamiento

Se recomienda usar las preferencias de idioma del usuario en el navegador para seleccionar qué configuración regional usar. Cambiar tu idioma preferido modificará el encabezado Accept-Language que llega a tu aplicación.

Por ejemplo, usando las siguientes bibliotecas, puedes examinar una Request entrante para determinar qué configuración regional seleccionar, basándote en los Headers, las configuraciones regionales que planeas soportar y la configuración regional predeterminada.

middleware.js
import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'

let headers = { 'accept-language': 'en-US,en;q=0.5' }
let languages = new Negotiator({ headers }).languages()
let locales = ['en-US', 'nl-NL', 'nl']
let defaultLocale = 'en-US'

match(languages, locales, defaultLocale) // -> 'en-US'

El enrutamiento puede ser internacionalizado ya sea por sub-ruta (/fr/products) o dominio (my-site.fr/products). Con esta información, ahora puedes redirigir al usuario basándote en la configuración regional dentro del Middleware.

middleware.js

let locales = ['en-US', 'nl-NL', 'nl']

// Obtiene la configuración regional preferida, similar al ejemplo anterior o usando una biblioteca
function getLocale(request) { ... }

export function middleware(request) {
  // Verifica si hay alguna configuración regional soportada en el pathname
  const { pathname } = request.nextUrl
  const pathnameHasLocale = locales.some(
    (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
  )

  if (pathnameHasLocale) return

  // Redirige si no hay configuración regional
  const locale = getLocale(request)
  request.nextUrl.pathname = `/${locale}${pathname}`
  // ej. la solicitud entrante es /products
  // La nueva URL ahora es /en-US/products
  return Response.redirect(request.nextUrl)
}

export const config = {
  matcher: [
    // Omite todas las rutas internas (_next)
    '/((?!_next).*)',
    // Opcional: solo ejecutar en la URL raíz (/)
    // '/'
  ],
}

Finalmente, asegúrate de que todos los archivos especiales dentro de app/ estén anidados bajo app/[lang]. Esto permite que el enrutador de Next.js maneje dinámicamente diferentes configuraciones regionales en la ruta, y reenvíe el parámetro lang a cada layout y página. Por ejemplo:

app/[lang]/page.js
// Ahora tienes acceso a la configuración regional actual
// ej. /en-US/products -> `lang` es "en-US"
export default async function Page({ params: { lang } }) {
  return ...
}

El layout raíz también puede estar anidado en la nueva carpeta (ej. app/[lang]/layout.js).

Localización

Cambiar el contenido mostrado basado en la configuración regional preferida del usuario, o localización, no es algo específico de Next.js. Los patrones descritos a continuación funcionarían igual con cualquier aplicación web.

Supongamos que queremos soportar contenido tanto en inglés como en holandés dentro de nuestra aplicación. Podríamos mantener dos "diccionarios" diferentes, que son objetos que nos dan un mapeo de alguna clave a una cadena localizada. Por ejemplo:

dictionaries/en.json
{
  "products": {
    "cart": "Add to Cart"
  }
}
dictionaries/nl.json
{
  "products": {
    "cart": "Toevoegen aan Winkelwagen"
  }
}

Luego podemos crear una función getDictionary para cargar las traducciones para la configuración regional solicitada:

app/[lang]/dictionaries.js
import 'server-only'

const dictionaries = {
  en: () => import('./dictionaries/en.json').then((module) => module.default),
  nl: () => import('./dictionaries/nl.json').then((module) => module.default),
}

export const getDictionary = async (locale) => dictionaries[locale]()

Dado el idioma actualmente seleccionado, podemos obtener el diccionario dentro de un layout o página.

app/[lang]/page.js
import { getDictionary } from './dictionaries'

export default async function Page({ params: { lang } }) {
  const dict = await getDictionary(lang) // en
  return <button>{dict.products.cart}</button> // Add to Cart
}

Debido a que todos los layouts y páginas en el directorio app/ son por defecto Componentes del Servidor, no necesitamos preocuparnos por el tamaño de los archivos de traducción afectando el tamaño del paquete JavaScript del lado del cliente. Este código solo se ejecutará en el servidor, y solo el HTML resultante se enviará al navegador.

Generación estática

Para generar rutas estáticas para un conjunto dado de configuraciones regionales, podemos usar generateStaticParams con cualquier página o layout. Esto puede ser global, por ejemplo, en el layout raíz:

app/[lang]/layout.js
export async function generateStaticParams() {
  return [{ lang: 'en-US' }, { lang: 'de' }]
}

export default function Root({ children, params }) {
  return (
    <html lang={params.lang}>
      <body>{children}</body>
    </html>
  )
}

Recursos