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.
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.mjsimport { 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.
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.