12k
All articles

Добавление интернационализации в React-приложение

Настройте интернационализацию React с react-i18next: интерполяция, множественное число, RTL, локальные форматы и SSR в Next.js.

OpenReplay Team
OpenReplay Team
Добавление интернационализации в React-приложение

Добавление интернационализации в React-приложение означает вынесение всех пользовательских строк во внешние файлы для каждого языка и их отображение через слой перевода вместо жёсткого кодирования текста в JSX.

Если вам когда-либо приходилось выпускать сборку, в которой на экране пользователя отображался сырой ключ вроде t('main.header'), или наблюдать, как немецкая строка разрушает вёрстку кнопки, которая отлично смотрелась на английском, — вы уже знаете, что сама настройка — не самая сложная часть. Базовое подключение занимает полдня; локально-специфичные граничные случаи отнимают весь остаток спринта. Производственным стандартом является react-i18next — React-обёртка над фреймворком i18next. Используйте react-i18next в качестве основного решения: он основан на хуках, поддерживает пространства имён и ленивую загрузку, работает с серверным рендерингом и опирается на крупнейшую экосистему плагинов i18next. Обращайтесь к react-intl только в том случае, если вы принципиально придерживаетесь синтаксиса ICU-сообщений. В этом руководстве рассматривается актуальная корректная настройка, а затем пять проблем, которые проявляются в продакшне: интерполяция, плюрализация, локально-зависимое форматирование чисел и дат, макет для письма справа налево и SSR.

Ключевые выводы

  • Устанавливайте interpolation.escapeValue: false в конфигурации i18next, поскольку React уже экранирует значения перед рендерингом; если оставить экранирование i18next включённым, строки будут экранироваться дважды.
  • В текущей версии i18next ключи множественного числа используют суффиксы CLDR/Intl (_zero, _one, _two, _few, _many, _other), а устаревший суффикс _plural относится к старому формату JSON v3; переменная-селектор должна называться count.
  • Форматирование чисел и дат зависит от региона, а не только от языка, поэтому уточняйте локали (en-US, ar-EG) и используйте встроенные Intl-форматтеры i18next через {{value, number}} и {{date, datetime}}.
  • Загружайте переводы из JSON-файлов с помощью i18next-http-backend и параметра loadPath; встраивание через require() включает все языки в основной бандл и исключает возможность ленивой загрузки.
  • В Next.js не реализуйте SSR-интернационализацию вручную: next-i18next v16 обеспечивает поддержку как App Router, так и Pages Router в одном пакете.

Как настроить react-i18next?

Установите ядро фреймворка, React-обёртку и два плагина, отвечающих за определение языка и загрузку файлов. Четыре пакета обеспечивают всю настройку, каждый с чётко определённой задачей:

ПакетВерсияНазначение
i18next26.xЯдро: поиск ключей, интерполяция, плюрализация, форматирование
react-i18next17.xReact-обёртка: useTranslation, Trans
i18next-browser-languagedetector8.xОпределяет язык пользователя
i18next-http-backend4.xЗагружает JSON-переводы по HTTP

Важная оговорка: i18next-http-backend v4 требует нативного fetch. Node ≥ 18, все современные браузеры, Deno и Bun поставляются с fetch по умолчанию. На более старых средах выполнения необходимо использовать ponyfill или оставаться на v3.

npm install i18next react-i18next i18next-browser-languagedetector i18next-http-backend

Создайте файл src/i18n.ts и выполните инициализацию один раз:

import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: 'en',
    supportedLngs: ['en', 'es', 'ar'],
    load: 'languageOnly',
    backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' },
    interpolation: { escapeValue: false },
  });

export default i18n;

Устанавливайте interpolation.escapeValue: false, поскольку React уже экранирует значения перед рендерингом; если оставить экранирование i18next включённым, строки будут экранироваться дважды. Параметр loadPath имеет принципиальное значение: встраивание ресурсов через require() (паттерн эпохи Create React App / Webpack) включает все языки в основной бандл и исключает ленивую загрузку. Импортируйте конфигурацию один раз в точке входа, до начала рендеринга: import './i18n'; в main.tsx.

Вынесение строк с помощью хука useTranslation

Переводы хранятся в JSON-файлах для каждого языка по пути public/locales/<lng>/translation.json, а компоненты обращаются к ним через функцию t хука useTranslation. Замените все жёстко заданные строки на обращения по ключу.

{ "main": { "header": "Welcome to the app!" } }
import { useTranslation } from 'react-i18next';

export default function Header() {
  const { t } = useTranslation();
  return <h1>{t('main.header')}</h1>;
}

Вложенные ключи (main.header) и пространства имён позволяют организовать большие наборы строк. Для текста, содержащего встроенную разметку или ссылки, обычный вызов t() нарушает структуру JSX. В таких случаях используйте компонент Trans, который интерполирует React-элементы в переведённое предложение, сохраняя разметку в компоненте, а не в JSON.

<Trans i18nKey="main.docs" components={{ docsLink: <a href="https://react.i18next.com/" /> }} />

Как переключать и определять язык?

Смените активный язык с помощью i18n.changeLanguage(lng) — каждый компонент, использующий useTranslation, автоматически перерендерится. Переключатель языка — это просто кнопки или элемент <select>, вызывающий этот метод:

const { i18n } = useTranslation();
<select
  value={i18n.resolvedLanguage}
  onChange={(e) => i18n.changeLanguage(e.target.value)}
>
  <option value="en">English</option>
  <option value="ar">العربية</option>
</select>

Определение языка обеспечивается плагином language-detector, который проверяет источники в фиксированном порядке: строка запроса (?lng=en), cookie, localStorage, браузерный объект navigator, затем атрибут <html lang>. Плагин останавливается на первом поддерживаемом совпадении. Определённый язык кэшируется в localStorage, поэтому возвращающиеся пользователи сохраняют свой выбор; вызов changeLanguage вручную также обновляет этот кэш.

Пять проблем, которые дают о себе знать

Большинство ошибок интернационализации проявляются за пределами штатного сценария. Ниже описаны режимы сбоев, которые проходят локальное QA и обнаруживаются только в реальной локали пользователя.

Интерполяция. Вставляйте динамические значения с помощью синтаксиса {{var}} и передавайте их вторым аргументом: t('greeting', { name }) для строки "Hello, {{name}}". Экранирование React в сочетании с escapeValue: false обеспечивает защиту от XSS.

Плюрализация. В английском языке две формы множественного числа, в арабском — шесть, именно поэтому никогда не пишите вручную if (count === 1). Передайте count в t() и позвольте Intl.PluralRules выбрать нужный ключ. Определяйте формы с помощью суффиксов CLDR: _zero, _one, _two, _few, _many, _other. Переменная должна называться именно count.

{
  "messages_one": "You have one message",
  "messages_other": "You have {{count}} new messages"
}

Суффикс _plural относится к устаревшему формату JSON v3. При введении формата JSON v4 i18next привёл суффиксы множественного числа в соответствие с теми, что используются в Intl API. Начиная с v24, Intl API является обязательным: если ваша среда выполнения не поддерживает Intl.PluralRules, необходимо использовать полифил, поскольку прежний механизм обратной совместимости с v3 упразднён, а compatibilityJSON больше не принимает значение 'v3'.

Форматирование чисел и дат. Используйте встроенные Intl-форматтеры i18next: {{value, number}} и {{date, datetime}} с такими параметрами, как {{value, number(style: percent)}}. Поскольку форматирование зависит от региона, уточняйте локали (en-US, ar-EG), чтобы числа и порядок элементов даты оставались согласованными в разных браузерах.

Письмо справа налево. Для RTL-языков устанавливайте направление документа через i18n.dir() при каждой смене языка, чтобы весь макет перестраивался без добавления CSS в каждый компонент:

useEffect(() => {
  const apply = (lng: string) => {
    document.documentElement.lang = lng;
    document.documentElement.dir = i18n.dir(lng);
  };
  i18n.on('languageChanged', apply);
  return () => i18n.off('languageChanged', apply);
}, [i18n]);

Читайте i18n.dir() внутри обработчика languageChanged, а не синхронно в процессе переключения: после вызова changeLanguage() значение i18next.language отражает новый язык только после загрузки ресурсов.

SSR. Не реализуйте серверную интернационализацию в Next.js вручную. next-i18next v16 — это тонкая обёртка над i18next и react-i18next, которая берёт на себя всю специфическую для Next.js интеграцию: middleware, разделение серверной и клиентской частей, а также гидратацию ресурсов. Пакет поддерживает как App Router (Server Components, Client Components, middleware), так и Pages Router, предоставляя getT() для Server Components и useT() для Client Components. Оборачивайте клиентские деревья компонентов в <Suspense>, не рассчитывая на наличие объекта window. Именно такие локально-специфичные дефекты — сырой ключ вроде main.header, отображаемый пользователю, обрезанный текст в RTL-макете или текст на резервном языке, просачивающийся на переведённый экран, — проходят QA на локали по умолчанию и обнаруживаются только при просмотре реальной сессии в целевой локали. Именно здесь запись сессий оправдывает своё применение.

Масштабирование с помощью пространств имён и извлечения ключей

По мере роста числа строк разбивайте переводы на пространства имён и загружайте их по маршруту с помощью useTranslation('dashboard'), чтобы каждая страница загружала только свой JSON и бандлы оставались небольшими. Когда строки разрастаются по всей кодовой базе, обратитесь к автоматизированным инструментам: i18next-cli — официальный универсальный инструмент командной строки, который обеспечивает извлечение ключей, линтинг кода, синхронизацию локалей и генерацию типов. Система управления переводами — Lokalise, Phrase или Crowdin — координирует работу переводчиков, когда начинается полноценная локализация.

Теперь у вас есть корректная настройка react-i18next и понимание ключевых нюансов. Подключите конфигурацию, вынесите строки во внешние файлы, затем переходите к пространствам имён по мере роста бандлов и к next-i18next при серверном рендеринге. Сверяйте точные версии пакетов с npm в момент установки, поскольку ядро i18next и его обёртки обновляются часто.

Часто задаваемые вопросы

В чём разница между i18next и react-i18next?

i18next — это ядро фреймворка, которое реализует саму логику перевода: поиск ключей, интерполяцию, плюрализацию и форматирование. react-i18next — это React-обёртка поверх него, предоставляющая хуки вроде useTranslation, компонент Trans и автоматический перерендеринг при смене языка. Устанавливать нужно оба пакета: i18next выполняет работу, react-i18next связывает его с компонентами. react-i18next требует совместимой версии i18next в качестве peer-зависимости, поэтому держите их на совместимых мажорных версиях.

Почему ключ перевода отображается как обычный текст вместо переведённой строки?

Если пользователь видит сырой ключ вроде main.header, значит поиск не дал результата — почти всегда из-за того, что JSON-файл для данного языка или пространства имён так и не загрузился. Типичные причины: loadPath не соответствует расположению файлов, пространство имён не зарегистрировано, конфигурация i18n не импортирована до начала рендеринга, или ключ отсутствует в файле. Проверьте вкладку сети на наличие неудачного запроса к пути локалей и убедитесь, что ключ существует в нужном языковом файле.

Нужно ли по-прежнему использовать суффикс _plural для ключей множественного числа в i18next?

Нет. Суффикс _plural относится к устаревшему формату JSON v3. Актуальная версия i18next использует словесные суффиксы CLDR/Intl, соответствующие Intl.PluralRules: _zero, _one, _two, _few, _many и _other. В английском языке используются две формы (_one и _other), в арабском — все шесть. Переменная, определяющая форму, должна называться count и обязательно присутствовать, поскольку при её отсутствии резервного варианта нет. Если Intl.PluralRules недоступен, необходимо использовать полифил: начиная с v24 прежний механизм обратной совместимости с v3 упразднён, а compatibilityJSON больше не принимает значение v3.

Нужно ли хранить переводы в JSON-файлах или их можно встроить прямо в конфигурацию?

Встроить переводы через опцию resources технически возможно, однако для всего, что выходит за рамки простейшего приложения, следует загружать их из JSON-файлов с помощью i18next-http-backend и параметра loadPath вида /locales/{{lng}}/{{ns}}.json. Встраивание всех языков через require() включает все переводы в основной бандл и исключает ленивую загрузку — пользователи скачивают строки для языков, которыми никогда не воспользуются. Загрузка из файлов позволяет получать только активный язык и пространство имён по требованию. Обратите внимание, что i18next-http-backend v4 требует нативного fetch, то есть Node 18 или новее.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.