Представьте ситуацию: вы закончили приложение, оно работает как часы, но ваш клиент из Берлина не может понять ни слова в интерфейсе. Или пользователь из Токио видит кривой текст, потому что система не знает, как правильно отобразить японские иероглифы. Это классическая проблема интернационализации (i18n). Для джуна это звучит страшно, но на самом деле это просто способ сделать так, чтобы ваше приложение могло говорить на разных языках без переписывания всего кода.
В этой статье мы разберем, как добавить поддержку нескольких языков в React-приложение с помощью популярной библиотеки react-i18next. Мы пройдем путь от установки до сложных случаев вроде переменных внутри предложений. Никакой магии, только понятные шаги и реальные примеры кода.
Что такое i18n и почему это важно
Интерационализация - это процесс подготовки программного обеспечения к поддержке различных языков и регионов. Обратите внимание: это не перевод сам по себе. Перевод называется локализацией (l10n). Интернационализация - это архитектура, которая позволяет легко добавлять эти переводы позже.
Если вы пишете строки прямо в компонентах, например <button>Отправить</button>, то для смены языка вам придется менять код и пересобирать приложение. С i18n вы выносите все тексты в отдельные файлы. Приложение само решает, какой файл подгрузить в зависимости от настроек браузера или выбора пользователя.
Для начинающего разработчика главное правило: никогда не хардкодьте тексты в JSX. Всегда используйте ключи. Вместо "Hello" пишите t('greeting').
Выбор инструмента: почему react-i18next?
На рынке есть несколько библиотек, таких как FormatJS (часть экосистемы React Intl) или Lingui. Однако для большинства проектов, особенно учебных и стартапов, i18next в связке с react-i18next остается стандартом де-факто.
Почему? Во-первых, у него огромное сообщество. Если вы застрянете, ответ на StackOverflow найдется быстро. Во-вторых, он поддерживает ленивую загрузку переводов (lazy loading), что критично для производительности больших приложений. В-третьих, API интуитивно понятно: вы вызываете функцию перевода, и она возвращает нужную строку.
| Библиотека | Сложность настройки | Размер бандла | Поддержка плаuralization | Экосистема |
|---|---|---|---|---|
| react-i18next | Низкая | Средний | Отличная | Огромная |
| React Intl | Высокая | Большой | Хорошая | Зрелая |
| Lingui | Средняя | Маленький | Хорошая | Растущая |
Быстрый старт: установка и базовая настройка
Давайте создадим минимальное рабочее окружение. Нам понадобятся два пакета:
i18next- ядро библиотеки.react-i18next- обертка для работы с React.
Выполните команду в терминале:
npm install i18next react-i18next
Теперь создадим структуру папок. Обычно переводы хранят в директории /public/locales или /src/locales. Создадим две папки: en и ru. Внутри каждой будет файл translation.json.
Файл public/locales/en/translation.json:
{
"welcome": "Welcome to our app!",
"login_button": "Login"
}
Файл public/locales/ru/translation.json:
{
"welcome": "Добро пожаловать в наше приложение!",
"login_button": "Войти"
}
Теперь настроим инициализацию. Создайте файл i18n.js в корне проекта:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
debug: true,
interpolation: {
escapeValue: false,
}
});
export default i18n;
Не забудьте импортировать этот файл в вашем главном файле приложения (index.js или main.jsx):
import './i18n';
Использование переводов в компонентах
Как теперь получить доступ к этим строкам в React-компоненте? Здесь на помощь приходит хук useTranslation. Это самый современный и удобный способ работы с i18n в функциональных компонентах.
Пример компонента кнопки входа:
import React from 'react';
import { useTranslation } from 'react-i18next';
function LoginForm() {
const { t, i18n } = useTranslation();
return (
<div>
<h1>{t('welcome')}</h1>
<button onClick={() => console.log('Logged in')}>
{t('login_button')}
</button>
<button onClick={() => i18n.changeLanguage('ru')}>
Переключить на русский
</button>
</div>
);
}
Обратите внимание на объект i18n. Он содержит метод changeLanguage. Вы можете вызывать его из любого места, где доступен контекст перевода. При изменении языка библиотека автоматически обновляет все компоненты, использующие хук useTranslation.
Работа с переменными и множественным числом
Статичные строки - это хорошо, но реальные данные динамичны. Как показать "У вас 5 новых сообщений" или "У вас 1 новое сообщение"? i18next отлично справляется с этим через интерполяцию и правила множественного числа.
Допустим, у нас есть ключ messages_count. В JSON-файлах структура будет такой:
en/translation.json:
{
"messages_count_one": "You have {{count}} new message",
"messages_count_other": "You have {{count}} new messages"
}
ru/translation.json:
{
"messages_count_one": "У вас {{count}} новое сообщение",
"messages_count_few": "У вас {{count}} новых сообщения",
"messages_count_many": "У вас {{count}} новых сообщений"
}
В компоненте передаем значение через второй аргумент функции t:
<p>{t('messages_count', { count: 5 })}</p>
Библиотека сама определит, какой вариант использовать, основываясь на грамматических правилах выбранного языка. Для английского достаточно двух форм (_one, _other), для русского нужно три (_one, _few, _many).
Проблемы производительности и лучшие практики
Когда приложение растет, количество строк перевода увеличивается. Загружать все языковые пакеты сразу - плохая идея. Пользователь из России не нуждается в испанском переводе при первом заходе.
Используйте i18next-http-backend для загрузки файлов по требованию. Когда пользователь переключает язык, браузер скачивает только нужный JSON-файл. Это экономит трафик и ускоряет первую отрисовку страницы (LCP).
Еще один важный момент - организация ключей. Не делайте плоскую структуру с тысячами ключей. Группируйте их по смыслу или по страницам:
{
"common": {
"save": "Save",
"cancel": "Cancel"
},
"dashboard": {
"title": "Dashboard",
"stats": "Your stats"
}
}
В коде это выглядит так: t('common.save'). Такой подход облегчает поиск ошибок и поддержку.
Частые ошибки новичков
Самая частая ошибка - забыть настроить fallbackLng. Если пользователь выберет язык, для которого нет перевода, приложение упадет или покажет пустые строки. Всегда указывайте запасной язык.
Вторая ошибка - попытка перевести HTML-теги внутри строки. Например, "Important notice". Лучше вынести форматирование в JSX, а в JSON оставить только чистый текст, либо использовать специализированные компоненты Trans из react-i18next, которые умеют работать с разметкой.
Как проверить, какой язык сейчас активен?
Используйте свойство i18n.language из объекта, полученного через хук useTranslation. Оно возвращает строковый код текущего языка, например, 'ru' или 'en-US'.
Можно ли хранить переводы в базе данных?
Да, это возможно. Вместо статических JSON-файлов можно настроить кастомный backend, который будет запрашивать переводы с сервера. Это удобно для CMS, где контент редактируют менеджеры без доступа к коду.
Что делать, если переводчик ошибся в ключах?
Включите режим debug: true в конфигурации i18n. В консоли браузера будут выводиться предупреждения о отсутствующих ключах. Также существуют инструменты для линтинга файлов переводов, которые сверяют ключи между языками.
Как обрабатывать RTL (справа налево) языки, такие как арабский?
i18next сам определяет направление письма (directionality) для поддерживаемых языков. Вам нужно лишь применить CSS-правило direction: rtl к контейнеру приложения, когда i18n.dir() возвращает 'rtl'. Многие UI-библиотеки делают это автоматически.
Нужен ли отдельный файл для каждого языка или можно один большой?
Рекомендуется разделять файлы по языкам (например, en.json, ru.json). Один большой файл со всеми языками усложняет работу переводчиков и увеличивает размер бандла, так как нельзя загружать данные выборочно.