При локализации веб-приложений и сайтов необходимо определить способ представления языка в адресной строке. От выбранной структуры URL зависят SEO-показатели, удобство пользователей, работа маршрутизации, настройка серверов и интеграция с библиотекой I18next.
Типичная задача состоит в том, чтобы один и тот же контент отображался на разных языках:
/ru/about/en/about/de/aboutI18next предоставляет механизмы определения текущего языка из различных источников, включая URL, параметры запроса, cookie, localStorage и HTTP-заголовки браузера.
Наиболее распространённый вариант.
Примеры:
/ru/
/en/
/de/
/ru/products
/en/products
/de/products
Преимущества:
Недостатки:
Пример определения языка:
https://site.com/ru/products
Язык извлекается из первого сегмента пути.
Примеры:
ru.site.com
en.site.com
de.site.com
Преимущества:
Недостатки:
Примеры:
site.ru
site.com
site.de
Преимущества:
Недостатки:
Примеры:
/products?lang=ru
/products?lang=en
Преимущества:
Недостатки:
Такой подход чаще применяется во внутренних системах и административных панелях.
Для извлечения языка из URL используется плагин Language Detector.
Установка:
npm install i18next-browser-languagedetector
Подключение:
import i18next from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
i18next
.use(LanguageDetector)
.init({
fallbackLng: "en"
});
По умолчанию детектор анализирует несколько источников языка.
Наиболее популярная конфигурация:
i18next
.use(LanguageDetector)
.init({
detection: {
order: ["path"]
}
});
Для URL:
/ru/profile
будет выбран язык:
ru
Плагин позволяет указать положение языкового кода в URL.
Пример адреса:
/app/ru/profile
Здесь язык находится во втором сегменте.
Настройка:
i18next
.use(LanguageDetector)
.init({
detection: {
order: ["path"],
lookupFromPathIndex: 1
}
});
Разбор пути:
/app/ru/profile
0 1 2
Индекс:
1
соответствует языку:
ru
Практически всегда используется цепочка проверок.
Пример:
i18next
.use(LanguageDetector)
.init({
detection: {
order: [
"path",
"localStorage",
"cookie",
"navigator"
]
}
});
Алгоритм работы:
Если ничего не найдено, используется fallbackLng.
Даже при использовании URL возможна ситуация, когда язык отсутствует или не поддерживается.
Настройка:
i18next.init({
fallbackLng: "en"
});
Если пользователь открывает:
/fr/about
а французская локаль отсутствует, приложение переключится на:
en
Для предотвращения выбора неподдерживаемых локалей рекомендуется использовать список допустимых языков.
i18next.init({
supportedLngs: ["en", "ru", "de"],
fallbackLng: "en"
});
Теперь язык:
/es
будет автоматически заменён на резервный вариант.
I18next изменяет внутреннее состояние приложения, но не модифицирует адресную строку автоматически.
Пример:
await i18next.changeLanguage("de");
После вызова необходимо обновить маршрут вручную.
Для React Router:
navigate("/de/profile");
Для Next.js:
router.push("/de/profile");
Для Vue Router:
router.push("/de/profile");
Важно, чтобы язык URL и язык I18next всегда совпадали.
Неправильная ситуация:
URL: /ru/profile
Текущий язык I18next: en
Правильная ситуация:
URL: /ru/profile
Текущий язык I18next: ru
При загрузке страницы язык должен определяться из URL и сразу передаваться в I18next.
Типичный переключатель языков строится на изменении маршрута.
Пример:
const changeLanguage = (lng) => {
i18next.changeLanguage(lng);
const currentPath = window.location.pathname;
const newPath = currentPath.replace(
/^\/(ru|en|de)/,
`/${lng}`
);
window.location.pathname = newPath;
};
Текущий путь:
/ru/products
После выбора немецкого:
/de/products
Иногда переводится не только содержимое страницы, но и сами маршруты.
Пример:
/en/about
/ru/o-kompanii
/de/uber-uns
В этом случае возникает дополнительный уровень локализации.
Структура может храниться в отдельном объекте:
const routes = {
about: {
en: "/about",
ru: "/o-kompanii",
de: "/uber-uns"
}
};
Получение маршрута:
routes.about[i18next.language];
Для поисковых систем каждая языковая версия должна иметь собственный адрес.
Хороший вариант:
/en/about
/ru/about
/de/about
Плохой вариант:
/about
с динамической подменой языка на клиенте.
Поисковые роботы значительно лучше работают с отдельными URL для каждой локали.
Для каждой страницы рекомендуется указывать альтернативные языковые версии.
Пример:
<link
rel="alternate"
hreflang="en"
href="https://site.com/en/about"
/>
<link
rel="alternate"
hreflang="ru"
href="https://site.com/ru/about"
/>
<link
rel="alternate"
hreflang="de"
href="https://site.com/de/about"
/>
Поисковые системы понимают связь между локализованными страницами и корректно показывают нужную версию пользователям разных стран.
В SSR-приложениях язык определяется ещё на сервере.
Пример запроса:
GET /ru/products
Сервер:
Такой подход обеспечивает:
Часто применяется комбинированная стратегия.
Приоритет:
detection: {
order: [
"path",
"cookie",
"navigator"
]
}
Сценарий:
/de/products.Типичная организация ресурсов:
public
└── locales
├── en
│ ├── common.json
│ └── profile.json
│
├── ru
│ ├── common.json
│ └── profile.json
│
└── de
├── common.json
└── profile.json
Конфигурация:
i18next.init({
supportedLngs: [
"en",
"ru",
"de"
],
fallbackLng: "en"
});
Код локали в URL напрямую соответствует названию каталога переводов.
Неправильно:
URL: /ru/products
i18next.language === "en"
Такая ситуация приводит к путанице в интерфейсе и проблемам индексации.
Неправильно:
i18next.init({});
Правильно:
i18next.init({
fallbackLng: "en"
});
Неправильно:
order: ["navigator"]
В этом случае невозможно создать постоянную ссылку на конкретную локаль.
Неправильно:
<a href="/products">
Правильно:
<a href="/ru/products">
или
<a href={`/${i18next.language}/products`}>
Для большинства современных проектов оптимальным считается следующий подход:
/ru/...
/en/...
/de/...
Конфигурация I18next:
i18next
.use(LanguageDetector)
.init({
fallbackLng: "en",
supportedLngs: [
"en",
"ru",
"de"
],
detection: {
order: ["path"],
lookupFromPathIndex: 0
}
});
Такая схема обеспечивает: