Перейти к содержимому

Реализация функций i18n

В этом руководстве вы узнаете, как использовать коллекции контента и динамическую маршрутизацию для создания собственного решения интернационализации (i18n) и отображения контента на разных языках.

:::tipВ версии 4.0 Astro добавил встроенную поддержку i18n-маршрутизации, позволяющую указать основной язык и список поддерживаемых языков, а также предоставляющую полезные вспомогательные функции для работы с международной аудиторией. Если вы предпочитаете этот подход, ознакомьтесь с нашим руководством по интернационализации.:::

В этом примере для каждого языка используется свой путь, например, example.com/en/blog для английского и example.com/ru/blog для русского.

Если вы хотите, чтобы язык по умолчанию не отображался в URL (в отличие от других языков), ниже вы найдете инструкции по скрытию языка по умолчанию в URL.

Ознакомьтесь с разделом ресурсов, где вы найдете внешние ссылки на связанные темы, такие как стили для письма справа налево (RTL) и выбор языковых тегов.

Рецепт

Настройка страниц для каждого языка

  1. Создайте директорию для каждого языка, который вы хотите поддерживать. Например, en/ и ru/ для поддержки английского и русского:

    • Директорияsrc/
      • Директорияpages/
        • Директорияen/
          • about.astro
          • index.astro
        • Директорияru/
          • about.astro
          • index.astro
        • index.astro
  2. Настройте src/pages/index.astro для перенаправления на язык по умолчанию.

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

    Этот подход использует meta refresh и будет работать независимо от способа развертывания вашего сайта. На некоторых статических хостингах можно также настроить серверную переадресацию через специальный конфигурационный файл. Подробнее об этом читайте в документации вашей платформы развертывания.

Организация переводов с помощью коллекций

  1. Создайте в src/content/ отдельные каталоги для каждого типа контента и добавьте в них подкаталоги для всех поддерживаемых языков. Например, для блог-постов на английском и русском:

    • Директорияsrc/
      • Директорияcontent/
        • Директорияblog/
          • Директорияen/ Посты на английском
            • post-1.md
            • post-2.md
          • Директорияru/ Посты на русском
            • post-1.md
            • post-2.md
  2. Создайте файл src/content.config.ts и экспортируйте коллекцию для каждого типа контента.

    //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
    };

  3. Используйте динамические маршруты (EN) для получения и отображения контента в зависимости от параметров lang и slug.

    В режиме статической генерации используйте функцию getStaticPaths, чтобы создать отдельную страницу для каждой записи контента:

      //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/>

    :::tip[Форматирование даты]В примере выше используется встроенный метод toLocaleString() для создания удобочитаемой строки из даты в метаданных.Это гарантирует, что дата и время будут отформатированы в соответствии с языком пользователя.:::

Перевод элементов интерфейса

Создайте словари с переводами для всех текстовых элементов интерфейса вашего сайта. Это позволит посетителям использовать сайт полностью на их родном языке.

  1. Создайте файл src/i18n/ui.ts для хранения переводов интерфейса:

    // src/i18n/ui.ts
    export const languages = {
      en: 'English',
      ru: 'Русский',
    };
    
    export const defaultLang = 'en';
    
    export const ui = {
      en: {
        'nav.home': 'Home',
        'nav.about': 'About',
        'nav.twitter': 'Twitter',
      },
      ru: {
        'nav.home': 'Главная',
        'nav.about': 'О нас',
      },
    } as const;
  2. Создайте в файле src/i18n/utils.ts две вспомогательные функции: первую для определения языка страницы по текущему URL и вторую для получения переводов элементов интерфейса:

    // 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];
      }
    }

    :::note[Обратили внимание?]В первом шаге строка nav.twitter не была переведена на русский. Иногда это именно то, что нужно — не переводить имена собственные или общепринятые термины. Когда перевод для ключа отсутствует, useTranslations автоматически возвращает значение из языка по умолчанию. Поэтому русские пользователи тоже увидят "Twitter" в навигационной панели.:::

  3. Импортируйте нужные вспомогательные функции и используйте их для отображения текста на соответствующем языке. Например, компонент навигации может выглядеть так:

    ---
    // 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. На каждой странице элемент <html> должен содержать атрибут lang с указанием используемого языка. В этом примере базовый макет определяет язык из текущего маршрута:

    ---
    // 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>

    Затем вы можете использовать этот макет, чтобы страницы автоматически получали правильный атрибут lang.

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

Позвольте пользователям переключать языки

Создайте ссылки на все поддерживаемые языки, чтобы пользователи могли выбрать, на каком языке они хотят просматривать ваш сайт.

  1. Создайте компонент для переключения языков:

    ---
    // 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. Добавьте компонент <LanguagePicker /> на сайт так, чтобы он отображался на каждой странице. В примере ниже он добавлен в футер сайта в базовом макете:

    ---
    // 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>

Скрытие языка по умолчанию в URL

  1. Создайте директорию для каждого языка, кроме языка по умолчанию. Например, храните страницы на языке по умолчанию непосредственно в pages/, а переведенные страницы в ru/:

    • Директорияsrc/
      • Директорияpages/
        • about.astro
        • index.astro
        • Директорияru/
          • about.astro
          • index.astro
  2. Добавьте еще одну строку в src/i18n/ui.ts для управления отображением языка по умолчанию:

    // src/i18n/ui.ts
    export const showDefaultLang = false;
  3. Добавьте в файл src/i18n/utils.ts функцию, которая будет формировать правильные пути с учетом текущего языка:

    // 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. Импортируйте эту функцию там, где она нужна, например, в компонент навигации:

    ---
    // 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. Эту же функцию можно использовать в переключателе языков:

    ---
    // 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>

Перевод маршрутов

Переведите маршруты ваших страниц для каждого языка.

  1. Добавьте соответствия маршрутов в src/i18n/ui.ts:

    // src/i18n/ui.ts
    export const routes = {
      de: {
        'services': 'leistungen',
      },
      ru: {
        'services': 'услуги',
      },
    }
  2. Обновите функцию useTranslatedPath в src/i18n/utils.ts, добавив логику перевода маршрутов.

    // 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. Создайте вспомогательную функцию в src/i18n/utils.ts, чтобы получить маршрут на основе текущего URL (если он существует):

    // 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. Теперь эту функцию можно использовать для получения переведенного маршрута. Например, если перевод маршрута не определен, пользователь будет перенаправлен на главную страницу:

    ---
    // 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>

Ресурсы

Библиотеки сообщества

  • astro-i18next — Интеграция Astro для i18next, включающая ряд полезных компонентов.
  • astro-i18n — Библиотека интернационализации для Astro с упором на TypeScript.
  • astro-i18n-aut — Интеграция Astro для i18n, которая поддерживает defaultLocale без генерации страниц. Интеграция не зависит от адаптера и UI-фреймворка.
  • astro-react-i18next — Интеграция Astro, которая позволяет легко использовать i18next и react-i18next в React-компонентах на сайтах Astro.
  • paraglide — Полностью типобезопасная библиотека i18n, специально разработанная для паттернов частичной гидратации, таких как островки Astro.
Внести свой вклад Сообщество Поддержать