Cómo localizamos nuestro sitio a 9 idiomas con Astro
Lanzamos el enrutamiento de idiomas para 9 idiomas en este sprint. Aquí está la versión honesta de cómo sucedió, incluido el error que nos estaba perjudicando silenciosamente en producción antes de que lo notáramos.
El descubrimiento
Allá por el Sprint 1, construimos el andamiaje de i18n que cabría esperar: archivos JSON de idioma para los 9 idiomas (inglés, español, árabe, alemán, francés, japonés, portugués, ruso, chino), un ayudante getTranslation(), getLocalePath() para construir URLs sensibles al idioma, y etiquetas hreflang en cada página apuntando a las 9 variantes de idioma.
Lo que no construimos: páginas reales en esas URLs de idioma.
Cada página en src/pages/ tenía codificado const locale = "en" as const. La infraestructura de traducción existía y funcionaba, pero no había nada detrás. Eso significaba que BaseLayout.astro emitía etiquetas hreflang como https://proman365.com/es/pricing/ en cada página, para URLs que devolvían un 404 en producción.
Esto no es un error cosmético. Los motores de búsqueda usan las etiquetas hreflang para entender qué URLs son variantes de idioma entre sí. Anunciar 404 a gran escala es una señal SEO negativa activa: le dice a los rastreadores que los metadatos de tu sitio no coinciden con la realidad, lo cual erosiona la confianza en todos tus metadatos, no solo en la parte rota.
La solución: extracción de plantillas, no duplicación
La solución ingenua es copiar cada página 8 veces, una por cada idioma que no sea inglés, y codificar una constante de idioma diferente en cada copia. Rechazamos eso de inmediato: 8 veces los archivos significa 8 veces la degradación en el momento en que alguien toca una página y olvida las otras 7 copias.
El enfoque que tomamos en su lugar: extraer el contenido de cada página en un componente de plantilla que toma locale como propiedad, y luego renderizar esa plantilla desde dos tipos de rutas.
Para las 12 páginas de Nivel 1 (inicio, precios, contacto, socios, ayuda y las seis páginas de funciones), cada una se movió de src/pages/*.astro a src/page-templates/*.astro, reemplazando la constante de idioma codificada por:
interface Props {
locale: Locale;
}
const { locale } = Astro.props;
La ruta original en inglés se convierte en un envoltorio delgado: unas pocas líneas que solo importan la plantilla y la renderizan con locale="en". Una nueva ruta espejo bajo src/pages/[locale]/ renderiza la misma plantilla para los 8 idiomas no ingleses, usando getStaticPaths() para generar una página por idioma.
Esto funcionó de forma limpia porque la lógica de cada página de Nivel 1 (getTranslation(locale), los constructores de etiquetas meta, el marcado de esquema) ya estaba parametrizada sobre esa única constante locale desde el andamiaje del Sprint 1. No estábamos adaptando i18n a estas páginas; finalmente estábamos conectando el i18n que había estado sin usar.
El resultado: 96 páginas estáticas nuevas (12 páginas × 8 idiomas) del lanzamiento de Nivel 1, seguido de una segunda pasada extendiendo el mismo patrón a las 64 páginas de comparación (8 competidores × 8 idiomas).
Controlar hreflang detrás de una propiedad localized
Arreglar la brecha de enrutamiento no arregla automáticamente el problema de hreflang: aún necesitas que las etiquetas solo afirmen lo que es realmente cierto. Añadimos una propiedad localized?: boolean a BaseLayout y PageLayout, con valor predeterminado false.
Cuando localized es false, una página emite exactamente dos etiquetas hreflang: en y x-default. Cuando es true, emite el conjunto completo de 10 (los 9 idiomas más x-default). Solo las plantillas de Nivel 1 y las páginas de comparación pasan localized={true}.
Establecer false por defecto fue una decisión de seguridad deliberada. Una página tiene que optar explícitamente por afirmar que tiene 9 variantes de idioma. Si alguien añade una página nueva mañana y olvida pensar en los idiomas, falla de forma segura: metadatos solo en inglés, no otra ronda de URLs fantasma.
Lo que deliberadamente no localizamos
No todo recibió el tratamiento de idiomas, y esa fue una decisión, no un descuido:
- El blog permanece solo en inglés indefinidamente. Esta es una práctica estándar para blogs de marketing SaaS, y traducir contenido extenso con máquinas produce un texto peor que simplemente no traducirlo. Si la analítica eventualmente justifica contenido dedicado por idioma, lo reconsideraremos.
- Las páginas legales (privacidad, términos, política de cookies) permanecen en inglés. Ese texto es contenido legal generado por Termly; traducirlo no es una decisión que estemos en posición de tomar unilateralmente.
- La página 404 permanece en inglés: el alojamiento estático sirve un único 404 sin importar la ruta solicitada, así que no hay una forma limpia de localizarla sin un modelo de alojamiento diferente.
- El RSS ya era solo en inglés y estaba correctamente excluido de hreflang; no se necesitaron cambios ahí.
La lista de conclusiones
Si estás haciendo algo similar en un sitio estático de Astro:
- Audita antes de construir. Verifica si tus etiquetas hreflang apuntan a páginas que realmente existen. Si tu andamiaje de i18n es anterior a tu enrutamiento, es posible que ya tengas este error.
- Extrae plantillas, no dupliques páginas. Una plantilla por página, rutas envoltorio delgadas específicas de idioma encima. La degradación ocurre en la capa del envoltorio si es que ocurre, y los envoltorios son baratos de mantener sincronizados.
- Los reclamos de metadatos predeterminados deben ser la afirmación verdadera más estrecha. Una bandera
localizedque por defecto es “no” significa que las páginas nuevas no pueden sobre-declarar accidentalmente. - Decide qué permanece sin traducir, a propósito, y anótalo. El blog, lo legal y las páginas de error son candidatos razonables para excluir, pero conviértelo en una decisión documentada, no en silencio.
- La traducción y el enrutamiento son problemas separados. Lanzar las rutas no requiere terminar las traducciones: el texto de respaldo en inglés en una ruta de idioma real sigue siendo más honesto que una clave de traducción funcional sin una ruta detrás.
Todavía tenemos trabajo de traducción manual por delante: muchas cadenas en los archivos de idiomas no ingleses son marcadores de posición en inglés esperando a un traductor humano. Pero las URLs ahora son reales, y los metadatos dejaron de mentir. Ese era el problema más urgente.
Alex Rivera
Gestor de proyectos y consultor de flujos de trabajo. Ayuda a los equipos a encontrar herramientas que se ajusten a su forma real de trabajar.