Enrutamiento de internacionalización (i18n)
Ejemplos
Next.js tiene soporte integrado para enrutamiento internacionalizado (i18n) desde la versión v10.0.0
. Puedes proporcionar una lista de locales, el locale predeterminado y locales específicos por dominio, y Next.js manejará automáticamente el enrutamiento.
El soporte de enrutamiento i18n actualmente está diseñado para complementar soluciones existentes de bibliotecas i18n como react-intl
, react-i18next
, lingui
, rosetta
, next-intl
, next-translate
, next-multilingual
, typesafe-i18n
, tolgee
, y otras, simplificando las rutas y el análisis de locales.
Comenzando
Para comenzar, agrega la configuración i18n
a tu archivo next.config.js
.
Los locales son Identificadores de Locale UTS, un formato estandarizado para definir locales.
Generalmente un Identificador de Locale está compuesto por un idioma, región y script separados por un guión: idioma-region-script
. La región y el script son opcionales. Un ejemplo:
en-US
- Inglés como se habla en Estados Unidosnl-NL
- Holandés como se habla en Países Bajosnl
- Holandés, sin región específica
Si el locale del usuario es nl-BE
y no está listado en tu configuración, serán redirigidos a nl
si está disponible, o al locale predeterminado en caso contrario.
Si no planeas soportar todas las regiones de un país, es una buena práctica incluir locales de país que actuarán como respaldo.
module.exports = {
i18n: {
// Estos son todos los locales que deseas soportar en
// tu aplicación
locales: ['en-US', 'fr', 'nl-NL'],
// Este es el locale predeterminado que deseas que se use al visitar
// una ruta sin prefijo de locale, ej. `/hello`
defaultLocale: 'en-US',
// Esta es una lista de dominios de locale y el locale predeterminado que
// deben manejar (solo son requeridos al configurar enrutamiento por dominio)
// Nota: los subdominios deben incluirse en el valor del dominio para coincidir, ej. "fr.example.com".
domains: [
{
domain: 'example.com',
defaultLocale: 'en-US',
},
{
domain: 'example.nl',
defaultLocale: 'nl-NL',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
// también se puede usar un campo http opcional para probar
// dominios de locale localmente con http en lugar de https
http: true,
},
],
},
}
Estrategias de Locale
Hay dos estrategias para manejar locales: Enrutamiento por Sub-ruta y Enrutamiento por Dominio.
Enrutamiento por Sub-ruta
El Enrutamiento por Sub-ruta coloca el locale en la ruta de la URL.
module.exports = {
i18n: {
locales: ['en-US', 'fr', 'nl-NL'],
defaultLocale: 'en-US',
},
}
Con la configuración anterior en-US
, fr
y nl-NL
estarán disponibles para enrutamiento, y en-US
es el locale predeterminado. Si tienes un pages/blog.js
las siguientes URLs estarán disponibles:
/blog
/fr/blog
/nl-nl/blog
El locale predeterminado no tiene prefijo.
Enrutamiento por Dominio
Al usar enrutamiento por dominio puedes configurar locales para ser servidos desde diferentes dominios:
module.exports = {
i18n: {
locales: ['en-US', 'fr', 'nl-NL', 'nl-BE'],
defaultLocale: 'en-US',
domains: [
{
// Nota: los subdominios deben incluirse en el valor del dominio para coincidir
// ej. www.example.com debe usarse si ese es el nombre de host esperado
domain: 'example.com',
defaultLocale: 'en-US',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
{
domain: 'example.nl',
defaultLocale: 'nl-NL',
// especifica otros locales que deben redirigirse
// a este dominio
locales: ['nl-BE'],
},
],
},
}
Por ejemplo, si tienes pages/blog.js
las siguientes URLs estarán disponibles:
example.com/blog
www.example.com/blog
example.fr/blog
example.nl/blog
example.nl/nl-BE/blog
Detección Automática de Locale
Cuando un usuario visita la raíz de la aplicación (generalmente /
), Next.js intentará detectar automáticamente qué locale prefiere el usuario basado en la cabecera Accept-Language
y el dominio actual.
Si se detecta un locale diferente al predeterminado, el usuario será redirigido a:
- Con Enrutamiento por Sub-ruta: La ruta con prefijo de locale
- Con Enrutamiento por Dominio: El dominio con ese locale especificado como predeterminado
Al usar Enrutamiento por Dominio, si un usuario con la cabecera Accept-Language
fr;q=0.9
visita example.com
, será redirigido a example.fr
ya que ese dominio maneja el locale fr
por defecto.
Al usar Enrutamiento por Sub-ruta, el usuario sería redirigido a /fr
.
Prefijar el Locale Predeterminado
Con Next.js 12 y Middleware, podemos agregar un prefijo al locale predeterminado con una solución alternativa.
Por ejemplo, aquí hay un archivo next.config.js
con soporte para algunos idiomas. Nota que el locale "default"
ha sido agregado intencionalmente.
module.exports = {
i18n: {
locales: ['default', 'en', 'de', 'fr'],
defaultLocale: 'default',
localeDetection: false,
},
trailingSlash: true,
}
Luego, podemos usar Middleware para agregar reglas de enrutamiento personalizadas:
import { NextRequest, NextResponse } from 'next/server'
const PUBLIC_FILE = /\.(.*)$/
export async function middleware(req: NextRequest) {
if (
req.nextUrl.pathname.startsWith('/_next') ||
req.nextUrl.pathname.includes('/api/') ||
PUBLIC_FILE.test(req.nextUrl.pathname)
) {
return
}
if (req.nextUrl.locale === 'default') {
const locale = req.cookies.get('NEXT_LOCALE')?.value || 'en'
return NextResponse.redirect(
new URL(`/${locale}${req.nextUrl.pathname}${req.nextUrl.search}`, req.url)
)
}
}
Este Middleware omite agregar el prefijo predeterminado a API Routes y archivos públicos como fuentes o imágenes. Si se hace una solicitud al locale predeterminado, redirigimos a nuestro prefijo /en
.
Desactivar la Detección Automática de Locale
La detección automática de locale puede desactivarse con:
module.exports = {
i18n: {
localeDetection: false,
},
}
Cuando localeDetection
está configurado como false
, Next.js ya no redirigirá automáticamente basado en el locale preferido del usuario y solo proporcionará información de locale detectada desde el dominio basado en locale o la ruta de locale como se describió anteriormente.
Accediendo a la información del locale
Puedes acceder a la información del locale a través del enrutador de Next.js. Por ejemplo, usando el hook useRouter()
están disponibles las siguientes propiedades:
locale
contiene el locale actualmente activo.locales
contiene todos los locales configurados.defaultLocale
contiene el locale predeterminado configurado.
Al pre-renderizar páginas con getStaticProps
o getServerSideProps
, la información del locale se proporciona en el contexto proporcionado a la función.
Al aprovechar getStaticPaths
, los locales configurados se proporcionan en el parámetro de contexto de la función bajo locales
y el defaultLocale
configurado bajo defaultLocale
.
Transición entre locales
Puedes usar next/link
o next/router
para transicionar entre locales.
Para next/link
, se puede proporcionar una propiedad locale
para transicionar a un locale diferente del actualmente activo. Si no se proporciona una propiedad locale
, se usará el locale
actualmente activo durante las transiciones del cliente. Por ejemplo:
import Link from 'next/link'
export default function IndexPage(props) {
return (
<Link href="/another" locale="fr">
A /fr/another
</Link>
)
}
Cuando se usan los métodos de next/router
directamente, puedes especificar el locale
que debe usarse a través de las opciones de transición. Por ejemplo:
import { useRouter } from 'next/router'
export default function IndexPage(props) {
const router = useRouter()
return (
<div
onClick={() => {
router.push('/another', '/another', { locale: 'fr' })
}}
>
a /fr/another
</div>
)
}
Nota que para manejar el cambio solo del locale
mientras se preserva toda la información de enrutamiento como valores de consulta de rutas dinámicas o valores de consulta ocultos de href, puedes proporcionar el parámetro href
como un objeto:
import { useRouter } from 'next/router'
const router = useRouter()
const { pathname, asPath, query } = router
// cambia solo el locale y mantén toda la información de ruta incluyendo la consulta del href
router.push({ pathname, query }, asPath, { locale: nextLocale })
Consulta aquí para más información sobre la estructura del objeto para router.push
.
Si tienes un href
que ya incluye el locale, puedes optar por no manejar automáticamente el prefijo de locale:
import Link from 'next/link'
export default function IndexPage(props) {
return (
<Link href="/fr/another" locale={false}>
A /fr/another
</Link>
)
}
Aprovechando la cookie NEXT_LOCALE
Next.js soporta anular la cabecera accept-language con una cookie NEXT_LOCALE=el-locale
. Esta cookie puede configurarse usando un selector de idioma y luego, cuando un usuario regresa al sitio, aprovechará el locale especificado en la cookie al redirigir desde /
a la ubicación de locale correcta.
Por ejemplo, si un usuario prefiere el locale fr
en su cabecera accept-language pero hay una cookie NEXT_LOCALE=en
configurada, al visitar /
el usuario será redirigido a la ubicación del locale en
hasta que la cookie sea eliminada o expire.
Optimización para Motores de Búsqueda
Como Next.js sabe qué idioma está visitando el usuario, agregará automáticamente el atributo lang
a la etiqueta <html>
.
Next.js no sabe sobre variantes de una página, por lo que depende de ti agregar las etiquetas meta hreflang
usando next/head
. Puedes aprender más sobre hreflang
en la documentación de Google Webmasters.
¿Cómo funciona esto con Generación Estática?
Nota que el Enrutamiento Internacionalizado no se integra con
output: 'export'
ya que no aprovecha la capa de enrutamiento de Next.js. Las aplicaciones híbridas de Next.js que no usanoutput: 'export'
tienen soporte completo.
Rutas Dinámicas y Páginas getStaticProps
Para páginas que usan getStaticProps
con Rutas Dinámicas, todas las variantes de locale de la página que se desean pre-renderizar deben devolverse desde getStaticPaths
. Junto con el objeto params
devuelto para paths
, también puedes devolver un campo locale
especificando qué locale deseas renderizar. Por ejemplo:
export const getStaticPaths = ({ locales }) => {
return {
paths: [
// si no se proporciona `locale` solo se generará el defaultLocale
{ params: { slug: 'post-1' }, locale: 'en-US' },
{ params: { slug: 'post-1' }, locale: 'fr' },
],
fallback: true,
}
}
Para páginas Optimizadas Estáticamente de Forma Automática y páginas no dinámicas con getStaticProps
, se generará una versión de la página para cada locale. Esto es importante considerarlo porque puede aumentar los tiempos de construcción dependiendo de cuántos locales estén configurados dentro de getStaticProps
.
Por ejemplo, si tienes 50 locales configurados con 10 páginas no dinámicas que usan getStaticProps
, esto significa que getStaticProps
se llamará 500 veces. Se generarán 50 versiones de las 10 páginas durante cada construcción.
Para disminuir el tiempo de construcción de páginas dinámicas con getStaticProps
, usa un modo fallback
. Esto te permite devolver solo las rutas y locales más populares desde getStaticPaths
para pre-renderizado durante la construcción. Luego, Next.js construirá las páginas restantes en tiempo de ejecución según sean solicitadas.
Páginas Optimizadas Estáticamente de Forma Automática
Para páginas que son optimizadas estáticamente de forma automática, se generará una versión de la página para cada locale.
Páginas No Dinámicas con getStaticProps
Para páginas no dinámicas con getStaticProps
, se genera una versión para cada locale como se mencionó anteriormente. getStaticProps
se llama con cada locale
que se está renderizando. Si deseas excluir un cierto locale del pre-renderizado, puedes devolver notFound: true
desde getStaticProps
y esa variante de la página no se generará.
export async function getStaticProps({ locale }) {
// Llama a un endpoint de API externo para obtener posts.
// Puedes usar cualquier biblioteca de obtención de datos
const res = await fetch(`https://.../posts?locale=${locale}`)
const posts = await res.json()
if (posts.length === 0) {
return {
notFound: true,
}
}
// Al devolver { props: posts }, el componente Blog
// recibirá `posts` como prop en tiempo de construcción
return {
props: {
posts,
},
}
}
Límites para la configuración i18n
locales
: 100 locales en totaldomains
: 100 elementos de dominio de locale en total
Nota importante: Estos límites se agregaron inicialmente para evitar posibles problemas de rendimiento en tiempo de construcción. Puedes solucionar estos límites con enrutamiento personalizado usando Middleware en Next.js 12.