El routing i18n: locales, defaultLocale y prefijos
Activar el enrutado internacional declarando la clave i18n en la configuración: enumerar los idiomas con locales, elegir el principal con defaultLocale y gobernar la forma de las URLs con routing. El papel decisivo de prefixDefaultLocale, la disposición de carpetas que cada modo exige, los locales con path y codes personalizados, y qué hace exactamente redirectToDefaultLocale con la raíz del sitio.
Un sitio en varios idiomas no es un sitio traducido: es un sitio que aprende a multiplicar sus rutas. La misma página tiene que existir en español, en inglés y en francés, cada versión bajo una dirección que la distinga, y alguien debe decidir cómo se dibuja esa dirección: si el idioma principal lleva prefijo o vive en la raíz, qué pasa cuando alguien pide / a secas, cómo casan las carpetas con las URLs. La clave i18n de astro.config.mjs es donde se toman esas decisiones de una vez y para todo el proyecto. No traduce una sola palabra —eso vendrá después—, pero fija la gramática de las URLs sobre la que todo lo demás se apoya.
- Activar el enrutado internacional declarando la clave
i18nenastro.config.mjs. - Enumerar los idiomas con
localesy elegir el principal condefaultLocale. - Gobernar la forma de las URLs con
prefixDefaultLocaley su disposición de carpetas. - Decidir el destino de la raíz con
redirectToDefaultLocaley conocer loslocalesconpathycodes.
locales y defaultLocale: declarar el mapa de idiomas
Todo empieza con dos datos: qué idiomas hablas y cuál es el principal. locales es un array con los códigos que tu sitio soporta; defaultLocale señala uno de ellos como el idioma de referencia, el que se sirve cuando nada indica otra cosa. Con esas dos líneas Astro ya sabe distinguir una ruta localizada de una que no lo está, y activa todo el aparato de helpers y propiedades que veremos en las lecciones siguientes.
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
i18n: {
locales: ['es', 'en', 'fr'],
defaultLocale: 'es',
},
});
Conviene entender qué hace y qué no hace esta declaración. Activa el reconocimiento de idiomas en las URLs y habilita el módulo virtual astro:i18n; pero no genera ni una sola página por su cuenta. Las carpetas siguen siendo tuyas: eres tú quien crea src/pages/en/ para el inglés y src/pages/fr/ para el francés. Astro no traduce ni duplica contenido, solo interpreta la forma de las direcciones y te da las herramientas para construirlas. La internacionalización de Astro es una capa de enrutado, no un motor de traducción.
El defaultLocale debe ser uno de los valores presentes en locales, o Astro protesta en el arranque. Esa redundancia aparente es deliberada: obliga a que el idioma por defecto sea siempre un idioma declarado, nunca un huérfano. A partir de aquí, la pregunta que lo cambia todo es cómo se ve en la URL ese idioma principal, y esa la responde routing.
prefixDefaultLocale: dos mundos de URLs
Dentro de routing, la opción prefixDefaultLocale es la decisión estructural del nivel entero, porque define dos disposiciones de carpetas incompatibles. Su valor por defecto es false, y significa que solo los idiomas secundarios llevan prefijo. El idioma por defecto vive en la raíz de src/pages, sin marca alguna en su dirección.
i18n: {
locales: ['es', 'en', 'fr'],
defaultLocale: 'es',
routing: {
prefixDefaultLocale: false,
},
},
Con false, la disposición es asimétrica y suele ser la más natural para un sitio que nace en un idioma y se abre a otros. El fichero src/pages/index.astro sirve / en español; src/pages/en/index.astro sirve /en/ en inglés; src/pages/fr/blog.astro sirve /fr/blog. El español no tiene carpeta propia porque es la raíz.
Cuando pones prefixDefaultLocale: true, el mundo se vuelve simétrico: todos los idiomas llevan prefijo, sin excepción. Ya no hay contenido en la raíz de src/pages; cada página vive bajo la carpeta de su idioma, incluido el principal.
routing: {
prefixDefaultLocale: true,
},
Ahora src/pages/es/index.astro sirve /es/, src/pages/en/index.astro sirve /en/, y la raíz / deja de tener un dueño natural: nadie vive ahí. Esa simetría tiene una virtud —ninguna URL es ambigua sobre su idioma— y un coste —hay que decidir qué ocurre cuando alguien escribe / sin más—, y ese coste lo paga la siguiente opción.
prefixDefaultLocale false
Asimetrico. El idioma por defecto vive en la raiz de src pages sin prefijo. Los demas en su carpeta.
prefixDefaultLocale true
Simetrico. Todos los idiomas llevan prefijo y viven en su carpeta. La raiz queda vacia.
redirectToDefaultLocale
Con prefijo en todos, decide si la raiz redirige al idioma por defecto o responde con un 404.
path y codes
Un locale puede ser un objeto: el path que se ve en la URL y los codes que casan en la negociacion.
redirectToDefaultLocale y los locales con forma de objeto
Cuando prefixDefaultLocale es true, la raíz queda sin contenido, y redirectToDefaultLocale decide su suerte. Con su valor por defecto —true—, quien pide / recibe una redirección a /es/, el idioma principal, de modo que la portada nunca queda muerta. Si lo pones en false, la raíz responde con un 404 y te toca a ti resolverla, típicamente con un middleware que negocie el idioma del visitante, como veremos en la cuarta lección.
routing: {
prefixDefaultLocale: true,
redirectToDefaultLocale: false,
},
La otra pieza fina es que un elemento de locales no tiene por qué ser una cadena. Puede ser un objeto con dos campos: path, el segmento que de verdad aparece en la URL, y codes, la lista de códigos de idioma que ese segmento representa a efectos de negociación por cabecera. Así separas lo que el usuario ve de lo que el navegador anuncia.
i18n: {
defaultLocale: 'es',
locales: [
'en',
{ path: 'espanol', codes: ['es', 'es-ES', 'es-MX'] },
],
},
Aquí la URL usa /espanol/, más legible que un código seco, mientras que codes mantiene el vínculo con es, es-ES y es-MX para que la detección de preferencia del visitante siga funcionando. Esta dualidad path–codes es la que las funciones getPathByLocale y getLocaleByPath traducen en un sentido y en otro, protagonistas de la lección siguiente.
Queda una tercera vía que conviene al menos nombrar: routing no solo admite un objeto de opciones, también acepta el valor 'manual'. Con él renuncias al enrutado i18n automático de Astro y asumes el control total desde un middleware, apoyándote en las utilidades que exporta astro:i18n. Es la puerta de escape para sitios con reglas de idioma que no encajan en prefixDefaultLocale ni redirectToDefaultLocale, y la abriremos en la lección de negociación por cabecera; por ahora basta saber que existe y que el modo declarativo de este capítulo cubre la inmensa mayoría de los casos reales.
flowchart TD REQ[peticion a una URL] --> ROUTE[el enrutado i18n mira el prefijo] ROUTE -->|con prefijo en o fr| LOC[locale de esa ruta] ROUTE -->|sin prefijo y default en raiz| DEF[defaultLocale] ROUTE -->|raiz vacia y prefijo en todos| RED[redirectToDefaultLocale decide] LOC --> PAGE[renderiza la pagina del idioma] DEF --> PAGE style REQ fill:#89b4fa,color:#11111b style ROUTE fill:#f9e2af,color:#11111b style PAGE fill:#a6e3a1,color:#11111b
La clave i18n no genera rutas: reconoce idiomas en las direcciones y habilita astro:i18n. Las carpetas por idioma dentro de src/pages las creas tú. Si declaras locales: ['es', 'en'] pero no existe ninguna carpeta en, no habrá páginas en inglés; solo estará el andamiaje listo para cuando las escribas. Separar el enrutado de la traducción es justo lo que te deja elegir después cómo organizas el contenido.
Pasar de false a true no es tocar un interruptor: es reorganizar src/pages. Con false, el idioma por defecto está en la raíz; con true, tiene que mudarse a su propia carpeta —src/pages/es/— y la raíz queda vacía. Si cambias el valor sin mover los ficheros, obtendrás 404 en masa. Decide la disposición al principio del proyecto, cuando mover cuatro carpetas cuesta poco, y no a mitad de camino.
Que un idioma sea el defaultLocale no lo hace superior ni más importante: solo lo designa como el caso de referencia, el que se sirve cuando la URL no especifica otro. Puedes cambiar cuál es el principal editando una línea, y con prefixDefaultLocale: false decidir además que ese papel se traduzca en vivir sin prefijo. La elección suele seguir a tu audiencia mayoritaria o al idioma en que nació el proyecto, pero es reversible y no ata al resto de la configuración: es una convención de referencia, nunca una jerarquía de valor entre lenguas.
La idea profunda que esconde esta configuración es que una página deja de identificarse con una sola URL. Hasta ahora, en el enrutado por ficheros, un archivo era una dirección: src/pages/blog.astro era /blog, y punto. La internacionalización rompe esa identidad de uno a uno e introduce una distinción que la ingeniería de sistemas conoce bien: la diferencia entre la identidad de un recurso y sus representaciones. La página del blog es ahora una sola entidad conceptual —un contenido, un propósito— que se manifiesta en varias representaciones concretas, una por idioma, cada una con su dirección. Y el trabajo de i18n es precisamente gobernar cómo se derivan esas direcciones a partir de dos coordenadas ortogonales: qué contenido y en qué idioma. prefixDefaultLocale es la política que decide si esas coordenadas se ven siempre en la URL o solo cuando difieren del caso por defecto, exactamente como un sistema de codificación decide si el caso más frecuente se marca o se deja implícito para ahorrar. Por eso la elección no es cosmética: al escoger si el idioma principal lleva prefijo estás escogiendo qué consideras el caso base de tu sitio, el estado que no necesita anunciarse. Y al declararlo una vez en la configuración, en lugar de repartir esa decisión por cientos de enlaces, consigues que toda la lógica de direcciones se derive de una fuente única. Cambiar la política de idiomas de tu sitio entero pasa a ser editar cuatro líneas, no reescribir el árbol de rutas. Esa es la marca de una buena abstracción: convierte una decisión que estaba dispersa e implícita en un parámetro explícito y central.
- Declara
i18nconlocales: ['es', 'en', 'fr']ydefaultLocale: 'es', y creasrc/pages/index.astromássrc/pages/en/index.astro. - Con
prefixDefaultLocale: false, comprueba que el español sale en/y el inglés en/en/. - Cambia a
prefixDefaultLocale: true, mueve el español asrc/pages/es/y observa cómo/redirige a/es/. - Pon
redirectToDefaultLocale: falsey verifica que ahora/responde con un 404, dejando el hueco que un middleware deberá resolver.