Helpers de astro:i18n: construir URLs por idioma
Dejar de teclear a mano las direcciones localizadas y derivarlas de la configuración con las funciones del módulo virtual astro:i18n. getRelativeLocaleUrl y getAbsoluteLocaleUrl para una URL por idioma, sus variantes en lista para recorrer todos los idiomas de golpe, y el par getPathByLocale y getLocaleByPath que traduce entre el código de un idioma y el segmento que se ve en la URL cuando usas path y codes personalizados.
getRelativeLocaleUrl: la dirección derivada
La función central del módulo es getRelativeLocaleUrl. Recibe un código de idioma y, opcionalmente, una ruta interna, y devuelve la dirección relativa correcta para ese idioma. Lo importante no es que concatene cadenas —eso lo haría cualquiera— sino que aplica toda tu configuración de rutas: el prefijo o su ausencia según prefixDefaultLocale, la barra final de trailingSlash, el subdirectorio de base, la forma de build.format. Una sola llamada carga con todas esas decisiones.
---import { getRelativeLocaleUrl } from 'astro:i18n';const inicioEn = getRelativeLocaleUrl('en'); // '/en/'const blogEn = getRelativeLocaleUrl('en', 'blog'); // '/en/blog/'const blogEs = getRelativeLocaleUrl('es', 'blog'); // '/blog/' si es es el default sin prefijo---<a href={blogEn}>Read in English</a>Fíjate en la última línea del comentario: si es es el idioma por defecto y prefixDefaultLocale es false, getRelativeLocaleUrl('es', 'blog') no devuelve /es/blog/ sino /blog/, porque el default no lleva prefijo. Esa es exactamente la lógica que no querrías reimplementar a mano en cada plantilla, y la razón de que la función exista: encapsula la regla completa de tu enrutado en un único punto, y tú solo dices qué idioma y qué ruta.
El tercer argumento, opcional, es un objeto de opciones. El más útil es normalizeLocale, activo por defecto, que pasa el código a minúsculas y forma canónica; puedes desactivarlo si necesitas preservar un código tal cual lo escribiste. La regla mental es simple: para cualquier enlace interno que dependa del idioma, esta función es la fuente, y el literal es el error.
getAbsoluteLocaleUrl y las variantes en lista
Cuando una dirección tiene que salir de tu sitio —una etiqueta canónica, un hreflang, una URL en un feed o en datos estructurados— no basta con la ruta relativa: hace falta el dominio completo. Para eso está getAbsoluteLocaleUrl, que hace lo mismo que su hermana relativa pero antepone el site de tu configuración. Sin site declarado no puede trabajar, del mismo modo que el sitemap o las canónicas tampoco pueden.
---import { getAbsoluteLocaleUrl } from 'astro:i18n';// con site: 'https://misitio.com'const canon = getAbsoluteLocaleUrl('fr', 'blog');// 'https://misitio.com/fr/blog/'---<link rel="canonical" href={canon} />Pero el patrón que de verdad brilla en un sitio multilingüe es el de las listas. getRelativeLocaleUrlList y getAbsoluteLocaleUrlList reciben solo la ruta —no un idioma— y devuelven un array con esa misma página en todos los idiomas configurados. Es el material exacto con el que se construye un selector de idioma o el bloque de etiquetas hreflang, sin que tengas que enumerar los locales a mano.
---import { getRelativeLocaleUrlList } from 'astro:i18n';const versiones = getRelativeLocaleUrlList('blog');// ['/blog/', '/en/blog/', '/fr/blog/']---<ul> {versiones.map((url) => <li><a href={url}>{url}</a></li>)}</ul>La ventaja no es ahorrar tres líneas: es que el selector se deriva de locales. Añade mañana un cuarto idioma a la configuración y el selector crece solo, sin que toques la plantilla. Lo mismo vale para las etiquetas hreflang de la lección de SEO: nacen de la misma lista, de modo que el conjunto de alternativas nunca queda desincronizado con los idiomas reales del sitio.
getPathByLocale y getLocaleByPath: código contra segmento
El último par de funciones cobra sentido cuando usas los locales con forma de objeto que vimos en la lección anterior: aquellos donde el path visible en la URL difiere de los codes de idioma. En cuanto la URL dice /espanol/ pero el código es es, necesitas traducir en ambas direcciones, y para eso están estas dos funciones inversas.
getPathByLocale recibe un código de idioma y devuelve el segmento de URL asociado; getLocaleByPath hace el viaje contrario, del segmento al código. La primera la usas cuando tienes un código —de una cabecera, de una preferencia— y quieres construir la URL; la segunda, cuando lees un segmento de la URL y quieres saber a qué idioma pertenece.
import { getPathByLocale, getLocaleByPath } from 'astro:i18n';// con locales: [{ path: 'espanol', codes: ['es', 'es-ES'] }, 'en']getPathByLocale('es'); // 'espanol'getPathByLocale('es-ES'); // 'espanol' (ambos codes apuntan al mismo path)getLocaleByPath('espanol'); // 'es' (el primer code asociado)La asimetría es reveladora: varios codes pueden apuntar al mismo path —es y es-ES comparten /espanol/—, pero un path devuelve un solo código, el primero de su lista. Es la misma relación de muchos a uno que hay entre las variantes regionales de un idioma y su carpeta única en el sitio. Cuando tus locales son cadenas simples, path y code coinciden y estas funciones son triviales; su valor aparece justo cuando decides separar lo que el usuario ve de lo que el navegador anuncia.
Conviene subrayar que estas dos funciones operan sobre la configuración, no sobre la petición: no saben nada del visitante ni de la URL en curso, solo consultan el mapa de locales que declaraste. Eso las hace utilizables en cualquier contexto —una página estática, un endpoint, un script de build— sin depender de que exista una petición viva, a diferencia de la negociación por cabecera que veremos más adelante. Son traducciones puras entre dos representaciones del mismo idioma, y por serlo, están siempre disponibles.