Реализация функций i18n
В этом руководстве вы узнаете, как использовать коллекции контента и динамическую маршрутизацию для создания собственного решения интернационализации (i18n) и отображения контента на разных языках.
:::tipВ версии 4.0 Astro добавил встроенную поддержку i18n-маршрутизации, позволяющую указать основной язык и список поддерживаемых языков, а также предоставляющую полезные вспомогательные функции для работы с международной аудиторией. Если вы предпочитаете этот подход, ознакомьтесь с нашим руководством по интернационализации.:::
В этом примере для каждого языка используется свой путь, например, example.com/en/blog для английского и example.com/ru/blog для русского.
Если вы хотите, чтобы язык по умолчанию не отображался в URL (в отличие от других языков), ниже вы найдете инструкции по скрытию языка по умолчанию в URL.
Рецепт
Настройка страниц для каждого языка
Создайте директорию для каждого языка, который вы хотите поддерживать. Например,
en/иru/для поддержки английского и русского:Директорияsrc/
Директорияpages/
Директорияen/
- about.astro
- index.astro
Директорияru/
- about.astro
- index.astro
- index.astro
Настройте
src/pages/index.astroдля перенаправления на язык по умолчанию.--- // src/pages/index.astro --- <meta http-equiv="refresh" content="0;url=/en/" />Этот подход использует meta refresh и будет работать независимо от способа развертывания вашего сайта. На некоторых статических хостингах можно также настроить серверную переадресацию через специальный конфигурационный файл. Подробнее об этом читайте в документации вашей платформы развертывания.
Если вы используете SSR-адаптер, вы можете использовать
Astro.redirect(EN) для серверной переадресации на язык по умолчанию.--- // src/pages/index.astro return Astro.redirect('/en/'); ---
Организация переводов с помощью коллекций
Создайте в
src/content/отдельные каталоги для каждого типа контента и добавьте в них подкаталоги для всех поддерживаемых языков. Например, для блог-постов на английском и русском:Директорияsrc/
Директорияcontent/
Директорияblog/
Директорияen/ Посты на английском
- post-1.md
- post-2.md
Директорияru/ Посты на русском
- post-1.md
- post-2.md
Создайте файл
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 };Подробнее о Коллекциях контента (EN).Используйте динамические маршруты (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/>В режиме SSR запрашивайте нужную запись напрямую:
//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/>Подробнее о динамических маршрутах (EN).:::tip[Форматирование даты]В примере выше используется встроенный метод
toLocaleString()для создания удобочитаемой строки из даты в метаданных.Это гарантирует, что дата и время будут отформатированы в соответствии с языком пользователя.:::
Перевод элементов интерфейса
Создайте словари с переводами для всех текстовых элементов интерфейса вашего сайта. Это позволит посетителям использовать сайт полностью на их родном языке.
Создайте файл
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;Создайте в файле
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" в навигационной панели.:::Импортируйте нужные вспомогательные функции и используйте их для отображения текста на соответствующем языке. Например, компонент навигации может выглядеть так:
--- // 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>На каждой странице элемент
<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>
Позвольте пользователям переключать языки
Создайте ссылки на все поддерживаемые языки, чтобы пользователи могли выбрать, на каком языке они хотят просматривать ваш сайт.
Создайте компонент для переключения языков:
--- // src/components/LanguagePicker.astro import { languages } from '../i18n/ui'; --- <ul> {Object.entries(languages).map(([lang, label]) => ( <li> <a href={`/${lang}/`}>{label}</a> </li> ))} </ul>Добавьте компонент
<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
Создайте директорию для каждого языка, кроме языка по умолчанию. Например, храните страницы на языке по умолчанию непосредственно в
pages/, а переведенные страницы вru/:Директорияsrc/
Директорияpages/
- about.astro
- index.astro
Директорияru/
- about.astro
- index.astro
Добавьте еще одну строку в
src/i18n/ui.tsдля управления отображением языка по умолчанию:// src/i18n/ui.ts export const showDefaultLang = false;Добавьте в файл
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}` } }Импортируйте эту функцию там, где она нужна, например, в компонент навигации:
--- // 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>Эту же функцию можно использовать в переключателе языков:
--- // 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>
Перевод маршрутов
Переведите маршруты ваших страниц для каждого языка.
Добавьте соответствия маршрутов в
src/i18n/ui.ts:// src/i18n/ui.ts export const routes = { de: { 'services': 'leistungen', }, ru: { 'services': 'услуги', }, }Обновите функцию
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}` } }Создайте вспомогательную функцию в
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; }Теперь эту функцию можно использовать для получения переведенного маршрута. Например, если перевод маршрута не определен, пользователь будет перенаправлен на главную страницу:
--- // 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.