Añadir características de i18n
En esta receta, aprenderás cómo usar content collections y enrutamiento dinámico para construir tu propia solución de internacionalización (i18n) y servir tu contenido en diferentes idiomas.
En v4.0, Astro añadió soporte integrado para enrutamiento i18n que te permite configurar idiomas por defecto y soportados e incluye valiosas funciones helper para asistirte en servir a una audiencia internacional. Si quieres usar esto en su lugar, consulta nuestra guía de internacionalización para aprender sobre estas funcionalidades.
Este ejemplo sirve cada idioma en su propio subpath, ej. example.com/en/blog para inglés y example.com/fr/blog para francés.
Si prefieres que el idioma por defecto no sea visible en la URL a diferencia de otros idiomas, hay instrucciones para ocultar el idioma por defecto más abajo.
Configurar páginas para cada idioma
Sección titulada “Configurar páginas para cada idioma”-
Crea un directorio para cada idioma que quieras soportar. Por ejemplo,
en/yfr/si estás soportando inglés y francés:Directoriosrc/
Directoriopages/
Directorioen/
- about.astro
- index.astro
Directoriofr/
- about.astro
- index.astro
- index.astro
-
Configura
src/pages/index.astropara redirigir a tu idioma por defecto.src/pages/index.astro <meta http-equiv="refresh" content="0;url=/en/" />Este enfoque usa un meta refresh y funcionará sin importar cómo despliegues tu sitio. Algunos hosts estáticos también te permiten configurar redirecciones de servidor con un archivo de configuración personalizado. Consulta la documentación de tu plataforma de despliegue para más detalles.
Si estás usando un SSR adapter, puedes usar
Astro.redirectpara redirigir al idioma por defecto en el servidor.src/pages/index.astro ---return Astro.redirect('/en/');---
Usar colecciones para contenido traducido
Sección titulada “Usar colecciones para contenido traducido”-
Crea una carpeta en
src/content/para cada tipo de contenido que quieras incluir y añade subdirectorios para cada idioma soportado. Por ejemplo, para soportar blog posts en inglés y francés:Directoriosrc/
Directoriocontent/
Directorioblog/
Directorioen/ Blog posts en inglés
- post-1.md
- post-2.md
Directoriofr/ Blog posts en francés
- post-1.md
- post-2.md
-
Crea un archivo
src/content.config.tsy exporta una colección para cada tipo de contenido.src/content.config.ts import { defineCollection } from 'astro:content';import { z } from 'astro/zod';const blogCollection = defineCollection({schema: z.object({title: z.string(),author: z.string(),date: z.date()})});export const collections = {'blog': blogCollection};Lee más sobre Content Collections. -
Usa rutas dinámicas para obtener y renderizar contenido basado en un parámetro
langy unslug.En modo de renderizado estático, usa
getStaticPathspara mapear cada entrada de contenido a una página:src/pages/[lang]/blog/[...slug].astro ---import { getCollection, render } from 'astro:content';export async function getStaticPaths() {const pages = await getCollection('blog');const paths = pages.map(page => {const [lang, ...slug] = page.id.split('/');return { params: { lang, slug: slug.join('/') || undefined }, props: page };});return paths;}const { lang, slug } = Astro.params;const page = Astro.props;const formattedDate = page.data.date.toLocaleString(lang);const { Content } = await render(page);---<h1>{page.data.title}</h1><p>by {page.data.author} • {formattedDate}</p><Content/>En modo SSR, obtén la entrada solicitada directamente:
src/pages/[lang]/blog/[...slug].astro ---import { getEntry, render } from 'astro:content';const { lang, slug } = Astro.params;const page = await getEntry('blog', `${lang}/${slug}`);if (!page) {return Astro.redirect('/404');}const formattedDate = page.data.date.toLocaleString(lang);const { Content, headings } = await render(page);---<h1>{page.data.title}</h1><p>by {page.data.author} • {formattedDate}</p><Content/>Lee más sobre enrutamiento dinámico.El ejemplo anterior usa el método de formateo de fechas
toLocaleString()integrado para crear un string legible para humanos a partir de la fecha del frontmatter. Esto asegura que la fecha y la hora estén formateadas para coincidir con el idioma del usuario.
Traducir cadenas de UI
Sección titulada “Traducir cadenas de UI”Crea diccionarios de términos para traducir las etiquetas de los elementos de UI alrededor de tu sitio. Esto permite que tus visitantes experimenten tu sitio completamente en su idioma.
-
Crea un archivo
src/i18n/ui.tspara almacenar tus cadenas de traducción:src/i18n/ui.ts export const languages = {en: 'English',fr: 'Français',};export const defaultLang = 'en';export const ui = {en: {'nav.home': 'Home','nav.about': 'About','nav.twitter': 'Twitter',},fr: {'nav.home': 'Accueil','nav.about': 'À propos',},} as const; -
Crea dos funciones helper: una para detectar el idioma de la página basándose en la URL actual, y otra para obtener cadenas de traducción para diferentes partes de la UI en
src/i18n/utils.ts:src/i18n/utils.ts import { ui, defaultLang } 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];}}En el paso 1, la cadena
nav.twitterno fue traducida al francés. Puede que no quieras que cada término sea traducido, como nombres propios o términos comunes de la industria. El helperuseTranslationsdevolverá el valor del idioma por defecto si una clave no está traducida. En este ejemplo, los usuarios franceses también verán “Twitter” en la barra de navegación. -
Importa los helpers donde sean necesarios y úsalos para elegir la cadena de UI que corresponda al idioma actual. Por ejemplo, un componente de nav podría verse así:
src/components/Nav.astro ---import { getLangFromUrl, useTranslations } from '../i18n/utils';const lang = getLangFromUrl(Astro.url);const t = useTranslations(lang);---<ul><li><a href={`/${lang}/home/`}>{t('nav.home')}</a></li><li><a href={`/${lang}/about/`}>{t('nav.about')}</a></li><li><a href="https://twitter.com/astrodotbuild">{t('nav.twitter')}</a></li></ul> -
Cada página debe tener un atributo
langen el elemento<html>que coincida con el idioma de la página. En este ejemplo, un layout reutilizable extrae el idioma de la ruta actual:src/layouts/Base.astro ---import { getLangFromUrl } from '../i18n/utils';const lang = getLangFromUrl(Astro.url);---<html lang={lang}><head><meta charset="utf-8" /><link rel="icon" type="image/svg+xml" href="/favicon.svg" /><meta name="viewport" content="width=device-width" /><title>Astro</title></head><body><slot /></body></html>Luego puedes usar este layout base para asegurar que las páginas usen el atributo
langcorrecto automáticamente.src/pages/en/about.astro ---import Base from '../../layouts/Base.astro';---<Base><h1>About me</h1>...</Base>
Permitir a los usuarios cambiar entre idiomas
Sección titulada “Permitir a los usuarios cambiar entre idiomas”Crea enlaces a los diferentes idiomas que soportas para que los usuarios puedan elegir el idioma en el que quieren leer tu sitio.
-
Crea un componente para mostrar un enlace para cada idioma:
src/components/LanguagePicker.astro ---import { languages } from '../i18n/ui';---<ul>{Object.entries(languages).map(([lang, label]) => (<li><a href={`/${lang}/`}>{label}</a></li>))}</ul> -
Añade
<LanguagePicker />a tu sitio para que se muestre en cada página. El ejemplo siguiente lo añade al footer del sitio en un layout base:src/layouts/Base.astro ---import LanguagePicker from '../components/LanguagePicker.astro';import { getLangFromUrl } from '../i18n/utils';const lang = getLangFromUrl(Astro.url);---<html lang={lang}><head><meta charset="utf-8" /><link rel="icon" type="image/svg+xml" href="/favicon.svg" /><meta name="viewport" content="width=device-width" /><title>Astro</title></head><body><slot /><footer><LanguagePicker /></footer></body></html>
Ocultar el idioma por defecto en la URL
Sección titulada “Ocultar el idioma por defecto en la URL”-
Crea un directorio para cada idioma excepto el idioma por defecto. Por ejemplo, almacena las páginas de tu idioma por defecto directamente en
pages/, y tus páginas traducidas enfr/:Directoriosrc/
Directoriopages/
- about.astro
- index.astro
Directoriofr/
- about.astro
- index.astro
-
Añade otra línea al archivo
src/i18n/ui.tspara activar la funcionalidad:src/i18n/ui.ts export const showDefaultLang = false; -
Añade una función helper a
src/i18n/utils.ts, para traducir rutas basándose en el idioma actual:src/i18n/utils.ts import { ui, defaultLang, showDefaultLang } from './ui';export function useTranslatedPath(lang: keyof typeof ui) {return function translatePath(path: string, l: string = lang) {return !showDefaultLang && l === defaultLang ? path : `/${l}${path}`}} -
Importa el helper donde sea necesario. Por ejemplo, un componente
navpodría verse así:src/components/Nav.astro ---import { getLangFromUrl, useTranslations, useTranslatedPath } from '../i18n/utils';const lang = getLangFromUrl(Astro.url);const t = useTranslations(lang);const translatePath = useTranslatedPath(lang);---<ul><li><a href={translatePath('/home/')}>{t('nav.home')}</a></li><li><a href={translatePath('/about/')}>{t('nav.about')}</a></li><li><a href="https://twitter.com/astrodotbuild">{t('nav.twitter')}</a></li></ul> -
La función helper también puede usarse para traducir rutas para un idioma específico. Por ejemplo, cuando los usuarios cambian entre idiomas:
src/components/LanguagePicker.astro ---import { languages } from '../i18n/ui';import { getLangFromUrl, useTranslatedPath } from '../i18n/utils';const lang = getLangFromUrl(Astro.url);const translatePath = useTranslatedPath(lang);---<ul>{Object.entries(languages).map(([lang, label]) => (<li><a href={translatePath('/', lang)}>{label}</a></li>))}</ul>
Traducir rutas
Sección titulada “Traducir rutas”Traduce las rutas de tus páginas para cada idioma.
-
Añade mapeos de rutas a
src/i18n/ui.ts:src/i18n/ui.ts export const routes = {de: {'services': 'leistungen',},fr: {'services': 'prestations-de-service',},} -
Actualiza la función helper
useTranslatedPathensrc/i18n/utils.tspara añadir lógica de traducción de rutas.src/i18n/utils.ts import { ui, defaultLang, showDefaultLang, routes } from './ui';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] !== undefined && routes[l][pathName] !== undefinedconst translatedPath = hasTranslation ? '/' + routes[l][pathName] : pathreturn !showDefaultLang && l === defaultLang ? translatedPath : `/${l}${translatedPath}`}} -
Crea una función helper para obtener la ruta, si existe basándose en la URL actual, en
src/i18n/utils.ts:src/i18n/utils.ts import { ui, defaultLang, showDefaultLang, routes } from './ui';export function getRouteFromUrl(url: URL): string | undefined {const pathname = new URL(url).pathname;const parts = pathname?.split('/');const path = parts.pop() || parts.pop();if (path === undefined) {return undefined;}const currentLang = getLangFromUrl(url);if (defaultLang === currentLang) {const route = Object.values(routes)[0];return route[path] !== undefined ? route[path] : 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;} -
La función helper puede usarse para obtener una ruta traducida. Por ejemplo, cuando no se define una ruta traducida, el usuario será redirigido a la página principal:
src/components/LanguagePicker.astro ---import { languages } from '../i18n/ui';import { getRouteFromUrl, useTranslatedPath } from '../i18n/utils';const route = getRouteFromUrl(Astro.url);---<ul>{Object.entries(languages).map(([lang, label]) => {const translatePath = useTranslatedPath(lang);return (<li><a href={translatePath(`/${route ? route : ''}`)}>{label}</a></li>)})}</ul>
Recursos
Sección titulada “Recursos”Librerías de la comunidad
Sección titulada “Librerías de la comunidad”Encuentra utilidades de i18n construidas por la comunidad que puedes añadir a tu proyecto de Astro en nuestro directorio de integraciones.
Recetas