¿Cómo traducir rutas en Astro para subpáginas?
Desde que comencé a usar la API de i18n de Astro para aplicarla en mis proyectos me surgió el problema que seguramente te haya traído hasta aquí. Cómo traducir las páginas internas o subpáginas dentro de carpetas como proyectos/mi-proyecto
para que encuentre su homóloga en el idioma a seleccionar en el momento que el usuario presiona el selector de cambio de idioma. La lógica es que no lleve a projects/my-project
.
El problema está en que el código que ofrece por defecto la documentación de Astro a enero de 2025 solamente funciona para páginas sin subcarpeta. Ya que el script básicamente toma solo el último trozo de URl dividido por un backslash /
. traduciendo y redireccionando solo a /my-project
, lo que termina llevando al usuario a una página 404 o el fallback del idioma.
En los siguientes ejemplos te traigo la solución para que lo soluciones al instante.
Vamos a ello 👇
Primeros pasos para traducir las rutas en Astro
Busca la carpeta i18n/
dentro de src y localiza los archivos: ui.ts
y utils.ts
además del LanguagePicker.astro
que debes tener dentro de components/
.
para una visión completa del manejo de la API i18n de Astro te aconsejo que visites la documentación oficial en Español y el enrutamiento i18n en las dos páginas que Astro documenta su funcionalidad.
├── src/
│ ├── components/
│ │ └── ui/
│ │ └── LanguagePicker.astro // Agregar
│ ├── i18n/
│ └── ui.ts
│ └── utils.ts
1. Actualizar funciones en utils.ts
Debemos acceder al archivo utils.ts
y actualizar las funciones useTranslatedPath()
y getRouteFromUrl()
. Así quedaría el archivo:
utils.ts
import { ui, defaultLang, showDefaultLang, routes, languages } from './ui';
export function getLangFromUrl(url: URL) {
const [, lang] = url.pathname.split('/');
if (lang in ui) return lang as keyof typeof ui;
return defaultLang;
}
export function useTranslations(lang: keyof typeof ui) {
return function t(key: keyof (typeof ui)[typeof defaultLang]) {
return ui[lang][key] || ui[defaultLang][key];
};
}
//Actualizamos función useTranslatedPath
export function useTranslatedPath(lang: keyof typeof ui) {
return function translatePath(path: string, l: string = lang) {
const pathName = path.replaceAll('/', '');
const hasTranslation =
defaultLang !== l &&
(routes[l as keyof typeof routes] as Record<string, string>) !== undefined &&
(routes[l as keyof typeof routes] as Record<string, string>)[pathName] !== undefined;
const translatedPath = hasTranslation
? '/' + (routes[l as keyof typeof routes] as Record<string, string>)[pathName]
: path;
const translatedPathReplaced = translatedPath.replaceAll('.', '/');
return !showDefaultLang && l === defaultLang ? translatedPathReplaced : `/${l}${translatedPathReplaced}`;
};
}
//Actualizamos función getRouteFromUrl
export function getRouteFromUrl(url: URL): string | undefined {
const pathname = new URL(url)?.pathname;
const path = pathname
?.split('/')
.filter(Boolean)
.filter((part) => !Object.keys(languages).includes(part))
.join('.');
if (!path) {
return undefined;
}
const currentLang = getLangFromUrl(url);
if (defaultLang === currentLang) {
const route = Object.values(routes)[0];
const pathTyped = path as keyof typeof route;
return pathTyped in route ? pathTyped : undefined;
}
const getKeyByValue = (obj: Record<string, string>, value: string): string | undefined => {
return Object.keys(obj).find((key) => obj[key] === value);
};
const reversedKey = getKeyByValue(routes[currentLang], path);
if (reversedKey !== undefined) {
return reversedKey;
}
return undefined;
}
2. Tracucir las URLs
Una vez tenemos el archivo actualizado ya podemos traducir páginas internas. Ahora en el archivo ui.ts
usaremos un punto (.) en vez de un backslash (/) para separar las carpetas como te muestro a continuación:
export const languages = {
es: '🇪🇸 es',
en: '🇺🇸 en',
};
export const defaultLang = 'es';
export const showDefaultLang = false;
export const ui = {
es: {
'nav.proyectos': 'Proyectos',
'nav.contacto': 'Contacto',
},
en: {
'nav.proyectos': 'Projects',
'nav.contacto': 'Contact',
},
} as const;
export const routes = {
es: {
contacto: 'contacto',
'proyectos.mi-proyecto': 'proyectos.mi-proyecto',
},
en: {
contacto: 'contact',
'proyectos.mi-proyecto': 'projects.my-project',
},
};
Si tienes dudas de cómo nombrar las carpetas, aquí te dejo el ejemplo:
├── src/
│ ├── pages/
│ └── en/
│ └── projects/
│ └── my-project.astro
│ └── proyectos
│ └── mi-proyecto.astro
Si notas en este caso tengo el idioma español por defecto por lo que no necesito tenerlo dentro de una carpeta en/
3. Hacer que el selector identifique la URL completa y la traduzca
Por último debemos percatarnos de que el archivo LanguagePicker.astro
tiene el enlace correcto para redireccionara a la misma página en el otro idioma. Aquí el ejemplo del selector de idiomas:
---
import { languages } from '@i18n/ui';
import { getLangFromUrl, getRouteFromUrl, useTranslatedPath } from '@i18n/utils';
const currentLang = getLangFromUrl(Astro.url);
const translatePath = useTranslatedPath(currentLang);
const route = getRouteFromUrl(Astro.url);
---
<ul class="flex gap-2">
{
Object.entries(languages).map(([lang, label]) => (
<li class:list={[currentLang === lang ? 'active hidden' : 'language--item', 'lang']}>
<!-- Aquí es donde agregamos la lógica para que cambia a la URL hermana traducida -->
<a href={translatePath(`/${route ? route : ''}`, lang)}>{label}</a>
</li>
))
}
</ul>
Conclusiones
Hemos visto en cómo unos pocos pasos añadimos la funcionalidad de cambiar a la página traducida en URLs con carpetas internas o subpáginas en el framework Astro. Si te ha sido útil, escríbeme o comparte el artículo con tus colegas.
→ Si tienes dudas puedes suscribirte o escribirme a info@cquesada.es
Te atenderé lo antes posible.