Guía de configuración i18n de Astro: sitio multilingüe en 30 minutos (con selector de idioma)

Tienes un blog Astro listo para salir al mercado internacional y te encuentras con que no sabes montar el soporte multilingüe. locales, defaultLocale, prefixDefaultLocale: un montón de opciones que no terminan de encajar. No sabes cómo organizar el contenido multilingüe, Content Collections suena abstracto y un selector de idioma parece imposible.
La configuración i18n de Astro no es tan complicada; el problema es que la documentación oficial es muy técnica. En este artículo recorremos todo el flujo de configuración i18n de Astro, desde lo básico hasta el selector de idioma, con código completo en cada paso. En 30 minutos tu sitio puede soportar varios idiomas.
Configuración básica de Astro i18n (10 minutos)
Configuración detallada de astro.config.mjs
Empecemos por lo más importante: el archivo de configuración. Desde Astro v4.0, el soporte i18n viene integrado y es bastante sencillo. Abre tu astro.config.mjs y añade estas líneas:
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
i18n: {
// Indica a Astro qué idiomas soporta tu sitio
locales: ['en', 'zh-cn', 'ja'],
// Idioma por defecto (debe ser uno de los locales)
defaultLocale: 'en',
// Si el idioma por defecto lleva prefijo en la ruta
prefixDefaultLocale: false,
}
});
Veamos cada opción:
locales — Todos los idiomas que soporta tu sitio. Usa códigos estándar como 'en' (inglés), 'zh-cn' (chino simplificado) o 'ja' (japonés). Si necesitas una distinción más fina, también puedes usar 'en-US', 'en-GB', etc.
defaultLocale — El idioma por defecto que verán los visitantes la primera vez. Debe ser uno de los valores del array locales; si no, Astro lanzará un error.
prefixDefaultLocale — Una opción con la que dudé bastante. Con false, las URL del idioma por defecto no llevan prefijo (por ejemplo, /about), y los demás sí (/zh-cn/about, /ja/about). Con true, todos los idiomas llevan prefijo (/en/about, /zh-cn/about).
En la mayoría de los casos, false basta: las URL del idioma principal quedan más limpias. Si te importa mucho la uniformidad de las URL o tienes requisitos SEO especiales, considera true.
Comparación de las tres estrategias de rutas
El enrutamiento i18n de Astro ofrece tres estrategias. Comparemos en una tabla cuál encaja mejor contigo:
| Estrategia | Configuración | Ejemplos de URL | Casos de uso | Ventajas y desventajas |
|---|---|---|---|---|
| Estrategia 1: idioma por defecto sin prefijo | prefixDefaultLocale: false | /about/zh-cn/about/ja/about | La mayoría de sitios (recomendado) | ✅ URL limpias para el idioma por defecto ❌ Formato de URL no uniforme |
| Estrategia 2: todos los idiomas con prefijo | prefixDefaultLocale: true | /en/about/zh-cn/about/ja/about | Necesidad de URL uniformes o requisitos SEO especiales | ✅ Formato de URL uniforme ✅ Lógica de cambio de idioma más simple ❌ URL del idioma por defecto algo más largas |
| Estrategia 3: modo manual | routing: 'manual' | Totalmente personalizable | Necesidades multilingües complejas Control total del enrutamiento | ✅ Máxima flexibilidad ❌ Configuración compleja; mucha lógica manual |
Mi recomendación: si el proyecto es relativamente simple (blog, documentación), usa la estrategia 1. Si quieres URL más ordenadas, la 2. La 3 es para proyectos con necesidades especiales, como elegir idioma según el comportamiento del usuario o usar subdominios distintos por idioma.
En este punto quizá pienses: solo quiero un blog bilingüe chino-inglés, ¿basta con copiar la configuración de arriba y adaptarla? Exacto, así de simple. Cambia locales a ['zh-cn', 'en'], pon defaultLocale en 'zh-cn' (si tu audiencia principal es china) y listo.
Configurar soporte multilingüe i18n en Astro
Pasos completos para configurar un sitio Astro multilingüe en 30 minutos
-
1
Step 1: Configurar astro.config.mjs
Añade la configuración i18n en astro.config.mjs: -
2
Step 2: Organizar contenido multilingüe
Elige un esquema de organización: -
3
Step 3: Crear diccionario de traducción UI
Crea el diccionario en src/i18n/ui.ts: -
4
Step 4: Implementar selector de idioma
Construye el componente con getRelativeLocaleUrl y Astro.currentLocale: -
5
Step 5: Optimización SEO
Añade etiquetas hreflang en el Layout, configura el sitemap y localiza la meta información
Organización del contenido multilingüe (dos enfoques)
Con la configuración lista, toca organizar el contenido. Aquí mucha gente se atasca: ¿cómo colocar los archivos? Astro te da dos caminos; elige el que te resulte más natural.
Enfoque 1: carpetas por idioma (recomendado para principiantes)
Es la forma más intuitiva: una carpeta por idioma, así:
src/pages/
├── about.astro # Idioma por defecto (supongamos chino)
├── blog.astro
├── index.astro
├── en/ # Versión en inglés
│ ├── about.astro
│ ├── blog.astro
│ └── index.astro
└── ja/ # Versión en japonés
├── about.astro
├── blog.astro
└── index.astro
Si usas prefixDefaultLocale: false (idioma por defecto sin prefijo), los archivos del idioma principal van directamente en la raíz de pages. Solo los demás idiomas necesitan subcarpetas.
Ventaja: estructura clara; cada idioma es independiente y no se pisan al editar. Inconveniente: con muchas páginas hay mucha duplicación. Por ejemplo, 20 páginas y 5 idiomas = 100 archivos que mantener.
Enfoque 2: rutas dinámicas (recomendado para usuarios avanzados)
Si el enfoque 1 te parece pesado, prueba rutas dinámicas. Un solo archivo puede atender todos los idiomas:
src/pages/
└── [lang]/
└── [...slug].astro
En [...slug].astro, renderiza el contenido según el parámetro lang:
---
// src/pages/[lang]/[...slug].astro
export function getStaticPaths() {
const locales = ['zh-cn', 'en', 'ja'];
const slugs = ['about', 'blog', 'contact'];
return locales.flatMap((lang) =>
slugs.map((slug) => ({
params: { lang, slug },
}))
);
}
const { lang, slug } = Astro.params;
// Carga el contenido según lang y slug
---
Mayor reutilización de código y menor coste de mantenimiento, pero exige entender el enrutamiento dinámico de Astro. Si eres nuevo, empieza con el enfoque 1 y pasa al 2 cuando te sientas cómodo.
Content Collections para contenido multilingüe (imprescindible en blogs)
Para un blog, Content Collections es lo central. Es la forma recomendada por Astro de gestionar contenido, ideal para blogs y documentación.
La estructura de carpetas sería así:
src/content/
└── blog/
├── en/
│ ├── post-1.md
│ └── post-2.md
├── zh-cn/
│ ├── post-1.md
│ └── post-2.md
└── ja/
├── post-1.md
└── post-2.md
Define el schema en src/content/config.ts:
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blogCollection = defineCollection({
schema: z.object({
title: z.string(),
author: z.string(),
date: z.date(),
lang: z.enum(['en', 'zh-cn', 'ja']), // Campo de idioma
}),
});
export const collections = {
blog: blogCollection,
};
Obtén artículos del idioma actual en la página:
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
const currentLang = Astro.currentLocale; // Obtiene el idioma actual
const posts = await getCollection('blog', ({ data }) => {
return data.lang === currentLang; // Solo artículos del idioma actual
});
---
Así se muestran automáticamente los artículos del idioma elegido. La documentación oficial de Astro usa este esquema para contenido multilingüe; es un enfoque fiable.
Gestión de archivos de traducción UI
Además del contenido, necesitas traducir textos fijos de la interfaz: navegación, botones, etiquetas de formulario, etc. Recomiendo un diccionario de traducción:
// src/i18n/ui.ts
export const ui = {
'en': {
'nav.home': 'Home',
'nav.about': 'About',
'nav.blog': 'Blog',
'btn.readMore': 'Read More',
},
'zh-cn': {
'nav.home': '首页',
'nav.about': '关于',
'nav.blog': '博客',
'btn.readMore': '阅读更多',
},
'ja': {
'nav.home': 'ホーム',
'nav.about': '概要',
'nav.blog': 'ブログ',
'btn.readMore': '続きを読む',
},
} as const;
Y dos funciones auxiliares:
// src/i18n/utils.ts
import { ui } from './ui';
// Obtiene el idioma actual desde la URL
export function getLangFromUrl(url: URL) {
const [, lang] = url.pathname.split('/');
if (lang in ui) return lang as keyof typeof ui;
return 'zh-cn'; // Idioma por defecto
}
// Devuelve la función de traducción
export function useTranslations(lang: keyof typeof ui) {
return function t(key: keyof typeof ui[typeof lang]) {
return ui[lang][key] || ui['zh-cn'][key];
}
}
Uso en un componente:
---
// Algún componente
import { getLangFromUrl, useTranslations } from '@/i18n/utils';
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
---
<nav>
<a href="/">{t('nav.home')}</a>
<a href="/about">{t('nav.about')}</a>
<a href="/blog">{t('nav.blog')}</a>
</nav>
Simple y práctico: todas las traducciones en un archivo, fácil de mantener. Si el volumen crece mucho, puedes dividirlo en varios JSON por módulo.
Implementar el selector de idioma (código completo)
Con configuración y contenido listos, llega la parte clave: el selector de idioma. Fue lo que más me costó al principio; tras revisar varios sitios, con las funciones helper de Astro resulta sencillo.
Entender las funciones helper i18n de Astro
Astro ofrece varias funciones muy útiles para URL multilingües:
getRelativeLocaleUrl(locale, path) — Obtiene la ruta relativa de un idioma.
import { getRelativeLocaleUrl } from 'astro:i18n';
// URL de la página About en inglés
const url = getRelativeLocaleUrl('en', 'about');
// Devuelve: '/en/about' o '/about' (según tu configuración)
getAbsoluteLocaleUrl(locale, path) — Obtiene la URL absoluta (con dominio).
import { getAbsoluteLocaleUrl } from 'astro:i18n';
const url = getAbsoluteLocaleUrl('en', 'about');
// Devuelve: 'https://example.com/en/about'
Astro.currentLocale — Obtiene el idioma de la página actual.
---
const currentLang = Astro.currentLocale;
// Devuelve: 'zh-cn', 'en', etc.
---
Astro.preferredLocale — Obtiene el idioma preferido del navegador (si tu sitio lo soporta).
---
const browserLang = Astro.preferredLocale;
// Devuelve: idioma del navegador (si está en tus locales)
---
Con estas herramientas ya puedes montar el selector.
Construir el componente de cambio de idioma
Aquí tienes un selector sencillo y listo para copiar:
---
// src/components/LanguageSwitcher.astro
import { getRelativeLocaleUrl } from 'astro:i18n';
// Todos los idiomas soportados (mejor leerlos de la config; aquí van fijos para el ejemplo)
const locales = {
'zh-cn': '简体中文',
'en': 'English',
'ja': '日本語',
};
// Idioma y ruta actuales
const currentLang = Astro.currentLocale || 'zh-cn';
const currentPath = Astro.url.pathname
.replace(`/${currentLang}/`, '/') // Quita el prefijo de idioma
.replace(/^\//, ''); // Quita la barra inicial
---
<div class="language-switcher">
<button class="lang-button">
{locales[currentLang]} ▼
</button>
<div class="lang-dropdown">
{Object.entries(locales).map(([lang, label]) => {
const url = getRelativeLocaleUrl(lang, currentPath);
return (
<a
href={url}
class={lang === currentLang ? 'active' : ''}
>
{label}
</a>
);
})}
</div>
</div>
<style>
.language-switcher {
position: relative;
display: inline-block;
}
.lang-button {
padding: 8px 16px;
background: #f3f4f6;
border: 1px solid #d1d5db;
border-radius: 6px;
cursor: pointer;
}
.lang-button:hover {
background: #e5e7eb;
}
.lang-dropdown {
display: none;
position: absolute;
top: 100%;
right: 0;
margin-top: 4px;
background: white;
border: 1px solid #d1d5db;
border-radius: 6px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
min-width: 150px;
}
.language-switcher:hover .lang-dropdown {
display: block;
}
.lang-dropdown a {
display: block;
padding: 10px 16px;
color: #374151;
text-decoration: none;
transition: background 0.2s;
}
.lang-dropdown a:hover {
background: #f3f4f6;
}
.lang-dropdown a.active {
background: #dbeafe;
color: #1e40af;
font-weight: 500;
}
</style>
<script>
// Alternar en móvil al pulsar
document.querySelector('.lang-button')?.addEventListener('click', (e) => {
e.stopPropagation();
const dropdown = document.querySelector('.lang-dropdown');
dropdown?.classList.toggle('show');
});
// Cerrar al pulsar fuera
document.addEventListener('click', () => {
document.querySelector('.lang-dropdown')?.classList.remove('show');
});
</script>
Úsalo en tu Layout o barra de navegación:
---
// src/layouts/Layout.astro
import LanguageSwitcher from '@/components/LanguageSwitcher.astro';
---
<header>
<nav>
<!-- Otros enlaces de navegación -->
<LanguageSwitcher />
</nav>
</header>
La lógica principal:
- Obtener idioma y ruta actuales
- Generar la URL de cada idioma soportado
- Resaltar el idioma activo
- Cambiar al pulsar el enlace correspondiente
Fíjate en getRelativeLocaleUrl(lang, currentPath): al cambiar de idioma el usuario permanece en la misma página en otra versión lingüística, no vuelve al inicio.
Detección del idioma del navegador (opcional pero recomendable)
A veces quieres que la primera visita redirija al idioma del navegador. Puedes hacerlo con middleware:
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware((context, next) => {
const url = context.url;
const currentLocale = context.currentLocale;
const preferredLocale = context.preferredLocale;
// Si entra en la raíz y el idioma del navegador no coincide, redirige
if (url.pathname === '/' && preferredLocale && preferredLocale !== currentLocale) {
return context.redirect(`/${preferredLocale}/`);
}
return next();
});
Así la primera visita va al idioma familiar del usuario. No fuerces la redirección en cada visita: si el usuario cambió de idioma manualmente y lo reviertes, la experiencia empeora.
Mejor combinar con una cookie que recuerde la elección:
// Middleware mejorado
export const onRequest = defineMiddleware((context, next) => {
const url = context.url;
const currentLocale = context.currentLocale;
const preferredLocale = context.preferredLocale;
const savedLang = context.cookies.get('user-lang')?.value;
// Prioridad a la preferencia guardada
if (savedLang && savedLang !== currentLocale && url.pathname === '/') {
return context.redirect(`/${savedLang}/`);
}
// Si no hay cookie, usa el idioma del navegador
if (!savedLang && url.pathname === '/' && preferredLocale && preferredLocale !== currentLocale) {
context.cookies.set('user-lang', preferredLocale, {
path: '/',
maxAge: 31536000, // 1 año
});
return context.redirect(`/${preferredLocale}/`);
}
return next();
});
Y al cambiar idioma en el selector, actualiza la cookie:
<script>
document.querySelectorAll('.lang-dropdown a').forEach((link) => {
link.addEventListener('click', (e) => {
const lang = e.target.getAttribute('data-lang');
document.cookie = `user-lang=${lang}; path=/; max-age=31536000`;
});
});
</script>
Así se recuerda la preferencia y en la próxima visita se muestra el idioma elegido.
Técnicas avanzadas (fallback, dominios, SEO)
Con lo básico cubierto, unas técnicas más avanzadas. Si el proyecto es simple, puedes saltar esta sección y volver cuando la necesites.
Configuración de estrategia fallback
Imagina que traduces el sitio poco a poco y aún no existe la versión japonesa de algunas páginas. Si un usuario entra en una URL japonesa inexistente, quizá quieras mostrar la versión en inglés. Eso es fallback:
// astro.config.mjs
export default defineConfig({
i18n: {
locales: ['en', 'zh-cn', 'ja'],
defaultLocale: 'en',
fallback: {
ja: 'en', // Si no hay japonés, muestra inglés
'zh-cn': 'en', // Si no hay chino, muestra inglés
},
}
});
Si /ja/some-page no existe, Astro mostrará el contenido de /en/some-page en lugar de un 404. Muy útil mientras el contenido sigue en traducción.
Mapeo de dominios personalizado
Algunos productos internacionales usan dominios distintos por idioma:
- Inglés:
example.com - Chino:
example.cn - Japonés:
example.jp
Astro lo soporta, pero el mapeo de dominios solo funciona en modo SSR:
// astro.config.mjs
export default defineConfig({
output: 'server', // Hay que activar SSR
adapter: node(), // Y configurar un adaptador
i18n: {
locales: ['en', 'zh-cn', 'ja'],
defaultLocale: 'en',
domains: {
'zh-cn': 'https://example.cn',
ja: 'https://example.jp',
},
}
});
El contenido en chino se despliega en example.cn y el japonés en example.jp. En sitios estáticos (modo por defecto) esta opción no está disponible.
Puntos clave de SEO
En sitios multilingües, el SEO consiste sobre todo en decir a los buscadores qué versiones lingüísticas existen. Lo más importante son las etiquetas hreflang.
Astro facilita el SEO i18n: con buena configuración, mucho es automático. Aun así, conviene hacer manualmente lo siguiente:
1. Añadir etiquetas hreflang en el Layout
---
// src/layouts/Layout.astro
import { getAbsoluteLocaleUrl } from 'astro:i18n';
const locales = ['en', 'zh-cn', 'ja'];
const currentPath = Astro.url.pathname
.replace(/^\/(en|zh-cn|ja)\//, '')
.replace(/^\//, '');
---
<html>
<head>
<!-- Etiqueta hreflang por idioma -->
{locales.map((lang) => (
<link
rel="alternate"
hreflang={lang}
href={getAbsoluteLocaleUrl(lang, currentPath)}
/>
))}
<!-- Etiqueta de idioma por defecto -->
<link
rel="alternate"
hreflang="x-default"
href={getAbsoluteLocaleUrl('en', currentPath)}
/>
<!-- Meta description localizada -->
<meta name="description" content={description[currentLang]} />
<!-- URL canónica -->
<link rel="canonical" href={getAbsoluteLocaleUrl(currentLang, currentPath)} />
</head>
</html>
2. sitemap.xml multilingüe
Si usas @astrojs/sitemap, genera un sitemap por idioma automáticamente. Asegúrate de configurar site:
// astro.config.mjs
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://example.com', // Obligatorio
integrations: [sitemap()],
});
3. Localizar meta información
Traduce también title, description, keywords y demás meta por idioma:
---
const meta = {
'en': {
title: 'Welcome to My Blog',
description: 'A blog about web development',
},
'zh-cn': {
title: '欢迎来到我的博客',
description: '一个关于 Web 开发的博客',
},
};
const currentLang = Astro.currentLocale || 'en';
---
<head>
<title>{meta[currentLang].title}</title>
<meta name="description" content={meta[currentLang].description} />
</head>
Con estos pasos, los buscadores indexarán correctamente tu sitio multilingüe.
Conclusión
Repasemos el flujo completo de configuración i18n en Astro:
Paso 1: configurar astro.config.mjs (5 minutos)
- Define el array
locales(idiomas soportados) - Define
defaultLocale(idioma por defecto) - Elige la estrategia
prefixDefaultLocalesegún tus necesidades
Paso 2: organizar el contenido multilingüe (elige el enfoque que te encaje)
- Principiantes: carpetas por idioma
- Avanzados: rutas dinámicas + Content Collections
- No olvides el diccionario de traducción UI
Paso 3: implementar el selector de idioma (copia y pega el código)
- Usa
getRelativeLocaleUrlpara generar URL - Usa
Astro.currentLocalepara el idioma actual - Opcional: detección del navegador y cookie de preferencia
Tras usar Astro i18n un tiempo, la verdad es que resulta muy cómodo: configuración simple, helpers útiles y buen rendimiento (todas las rutas por idioma se pregeneran en el build). Si tu sitio necesita varios idiomas, la solución integrada de Astro merece la pena.
¡Pon manos a la obra y añade soporte multilingüe a tu sitio Astro! Si te atascas, consulta la documentación oficial i18n de Astro; ahí están los detalles de la API.
Si tienes experiencia práctica o algún tropiezo que quieras compartir, déjalo en los comentarios; aprendemos entre todos.
FAQ
¿Cuánto tiempo lleva configurar i18n en Astro?
• Incluye definir locales, defaultLocale y prefixDefaultLocale en astro.config.mjs
Un sitio multilingüe completo (con selector de idioma y optimización SEO) lleva unos 30 minutos.
¿Debo poner prefixDefaultLocale en true o false?
• Así las URL del idioma por defecto son más limpias (por ejemplo, /about)
Si necesitas un formato de URL uniforme o requisitos SEO especiales, puedes usar true (por ejemplo, /en/about).
¿Cómo organizar el contenido multilingüe?
Enfoque 1: carpetas por idioma (recomendado para principiantes):
• Estructura clara, pero más archivos
Enfoque 2: rutas dinámicas (recomendado para usuarios avanzados):
• Mayor reutilización de código y menor coste de mantenimiento
Para blogs, se recomienda gestionar artículos multilingües con Content Collections.
¿Cómo implementar un selector de idioma?
Puedes combinarlo con detección del idioma del navegador y cookies para recordar la preferencia del usuario.
¿Cómo optimizar el SEO de un sitio multilingüe?
• Añadir etiquetas hreflang en el Layout para indicar a los buscadores las versiones en cada idioma
• Configurar el plugin sitemap para generar automáticamente un sitemap multilingüe
• Localizar title, description y demás meta información
Astro ofrece buen soporte para SEO i18n; gran parte del trabajo es automático.
13 min de lectura · Publicado el: 2 dic 2025 · Actualizado el: 21 ago 2026
Guía de Astro
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
5 mejores temas de blog Astro con tutorial de instalación y configuración
¿Quieres montar un blog rápido pero no sabes qué tema elegir? Este artículo recomienda 5 temas Astro probados (AstroPaper, Astro Air Blog, etc.), con tutorial de instalación detallado y soluciones a problemas habituales: blog personal en 30 minutos.
Parte 5 de 18
Siguiente
Guía completa de SEO para sitios Astro: de meta tags al posicionamiento en buscadores
Guía paso a paso para configurar SEO completo en tu sitio Astro: meta tags, sitemap, robots.txt y datos estructurados JSON-LD. Configura lo esencial en 30 minutos, mejora el ranking en Google, con ejemplos de código listos para usar.
Parte 7 de 18



Comentarios
Inicia sesión con GitHub para dejar un comentario