Saltar al contenido

Contenido traducido: colecciones por idioma y fallback

Organizar el contenido real de un sitio multilingüe más allá del enrutado. Dos estrategias para las colecciones por idioma, carpeta por locale o campo lang en el frontmatter, y cómo derivar el idioma del id con el glob loader de la Content Layer. La configuración fallback para que un idioma incompleto se apoye en otro, la diferencia entre fallbackType redirect y rewrite, y cómo detectar el idioma vigente al renderizar.

Dos estrategias para las colecciones por idioma

Hay dos maneras sensatas de guardar contenido en varios idiomas, y elegir bien ahorra fricción para siempre. La primera es la carpeta por idioma: dentro de la colección, una subcarpeta por cada locale, y dentro de cada una los mismos archivos con el mismo nombre. El idioma queda implícito en la ruta del fichero.

Texto
src/content/blog/  es/primer-post.md  es/segundo-post.md  en/primer-post.md  fr/primer-post.md

La segunda es el campo en el frontmatter: todos los archivos conviven en la misma carpeta y cada uno declara su idioma en un campo lang de su cabecera. La correspondencia entre versiones se establece con un slug o un translationId compartido. Es más plano y más explícito, a cambio de mezclar idiomas en un mismo directorio.

Con el glob loader de la Content Layer, la carpeta por idioma es especialmente cómoda, porque el idioma cae solo dentro del id de cada entrada. Al cargar con base en la raíz de la colección, un fichero en es/primer-post.md recibe el id es/primer-post, y de ahí extraer el idioma es partir una cadena.

TypeScript
// src/content.config.tsimport { defineCollection, z } from 'astro:content';import { glob } from 'astro/loaders';const blog = defineCollection({  loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),  schema: z.object({    title: z.string(),    translationId: z.string().optional(),  }),});export const collections = { blog };

Con esta colección, la página dinámica que genera las rutas puede leer el idioma directamente del id, sin necesidad siquiera de un campo lang, porque la propia estructura de carpetas lo codifica. El idioma deja de ser un dato que hay que recordar escribir y pasa a ser una consecuencia de dónde vive el archivo.

Generar las rutas y detectar el idioma vigente

Con el contenido organizado, la página que lo sirve extrae de cada id dos cosas: el idioma y el resto de la ruta. Una ruta dinámica como src/pages/[lang]/blog/[...slug].astro recorre la colección en getStaticPaths y reparte cada entrada en su URL localizada.

ASTRO
---import { getCollection } from 'astro:content';export async function getStaticPaths() {  const posts = await getCollection('blog');  return posts.map((post) => {    const [lang, ...resto] = post.id.split('/');    return {      params: { lang, slug: resto.join('/') },      props: { post },    };  });}const { post } = Astro.props;const idioma = Astro.currentLocale;---<h1>{post.data.title}</h1>

Detectar el idioma vigente al renderizar tiene dos vías, y conviene saber cuándo usar cada una. Una es el param: Astro.params.lang te da el segmento tal cual apareció en la URL. La otra es Astro.currentLocale, que Astro calcula a partir de la URL confrontándola con tu lista de locales, y que es la vía recomendada porque respeta los locales con path y codes personalizados donde el segmento y el código no coinciden. Para filtrar el contenido de un idioma —mostrar solo los posts en francés en la portada francesa— comparas el idioma extraído del id con el currentLocale de la página.

fallback: apoyarse en otro idioma cuando falta la traducción

Traducir un sitio entero de golpe es un lujo raro; lo normal es que el inglés tenga cincuenta páginas y el francés solo veinte. La opción fallback de la configuración resuelve el hueco: declara, por cada idioma, en qué otro apoyarse cuando una página no existe en él. Así el visitante francés que pide una página aún no traducida no topa con un 404, sino con la versión de reserva.

JS
// astro.config.mjsi18n: {  locales: ['es', 'en', 'fr'],  defaultLocale: 'es',  fallback: {    fr: 'en',    en: 'es',  },  routing: {    prefixDefaultLocale: false,    fallbackType: 'redirect',  },},

Aquí el francés cae al inglés y el inglés al español. Si /fr/contacto/ no existe pero /en/contacto/ sí, el visitante que pide la francesa recibe la inglesa. La cadena se resuelve por pasos: un idioma cae a su fallback, y ese fallback podría caer al suyo, hasta encontrar una página real o agotarse.

El matiz decisivo es fallbackType, que gobierna cómo se sirve esa reserva. Con redirect —el valor por defecto—, Astro emite una redirección: la URL en la barra cambia de /fr/contacto/ a /en/contacto/, y el usuario ve que está leyendo la versión inglesa. Con rewrite, en cambio, se sirve el contenido del fallback bajo la URL original: la barra sigue diciendo /fr/contacto/ aunque el HTML sea el inglés, sin salto visible.