Saltearse al contenido

Agrega características i18n

En esta receta, aprenderás a utilizar colecciones de contenido y enrutamiento dinámico para crear tu propia solución de internacionalización (i18n) y servir contenido en diferentes idiomas.

Este ejemplo sirve para cada idioma en su propia subruta, p. ej. example.com/en/blog para inglés y example.com/fr/blog para francés.

Si prefieres que el idioma predeterminado no sea visible en la URL, a diferencia de otros idiomas, hay instrucciones para ocultar el idioma predeterminado a continuación.

  1. Crea un directorio para cada idioma que desees soportar. Por ejemplo, en/ y fr/ si estás soportando inglés y francés:

    • Directoriosrc/
      • Directoriopages/
        • Directorioen/
          • about.astro
          • index.astro
        • Directoriofr/
          • about.astro
          • index.astro
        • index.astro
  2. Configura src/pages/index.astro para redirigir al idioma predeterminado.

    src/pages/index.astro
    <meta http-equiv="refresh" content="0;url=/en/" />

    Este enfoque utiliza un método meta refresh y funcionará sin importar cómo se despliegue 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 obtener más detalles.

Utiliza colecciones para el contenido traducido.

Sección titulada Utiliza colecciones para el contenido traducido.
  1. Crea una carpeta en src/content/ para cada tipo de contenido que desees incluir y agrega subdirectorios para cada idioma compatible. Por ejemplo, para soportar publicaciones de blog en inglés y francés:

    • Directoriosrc/
      • Directoriocontent/
        • Directorioblog/
          • Directorioen/ Artículos de blog en inglés
            • post-1.md
            • post-2.md
          • Directoriofr/ Artículos de blog en francés
            • post-1.md
            • post-2.md
  2. Crea un archivo src/content/config.ts y exporta una colección para cada tipo de contenido.

    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()
    })
    });
    export const collections = {
    'blog': blogCollection
    };
    Lee más sobre Colecciones de Contenido.
  3. Utiliza rutas dinámicas para obtener y mostrar contenido basado en los parámetros lang y slug.

    En el modo de renderizado estático, utiliza getStaticPaths para asignar cada entrada de contenido a una página:

    src/pages/[lang]/blog/[...slug].astro
    ---
    import { getCollection } from 'astro:content';
    export async function getStaticPaths() {
    const pages = await getCollection('blog');
    const paths = pages.map(page => {
    const [lang, ...slug] = page.slug.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 page.render();
    ---
    <h1>{page.data.title}</h1>
    <p>by {page.data.author}{formattedDate}</p>
    <Content/>
    Lee más sobre enrutamiento dinámico.

Crea diccionarios de términos para traducir las etiquetas de los elementos de la UI en tu sitio. Esto permitirá que tus visitantes experimenten tu sitio completamente en su idioma.

  1. Crea un archivo src/i18n/ui.ts para almacenar tus strings 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;
  2. Crea dos funciones auxiliares: una para detectar el idioma de la página según la URL actual y otra para obtener las cadenas de traducción de 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];
    }
    }
  3. Importa los helpers donde sea necesario y úsalos para elegir el string de UI que corresponda al idioma actual. Por ejemplo, un componente de navegación podría lucir 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>
  4. Cada página debe tener un atributo lang en el elemento <html> que coincida con el idioma de la página. En este ejemplo, un diseño 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>

    Puedes utilizar este diseño base para asegurarte de que las páginas utilicen automáticamente el atributo lang correcto.

    src/pages/en/about.astro
    ---
    import Base from '../../layouts/Base.astro';
    ---
    <Base>
    <h1>About me</h1>
    ...
    </Base>

Permite a los usuarios cambiar entre idiomas.

Sección titulada Permite a los usuarios cambiar entre idiomas.

Crea enlaces a los diferentes idiomas que admites para que los usuarios puedan elegir el idioma en el que desean leer tu sitio.

  1. Crea un componente para mostrar un enlace por 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>
  2. Agrega <LanguagePicker /> a tu sitio para que se muestre en todas las páginas. El ejemplo a continuación lo agrega al pie de página del diseño 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 predeterminado en la URL

Sección titulada Ocultar el idioma predeterminado en la URL
  1. Crea un directorio para cada idioma, excepto el idioma predeterminado. Por ejemplo, guarda las páginas en tu idioma predeterminado directamente en pages/, y las páginas traducidas en fr/:

    • Directoriosrc/
      • Directoriopages/
        • about.astro
        • index.astro
        • Directoriofr/
          • about.astro
          • index.astro
  2. Añade otra línea al archivo src/i18n/ui.ts para activar o desactivar la función:

    src/i18n/ui.ts
    export const showDefaultLang = false;
  3. Agrega una función auxiliar al archivo src/i18n/utils.ts para traducir rutas según 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}`
    }
    }
  4. Importa la función auxiliar donde sea necesario. Por ejemplo, un componente nav podrí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>
  5. La función auxiliar también puede utilizarse 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';
    ---
    <ul>
    {Object.entries(languages).map(([lang, label]) => (
    <li>
    <a href={translatePath('/', lang)}>{label}</a>
    </li>
    ))}
    </ul>

Traduce las rutas de tus páginas para cada idioma.

  1. Añade mappings de rutas al archivo src/i18n/ui.ts:

    src/i18n/ui.ts
    export const routes = {
    de: {
    'services': 'leistungen',
    },
    fr: {
    'services': 'prestations-de-service',
    },
    }
  2. Actualiza la función auxiliar useTranslatedPath en el archivo src/i18n/utils.ts para agregar la lógica de traducción de enrutamiento.

    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] !== undefined
    const translatedPath = hasTranslation ? '/' + routes[l][pathName] : path
    return !showDefaultLang && l === defaultLang ? translatedPath : `/${l}${translatedPath}`
    }
    }
  3. Crea una función auxiliar para obtener la ruta, si existe, basada en la URL actual en el archivo 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;
    }
  4. La función auxiliar se puede utilizar para obtener una ruta traducida. Por ejemplo, cuando no se define una ruta traducida, el usuario será redirigido a la página de inicio:

    src/components/LanguagePicker.astro
    ---
    import { languages } from '../i18n/ui';
    import { getRouteFromUrl } from '../i18n/utils';
    const route = getRouteFromUrl(Astro.url);
    ---
    <ul>
    {Object.entries(languages).map(([lang, label]) => (
    <li>
    <a href={translatePath(`/${route ? route : ''}`, lang)}>{label}</a>
    </li>
    ))}
    </ul>
  • astro-i18next — An Astro integration for i18next including some utility components.
  • astro-i18n — A TypeScript-first internationalization library for Astro.
  • astro-i18n-aut — An Astro integration for i18n that supports the defaultLocale without page generation. The integration is adapter agnostic and UI framework agnostic.
  • paraglide - Una biblioteca con seguridad de tipos completa para i18n, diseñada específicamente para patrones de hidratación parcial como las islas de Astro.