i18n calendar_today Apr 20, 2026

Как сделать мультиязычный сайт (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.

Комментарии (4)

АГ
18 апр 2026

Попробовал перенести проект с Next.js на Astro. Оказалось, что статическая генерация (SSG) и islands architecture действительно ускоряют загрузку (PageSpeed стал 95+). Подход с частичной гидратацией просто отличный!

МЕ
19 апр 2026

Подскажите, а как лучше настроить SSR адаптер для деплоя Astro на Vercel или Cloudflare? Вроде бы Node.js адаптер тоже подходит, но хочется использовать edge functions для максимальной скорости.

ПЗ
20 апр 2026

Спасибо за разбор! Особенно полезна часть про интеграцию Tailwind CSS v4 и работу с MDX коллекциями (content collections) через схемы Zod. Строгая типизация контента очень помогает при разработке.

ВЛ
21 апр 2026

Подключила PocketBase к Astro по вашей схеме. View Transitions (плавные переходы между страницами) работают шикарно, но возник вопрос: как правильно кэшировать запросы к БД на этапе сборки статического сайта?

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

Оставляя комментарий, вы соглашаетесь с правилами.

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