Cómo hacer que un sitio Astro 6 soporte 9 idiomas ― Traducción automática de 136 artículos y arquitectura multilingüe
Índice
- Estrategia multilingüe
- Definición del alcance
- Diseño de URLs
- Implementación de la base i18n
- Configuración i18n de Astro
- Utilidades de traducción
- Patrón View Component
- Soporte multilingüe del contenido del blog
- Estructura de directorios
- Utilidades de resolución de contenido
- Reglas de los archivos de traducción
- Flujo de trabajo de traducción
- View Components multilingües
- Implementación de BlogPostPage
- Implementación de páginas de lista
- Consideraciones de build
- Escape en frontmatter YAML
- Filtrado de rutas de imágenes OG
- Soporte multilingüe de Pages CMS
- UI de cambio de idioma
- Visualización multilingüe de etiquetas
- Datos de autor multilingües
- SEO para sitio multilingüe
- Soporte hreflang en sitemap
- Soporte de idioma en datos estructurados JSON-LD
- Feeds RSS multilingües
- Resumen
Actualizamos el sitio web oficial de Acecore de solo japonés a soporte para 9 idiomas. Este artículo cubre todo el proceso: internacionalización de la UI, traducción de 17 artículos × 8 idiomas = 136 archivos, y configuración multilingüe de Pages CMS.
Estrategia multilingüe
Definición del alcance
Abordamos el soporte multilingüe en tres fases:
- Base i18n: Configuración de enrutamiento i18n integrado de Astro, utilidades de traducción y archivos JSON de traducción para 9 idiomas
- Traducción de textos UI: Textos de componentes en encabezado, pie de página, barra lateral y todas las páginas
- Traducción de artículos: Los 17 artículos traducidos a 8 idiomas (136 archivos generados)
Diseño de URLs
Adoptamos prefixDefaultLocale: false de Astro, sirviendo japonés en la raíz (/blog/...) y otros idiomas con prefijos (/en/blog/..., /zh-cn/blog/..., etc.).
# Japonés (predeterminado)
/blog/astro-performance-tuning/
# Inglés
/en/blog/astro-performance-tuning/
# Chino simplificado
/zh-cn/blog/astro-performance-tuning/
Usar el mismo slug en todos los idiomas mantiene simple el mapeo de URLs al cambiar de idioma.
Implementación de la base i18n
Configuración i18n de Astro
Se configura el enrutamiento i18n en astro.config.mjs.
// astro.config.mjs
export default defineConfig({
i18n: {
defaultLocale: 'ja',
locales: ['ja', 'en', 'zh-cn', 'es', 'pt', 'fr', 'ko', 'de', 'ru'],
routing: {
prefixDefaultLocale: false,
},
},
})
Utilidades de traducción
Los archivos de configuración, funciones utilitarias y archivos JSON de traducción se consolidan en src/i18n/.
// src/i18n/utils.ts
export function t(locale: Locale, key: string): string {
return translations[locale]?.[key]
?? translations[defaultLocale][key]
?? key
}
Los archivos de traducción están en formato JSON bajo src/i18n/locales/, gestionando aproximadamente 100 claves para navegación, pie de página, UI del blog y metadatos.
Patrón View Component
La implementación de páginas usa el Patrón View Component. El diseño y la lógica se centralizan en src/views/, mientras los archivos de ruta (src/pages/) son wrappers ligeros que simplemente pasan el locale.
---
// src/pages/[locale]/about.astro (archivo de ruta)
import AboutPage from '../../views/AboutPage.astro'
const { locale } = Astro.params
---
<AboutPage locale={locale} />
Este diseño elimina la duplicación de lógica entre la ruta japonesa (/about) y las rutas multilingües (/en/about).
Soporte multilingüe del contenido del blog
Estructura de directorios
Los artículos traducidos se colocan en subdirectorios con código de idioma. El loader glob de Astro los detecta automáticamente de forma recursiva con el patrón **/*.md.
src/content/blog/
astro-performance-tuning.md # Japonés (base)
website-renewal.md
en/
astro-performance-tuning.md # Versión en inglés
website-renewal.md
zh-cn/
astro-performance-tuning.md # Versión en chino simplificado
website-renewal.md
es/
...
Utilidades de resolución de contenido
Se implementaron 3 funciones en src/utils/blog-i18n.ts.
// Determinar si es un artículo base (sin barra en el ID = base)
export function isBasePost(post: CollectionEntry<'blog'>): boolean {
return !post.id.includes('/')
}
// Eliminar prefijo de locale del ID para obtener el slug base
export function getBaseSlug(postId: string): string {
const idx = postId.indexOf('/')
return idx !== -1 ? postId.slice(idx + 1) : postId
}
// Obtener la versión localizada de un artículo base (fallback al original)
export function localizePost(
post: CollectionEntry<'blog'>,
allPosts: CollectionEntry<'blog'>[],
locale: Locale,
): CollectionEntry<'blog'> {
if (locale === defaultLocale) return post
return allPosts.find((p) => p.id === `${locale}/${post.id}`) ?? post
}
El punto clave es no modificar el esquema existente de la colección de contenido. El loader glob de Astro reconoce automáticamente los archivos en subdirectorios con IDs como en/astro-performance-tuning, sin necesidad de cambios de configuración.
Reglas de los archivos de traducción
Los archivos de traducción se generaron siguiendo estas reglas:
- Las claves del frontmatter permanecen en inglés (
title,description,date, etc.) - Los valores de etiquetas se mantienen en japonés (
['技術', 'Astro'], etc.) - URLs, rutas de imágenes, bloques de código y HTML no se modifican
- Fecha y autor permanecen sin cambios
- Texto del cuerpo y valores de texto del frontmatter (title, description, callout, FAQ, etc.) se traducen
Flujo de trabajo de traducción
El proceso de traducción sigue estos pasos:
- Crear inglés como idioma intermedio: Traducir del japonés original al inglés
- Traducir del inglés a cada idioma: Expandir desde el inglés a 7 idiomas
- Procesamiento por lotes: Procesar 5-6 artículos a la vez con GitHub Copilot
La traducción en dos etapas (japonés → inglés → idiomas destino) reduce la variación de calidad. Pasar por el inglés como idioma intermedio produce calidad más estable que traducir directamente del japonés a cada idioma.
View Components multilingües
Implementación de BlogPostPage
La página de artículos obtiene la versión locale del contenido usando localizePost() y la asigna a una variable de plantilla.
---
// src/views/BlogPostPage.astro
const localizedPost = localizePost(basePost, allPosts, locale)
const post = localizedPost // las referencias existentes de la plantilla funcionan tal cual
---
Este enfoque permite el soporte multilingüe sin cambiar ninguna referencia a post.data.title o post.body en la plantilla.
Implementación de páginas de lista
Las listas de blog, etiquetas, autores y archivos filtran solo artículos base con isBasePost(), y luego sustituyen con versiones traducidas usando localizePost() al momento de mostrar.
---
const allPosts = await getCollection('blog')
const basePosts = allPosts.filter(isBasePost)
const displayPosts = basePosts.map(p => localizePost(p, allPosts, locale))
---
Consideraciones de build
Escape en frontmatter YAML
Las traducciones al francés causaron problemas donde los apóstrofos (l'atelier, qu'on, etc.) conflictuaban con las comillas simples de YAML.
# NG: Error de análisis YAML
title: 'Le métavers est plus proche qu'on ne le pense'
# OK: Cambiar a comillas dobles
title: "Le métavers est plus proche qu'on ne le pense"
Se usó un script Node.js para corregir todos los archivos en lote. Texto en inglés como Acecore's tiene el mismo problema, por lo que el tipo de comillas debe considerarse al generar archivos de traducción.
Filtrado de rutas de imágenes OG
/blog/og/[slug].png.ts también capturaba slugs de artículos traducidos (en/aceserver-hijacked, etc.), causando errores de parámetros. Se resolvió filtrando con isBasePost().
export const getStaticPaths: GetStaticPaths = async () => {
const allPosts = await getCollection('blog')
const posts = allPosts.filter(isBasePost)
return posts.map((post) => ({
params: { slug: post.id },
props: { title: post.data.title },
}))
}
Soporte multilingüe de Pages CMS
Pages CMS (.pages.yml) solo apunta a archivos directamente bajo el directorio path especificado, por lo que los subdirectorios de traducción se registraron como colecciones individuales.
content:
- name: blog
label: ブログ(日本語)
path: src/content/blog
- name: blog-en
label: Blog(English)
path: src/content/blog/en
- name: blog-zh-cn
label: 博客(简体中文)
path: src/content/blog/zh-cn
# ... configurado para cada idioma
Las etiquetas se escriben en cada idioma para que sea inmediatamente claro qué colección corresponde a qué idioma en el CMS.
UI de cambio de idioma
Se añadió un componente LanguageSwitcher al encabezado, proporcionando una UI de cambio de idioma para escritorio y móvil. Al cambiar de idioma, los usuarios navegan al locale correspondiente de la misma página. En la primera visita, se detecta el navigator.language del navegador para redirección automática.
Visualización multilingüe de etiquetas
Las etiquetas de los artículos mantienen sus slugs en japonés en las URLs mientras solo se traduce el nombre visible. Esto evita la complejidad de enrutamiento mientras muestra las etiquetas en el idioma nativo del usuario.
// src/i18n/utils.ts
export function translateTag(tag: string, locale: Locale): string {
return t(locale, `tags.${tag}`) !== `tags.${tag}`
? t(locale, `tags.${tag}`)
: tag
}
Se añadió una sección tags a cada JSON de traducción, definiendo traducciones para los 25 tipos de etiquetas.
// en.json (extracto)
{
"tags": {
"技術": "Technology",
"セキュリティ": "Security",
"パフォーマンス": "Performance",
"アクセシビリティ": "Accessibility"
}
}
translateTag() se usa en 6 ubicaciones — tarjetas de artículos, barra lateral, índice de etiquetas y detalle de artículos — asegurando que todas las etiquetas se muestren unificadas en el idioma apropiado.
Datos de autor multilingües
Las biografías y listas de habilidades de los autores también cambian según el idioma. Se añadió un campo i18n a src/data/authors.json para almacenar las traducciones de cada idioma.
{
"id": "hatt",
"name": "hatt",
"bio": "代表取締役。Web制作・システム開発…",
"skills": ["TypeScript", "Astro", "..."]
"i18n": {
"en": {
"bio": "CEO and representative director. Web development...",
"skills": ["TypeScript", "Astro", "..."]
}
}
}
La utilidad getLocalizedAuthor() obtiene la información del autor apropiada para el locale.
// src/utils/blog-i18n.ts
export function getLocalizedAuthor(author: Author, locale: Locale) {
const localized = author.i18n?.[locale]
return localized ? { ...author, ...localized } : author
}
SEO para sitio multilingüe
Para maximizar los beneficios SEO del soporte multilingüe, implementamos mecanismos para que los motores de búsqueda identifiquen e indexen correctamente cada versión de idioma.
Soporte hreflang en sitemap
Se configuró la opción i18n de @astrojs/sitemap para generar automáticamente etiquetas xhtml:link rel="alternate" en el sitemap.
// astro.config.mjs
sitemap({
i18n: {
defaultLocale: 'ja',
locales: {
ja: 'ja',
en: 'en',
'zh-cn': 'zh-CN',
es: 'es',
pt: 'pt',
fr: 'fr',
ko: 'ko',
de: 'de',
ru: 'ru',
},
},
})
Esto genera enlaces hreflang para los 9 idiomas en cada URL, permitiendo que Google comprenda con precisión la correspondencia entre versiones de idioma.
Soporte de idioma en datos estructurados JSON-LD
Se añadió el campo inLanguage a los datos estructurados BlogPosting de los artículos, informando a los motores de búsqueda en qué idioma está escrito cada artículo.
// BlogPostPage.astro (extracto JSON-LD)
{
"@type": "BlogPosting",
"inLanguage": htmlLangMap[locale], // "ja", "en", "zh-CN", etc.
"headline": post.data.title,
// ...
}
Feeds RSS multilingües
Además del /rss.xml en japonés, se generan feeds RSS para cada versión de idioma (/en/rss.xml, /zh-cn/rss.xml, etc.). Los títulos y descripciones de los feeds se traducen por idioma, y la etiqueta <language> genera códigos de idioma compatibles con BCP47.
// src/pages/[locale]/rss.xml.ts
export const getStaticPaths = () =>
locales.filter((l) => l !== defaultLocale).map((l) => ({ params: { locale: l } }))
El <link rel="alternate" type="application/rss+xml"> en BaseLayout.astro también configura automáticamente la URL RSS apropiada para el locale.
Resumen
Aprovechando las funcionalidades i18n integradas de Astro 6, logramos soporte multilingüe de alta calidad incluso en un sitio estático.
- Base i18n: Sin prefijo para japonés con
prefixDefaultLocale: falsede Astro - Traducción de UI: Cero duplicación de lógica mediante el Patrón View Component
- Traducción de contenido: Enfoque de subdirectorios sin cambios de esquema
- Traducción de etiquetas: Slugs en japonés en URLs, nombres visibles traducidos por idioma
- Traducción de datos de autor: Bio y habilidades cambian según el idioma
- SEO: Hreflang en sitemap,
inLanguageen JSON-LD, feeds RSS multilingües - Fallback: Los artículos sin traducción muestran automáticamente la versión japonesa
- Soporte CMS: Los artículos de cada idioma son editables individualmente en Pages CMS
En adelante, los archivos de traducción se añadirán de forma incremental a medida que se publiquen nuevos artículos. Gracias a la función de fallback, la versión japonesa se muestra hasta que las traducciones estén completas, manteniendo la calidad del sitio.
Flujo de trabajo multilingüe
Base i18n
Configurar el enrutamiento i18n integrado de Astro y las utilidades de traducción.
Traducción de textos UI
Traducir los textos de encabezado, pie de página y todos los componentes.
Traducción de artículos
Generar 136 archivos de traducción (17 artículos × 8 idiomas).
CMS y verificación de build
Configurar Pages CMS multilingüe y verificar la generación de todas las páginas.
- Solo 1 idioma (japonés)
- 17 artículos de blog
- 523 páginas generadas (tras soporte multilingüe de UI)
- Pages CMS con 1 colección de blog
- Etiquetas y datos de autor solo en japonés
- 1 solo feed RSS
- Japonés + 8 idiomas (en, zh-cn, es, pt, fr, ko, de, ru)
- 17 artículos + 136 traducciones = 153 en total
- 541 páginas generadas (artículos traducidos con fallback)
- Pages CMS con 9 colecciones por idioma
- 25 etiquetas y datos de autor traducidos por idioma
- Feeds RSS multilingües (9 idiomas)
¿Por qué se eligieron 9 idiomas?
¿Cómo se garantiza la calidad de la traducción?
¿Qué ocurre cuando no existe un artículo traducido?
¿Es necesario traducir al añadir un nuevo artículo?
Gui
CEO de Acecore. Un ingeniero versátil que abarca desarrollo de sistemas, producción web, operaciones de infraestructura y educación en TI. Disfruta resolviendo desafíos organizacionales y humanos a través de la tecnología.
¿Quiere saber más sobre nuestros servicios?
Ofrecemos soporte integral en desarrollo de sistemas, diseño web, diseño gráfico y educación IT.