Как сделать мультиязычный сайт: i18n на практике
Практика3 минРедакция Innovialabs
Коротко
Мультиязычный сайт держится на четырёх решениях: отдельный URL для каждого языка, переводы в файлах по ключам, мягкое определение языка без принудительных редиректов и форматирование чисел, дат и множественного числа через Intl.
Короткий ответ
i18n (интернационализация) — это подготовка сайта к работе на нескольких языках, а l10n (локализация) — сами переводы и адаптация. Чтобы не переделывать проект потом, заложите с первого дня:
- Отдельный URL для каждого языка.
- Тексты в файлах переводов, а не в коде.
- Определение языка без насильственных редиректов.
- Форматирование чисел, дат и множественного числа по правилам языка.
Пример ниже — сайт на русском, английском и узбекском, где узбекский бывает в латинице и кириллице.
Стратегия URL
| Вариант | Пример | Плюсы | Минусы |
|---|---|---|---|
| Подпапки | site.uz/ru/, site.uz/en/ | Просто, один домен, общий SEO-вес | — |
| Поддомены | ru.site.uz | Можно разнести по серверам | Сложнее настройка |
| Разные домены | site.uz, site.com | Сильный сигнал для страны | Дорого и сложно |
| Параметр | ?lang=ru | — | Плохо для поиска |
Для большинства проектов лучший выбор — подпапки. Для узбекского можно использовать /uz/ для латиницы и /uz-cyrl/ для кириллицы. В разметке связывайте версии через hreflang с кодами ru, en, uz-Latn, uz-Cyrl и добавьте x-default.
Файлы переводов
Храните строки в JSON по ключам, по файлу на язык:
{
"nav": { "services": "Услуги", "contacts": "Контакты" },
"form": { "submit": "Отправить заявку" }
}
Практические правила:
- Один исходный язык — источник истины, остальные переводятся с него.
- Ключи по смыслу (
form.submit), а не по тексту. - Не склеивайте фразы из кусков: порядок слов в языках разный. Используйте подстановки:
"Привет, {name}". - Проверка на пропуски в CI: если ключа нет в одном из файлов, сборка должна об этом сообщать.
Кириллицу для узбекского можно получать транслитерацией из латиницы, но результат обязательно вычитывать: автоматика ошибается на заимствованных словах и апострофах.
Определение языка
- На корневом адресе
/можно посмотреть заголовокAccept-Languageи перенаправить на подходящую версию. - Не перенаправляйте с явных адресов вроде
/en/...: пользователь и поисковый робот должны получить ровно ту страницу, которую запросили. - Сохраняйте выбор пользователя в cookie и уважайте его при следующих визитах.
- Переключатель языка должен вести на ту же страницу на другом языке, а не на главную.
Множественное число
В английском и узбекском у существительных две формы, в русском — больше: «1 товар», «3 товара», «5 товаров». Не пишите условия вручную — используйте Intl.PluralRules:
const rules = new Intl.PluralRules("ru");
const forms: Record<string, string> = {
one: "товар", few: "товара", many: "товаров", other: "товара",
};
const label = (n: number) => `${n} ${forms[rules.select(n)]}`;
label(1); // "1 товар"
label(3); // "3 товара"
label(11); // "11 товаров"
Библиотеки вроде i18next или next-intl делают то же самое через формат ICU MessageFormat.
Даты, числа и валюты
new Intl.NumberFormat("ru").format(1234567.5); // "1 234 567,5"
new Intl.NumberFormat("en").format(1234567.5); // "1,234,567.5"
new Intl.DateTimeFormat("en", { dateStyle: "long" }).format(new Date());
Не форматируйте вручную: разделители тысяч, десятичная запятая и порядок дня и месяца отличаются. Поддержка uz-Latn и uz-Cyrl зависит от среды выполнения, поэтому проверьте вывод на сервере и в браузерах, а при необходимости задайте собственный формат.
Частые ошибки
- Флаги стран вместо названий языков в переключателе.
- Переведён интерфейс, но не переведены meta-теги, alt-тексты и письма.
- Одинаковый
titleу всех языковых версий. - Вёрстка ломается на длинных словах: немецкий и узбекский тексты бывают заметно длиннее русского.