i18n calendar_today 20 апр. 2026 г. schedule 2 мин

Как сделать мультиязычный сайт (i18n) на Astro

Полный гайд по созданию мультиязычного сайта на Astro.js. Настраиваем встроенный i18n-роутер, словари переводов, hreflang-теги и Content Collections для разных локалей.

person
Журналист
Автор
Иконки флагов разных стран и логотип Astro

Выход на международный рынок невозможен без мультиязычного сайта. Долгое время разработчикам приходилось устанавливать тяжелые сторонние библиотеки (вроде i18next или vue-i18n) для управления переводами. Они требовали сложной настройки, добавляли лишние килобайты в клиентский JS-бандл и превращали роутинг в лабиринт.

Начиная с Astro 4, фреймворк предлагает встроенную поддержку i18n (интернационализации) прямо из коробки. В 2026 году это один из самых зрелых и удобных встроенных i18n-решений в экосистеме фронтенда. В этом гайде мы пройдем весь путь от настройки конфига до правильных SEO-тегов для Google.

Шаг 1: Настройка конфигурации

Вся магия начинается с astro.config.mjs. Объявляем список языков и язык по умолчанию:

code
// astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
  site: 'https://yoursite.com', // Обязательно для генерации hreflang
  i18n: {
    defaultLocale: 'ru',
    locales: ['ru', 'en', 'es'],
    routing: {
      // false: русский по адресу '/', а не '/ru/'
      // true: русский также получает префикс '/ru/'
      prefixDefaultLocale: false,
    },
    // Что делать, если страница не переведена?
    // 'redirect' — редиректит на версию по умолчанию
    // 'ignore' — показывает 404
    fallback: {
      en: 'ru', // Если английской версии нет — берем русскую
      es: 'ru',
    },
  },
});

После этой настройки Astro автоматически:

  • Добавляет /en/ и /es/ префиксы к нужным URL.
  • Предоставляет хелперы getRelativeLocaleUrl() и Astro.currentLocale во всех компонентах.
  • Корректно перенаправляет по правилам fallback, если перевода нет.

Шаг 2: Файловая структура роутинга

Astro использует роутинг на основе файловой системы. Создайте папки с кодами локалей внутри src/pages/:

code
src/
└── pages/
    ├── index.astro           ← Главная (русская, по умолчанию)
    ├── about.astro           ← О нас (русская)
    ├── blog/
    │   └── [slug].astro      ← Блог (русский)
    ├── en/
    │   ├── index.astro       ← Home (english)
    │   ├── about.astro       ← About (english)
    │   └── blog/
    │       └── [slug].astro  ← Blog (english)
    └── es/
        ├── index.astro       ← Inicio (español)
        └── blog/
            └── [slug].astro  ← Blog (español)

Шаг 3: Словари и перевод UI

Помимо контента, нужно переводить элементы интерфейса: кнопки навигации, заголовки разделов, подписи к формам.

Создайте файл src/i18n/ui.ts:

code
// src/i18n/ui.ts
export const languages = {
  ru: { name: 'Русский', flag: '🇷🇺' },
  en: { name: 'English', flag: '🇬🇧' },
  es: { name: 'Español', flag: '🇪🇸' },
};

export const defaultLang = 'ru';

export const ui = {
  ru: {
    'nav.home': 'Главная',
    'nav.blog': 'Блог',
    'nav.about': 'О нас',
    'nav.contact': 'Контакты',
    'blog.readMore': 'Читать далее',
    'blog.publishedOn': 'Опубликовано',
    'footer.rights': 'Все права защищены',
  },
  en: {
    'nav.home': 'Home',
    'nav.blog': 'Blog',
    'nav.about': 'About',
    'nav.contact': 'Contact',
    'blog.readMore': 'Read more',
    'blog.publishedOn': 'Published on',
    'footer.rights': 'All rights reserved',
  },
  es: {
    'nav.home': 'Inicio',
    'nav.blog': 'Blog',
    'nav.about': 'Acerca de',
    'nav.contact': 'Contacto',
    'blog.readMore': 'Leer más',
    'blog.publishedOn': 'Publicado el',
    'footer.rights': 'Todos los derechos reservados',
  },
} as const;

export type UiKey = keyof (typeof ui)['ru'];

Создайте хелпер-функцию для получения строки:

code
// src/i18n/utils.ts
import { ui, defaultLang, type UiKey } from './ui';

export function useTranslations(locale: string) {
  return function t(key: UiKey): string {
    const lang = locale in ui ? (locale as keyof typeof ui) : defaultLang;
    return ui[lang][key] ?? ui[defaultLang][key];
  };
}

Использование в компоненте:

code
---
// src/components/Navigation.astro
import { useTranslations } from '../i18n/utils';
import { getRelativeLocaleUrl } from 'astro:i18n';

const locale = Astro.currentLocale ?? 'ru';
const t = useTranslations(locale);
---

<nav>
  <a href={getRelativeLocaleUrl(locale, '/')}>{t('nav.home')}</a>
  <a href={getRelativeLocaleUrl(locale, '/blog')}>{t('nav.blog')}</a>
  <a href={getRelativeLocaleUrl(locale, '/about')}>{t('nav.about')}</a>
</nav>

Шаг 4: Мультиязычный контент с Content Collections

Для организации переведенных статей рекомендуется структура с локалью в папке:

code
src/content/
└── blog/
    ├── ru/
    │   ├── kak-sozdat-sait.mdx
    │   └── vvedenie-v-astro.mdx
    ├── en/
    │   ├── how-to-create-website.mdx
    │   └── introduction-to-astro.mdx
    └── es/
        └── como-crear-un-sitio.mdx

В content.config.ts настраиваем коллекцию с учетом языка:

code
// content.config.ts
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';

const blog = defineCollection({
  loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
  schema: ({ image }) =>
    z.object({
      title: z.string(),
      description: z.string(),
      date: z.coerce.date(),
      locale: z.enum(['ru', 'en', 'es']).default('ru'),
      translationOf: z.string().optional(), // slug оригинала для перелинковки
      cover: image().optional(),
      coverAlt: z.string().optional(),
    }),
});

export const collections = { blog };

Шаг 5: SEO и теги hreflang

Это самый критичный шаг для мультиязычного SEO. Теги hreflang сообщают Google, что существуют версии страницы для разных языков и регионов. Без них поисковик может:

  • Показать английским пользователям русскую версию.
  • Посчитать все языковые версии за дублированный контент и понизить их в выдаче.
code
---
// src/layouts/BaseLayout.astro
import { getRelativeLocaleUrl } from 'astro:i18n';

const { title, description } = Astro.props;
const locale = Astro.currentLocale ?? 'ru';
const currentPath = Astro.url.pathname.replace(/^\/(en|es)\//, '/');
const siteUrl = 'https://yoursite.com';
---

<html lang={locale}>
  <head>
    <title>{title}</title>
    <meta name="description" content={description} />

    <!-- hreflang для Google -->
    <link
      rel="alternate"
      hreflang="ru"
      href={`${siteUrl}${getRelativeLocaleUrl('ru', currentPath)}`}
    />
    <link
      rel="alternate"
      hreflang="en"
      href={`${siteUrl}${getRelativeLocaleUrl('en', currentPath)}`}
    />
    <link
      rel="alternate"
      hreflang="es"
      href={`${siteUrl}${getRelativeLocaleUrl('es', currentPath)}`}
    />
    <!-- Язык по умолчанию (если Google не может определить язык браузера) -->
    <link rel="alternate" hreflang="x-default" href={`${siteUrl}/`} />
  </head>
  <body>
    <slot />
  </body>
</html>

Шаг 6: Переключатель языков

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

code
---
// src/components/LanguageSwitcher.astro
import { getRelativeLocaleUrl } from 'astro:i18n';
import { languages } from '../i18n/ui';

const locale = Astro.currentLocale ?? 'ru';
const currentPath = Astro.url.pathname.replace(/^\/(en|es)\//, '/');
---

<div class="lang-switcher">
  {
    Object.entries(languages).map(([lang, { name, flag }]) => (
      <a
        href={getRelativeLocaleUrl(lang, currentPath)}
        class:list={['lang-btn', { active: locale === lang }]}
        hreflang={lang}
      >
        {flag} {name}
      </a>
    ))
  }
</div>

Типичные ошибки при создании мультиязычных сайтов

  1. Забытые hreflang: Ошибка №1. Без них Google игнорирует языковую структуру.
  2. Одинаковые метатеги на всех языках: Каждая версия страницы должна иметь уникальные title и description на своем языке.
  3. Дублирование контента: Если у вас нет испанской версии статьи — лучше настроить fallback на русскую и не создавать пустую страницу.
  4. Жесткий текст в компонентах: Все UI-строки должны идти через функцию t(), иначе переключение языка сломает интерфейс.

Итог

Astro предлагает один из самых элегантных встроенных i18n-роутеров в мире фронтенда. Он берет на себя сложную работу с URL, предоставляет удобные хелперы и прекрасно интегрируется с Content Collections. Самое трудоемкое в мультиязычном сайте — не техническая часть, а качественный перевод контента. Инвестируйте в него — и ваш Astro-сайт откроет двери для аудитории со всего мира.

Портрет автора Дмитрий Соколов

Senior Frontend Engineer / Tech Writer

Senior Frontend Engineer с 9-летним опытом. Специализируется на Astro.js и JAMstack.

Комментарии

Загрузка комментариев...

Оставить комментарий

Комментарии проходят модерацию перед публикацией. Правила

Рекомендуем к прочтению