por: Carlos Quesada

¿Cómo traducir rutas en Astro para subpáginas?

  • Tutorial
  • Astro
  • i18n

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.


Busco ayudarte

Newsletter

Recibe información relevante, recursos, técnicas, tutoriales y noticias sobre el Diseño UX, Product Design y Desarrollo Web