В React-приложениях библиотека i18next предоставляет механизм динамической смены языка без перезагрузки страницы. Основная задача переключения заключается в изменении текущей локали и повторном рендеринге компонентов с новыми переводами.
В экосистеме React чаще всего используется связка:
Типичная структура:
src/
├── i18n.js
├── locales/
│ ├── en/
│ │ └── translation.json
│ └── ru/
│ └── translation.json
└── components/
└── LanguageSwitcher.jsx
Файл конфигурации:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import ru from './locales/ru/translation.json';
import en from './locales/en/translation.json';
i18n
.use(initReactI18next)
.init({
resources: {
ru: {
translation: ru
},
en: {
translation: en
}
},
lng: 'ru',
fallbackLng: 'en',
interpolation: {
escapeValue: false
}
});
export default i18n;
Основные параметры:
| Параметр | Назначение |
|---|---|
resources |
Хранилище переводов |
lng |
Текущий язык |
fallbackLng |
Резервный язык |
interpolation |
Настройки подстановки значений |
Инициализация должна выполняться до рендера приложения.
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './i18n';
const root = ReactDOM.createRoot(
document.getElementById('root')
);
root.render(<App />);
После подключения конфигурации любой компонент получает доступ к
переводам через хук useTranslation.
Пример компонента:
import { useTranslation } from 'react-i18next';
function Header() {
const { t } = useTranslation();
return (
<h1>{t('welcome')}</h1>
);
}
export default Header;
JSON-файл перевода:
{
"welcome": "Добро пожаловать"
}
Английская версия:
{
"welcome": "Welcome"
}
Главный механизм смены локали — метод
changeLanguage.
i18n.changeLanguage('en');
После вызова:
Простейший переключатель:
import { useTranslation } from 'react-i18next';
function LanguageSwitcher() {
const { i18n } = useTranslation();
const switchToRussian = () => {
i18n.changeLanguage('ru');
};
const switchToEnglish = () => {
i18n.changeLanguage('en');
};
return (
<div>
<button onCl ick={switchToRussian}>
RU
</button>
<button onCl ick={switchToEnglish}>
EN
</button>
</div>
);
}
export default LanguageSwitcher;
Текущая локаль хранится в:
i18n.language
Пример:
const currentLanguage = i18n.language;
console.log(currentLanguage);
Условный рендеринг:
{
i18n.language === 'ru'
? 'Русский'
: 'English'
}
Более масштабируемый вариант:
import { useTranslation } from 'react-i18next';
function LanguageSwitcher() {
const { i18n } = useTranslation();
const changeLanguage = (lng) => {
i18n.changeLanguage(lng);
};
return (
<div>
<button onCl ick={() => changeLanguage('ru')}>
RU
</button>
<button onCl ick={() => changeLanguage('en')}>
EN
</button>
<button onCl ick={() => changeLanguage('de')}>
DE
</button>
</div>
);
}
export default LanguageSwitcher;
Такой подход особенно полезен при большом количестве языков.
Наиболее распространённый интерфейс:
import { useTranslation } from 'react-i18next';
function LanguageSelect() {
const { i18n } = useTranslation();
const handleChange = (event) => {
i18n.changeLanguage(event.target.value);
};
return (
<sel ect
value={i18n.language}
onCha nge={handleChange}
>
<option value="ru">Русский</option>
<option value="en">English</option>
<option value="de">Deutsch</option>
</select>
);
}
export default LanguageSelect;
Преимущества:
Без сохранения после обновления страницы язык сбрасывается. Обычно используются:
localStorage;При смене языка:
const changeLanguage = (lng) => {
localStorage.setItem('language', lng);
i18n.changeLanguage(lng);
};
При инициализации:
const savedLanguage =
localStorage.getItem('language') || 'ru';
Полная конфигурация:
i18n.init({
resources,
lng: localStorage.getItem('language') || 'ru',
fallbackLng: 'en',
interpolation: {
escapeValue: false
}
});
Для этого используется плагин:
npm install i18next-browser-languagedetector
Подключение:
import LanguageDetector
fr om 'i18next-browser-languagedetector';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en'
});
Теперь библиотека автоматически определяет:
Настраивается параметром order.
detection: {
order: [
'localStorage',
'navigator',
'htmlTag'
]
}
Приоритет:
<html lang="">.Настройка сохранения:
detection: {
caches: ['localStorage']
}
Теперь выбранная локаль автоматически сохраняется.
В крупных приложениях переводы не хранятся внутри сборки. Вместо этого они подгружаются динамически.
Установка backend-плагина:
npm install i18next-http-backend
Конфигурация:
import Backend from 'i18next-http-backend';
i18n
.use(Backend)
.use(initReactI18next)
.init({
fallbackLng: 'en',
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json'
}
});
Структура:
public/
└── locales/
├── en/
│ └── translation.json
└── ru/
└── translation.json
При вызове:
i18n.changeLanguage('de');
библиотека:
При ленивой загрузке может возникнуть задержка. React Suspense позволяет отображать fallback.
import React, { Suspense } from 'react';
root.render(
<Suspense fallback={<div>Loading...</div>}>
<App />
</Suspense>
);
В больших проектах переводы разделяются по модулям.
Пример:
locales/
├── ru/
│ ├── common.json
│ ├── auth.json
│ └── dashboard.json
└── en/
Конфигурация:
i18n.init({
ns: ['common', 'auth'],
defaultNS: 'common'
});
Использование:
const { t } = useTranslation('auth');
t('login');
При смене локали i18next автоматически:
Важно синхронизировать язык интерфейса с DOM.
i18n.on('languageChanged', (lng) => {
document.documentElement.lang = lng;
});
Это влияет на:
Для арабского и иврита требуется изменение направления текста.
i18n.on('languageChanged', (lng) => {
const dir = lng === 'ar'
? 'rtl'
: 'ltr';
document.documentElement.dir = dir;
});
Более универсальный вариант:
const rtlLanguages = ['ar', 'he', 'fa'];
i18n.on('languageChanged', (lng) => {
document.documentElement.dir =
rtlLanguages.includes(lng)
? 'rtl'
: 'ltr';
});
Хотя react-i18next уже использует Context API, иногда
требуется собственная обёртка.
Пример:
import { createContext } from 'react';
export const LanguageContext =
createContext();
Provider:
<LanguageContext.Provider
value={{
language: i18n.language,
changeLanguage: i18n.changeLanguage
}}
>
<App />
</LanguageContext.Provider>
Такой подход полезен при интеграции:
Пример action:
export const setLanguage = (language) => ({
type: 'SET_LANGUAGE',
payload: language
});
Reducer:
const initialState = {
language: 'ru'
};
export default function reducer(
state = initialState,
action
) {
switch (action.type) {
case 'SET_LANGUAGE':
return {
...state,
language: action.payload
};
default:
return state;
}
}
Синхронизация:
store.subscribe(() => {
const state = store.getState();
i18n.changeLanguage(state.language);
});
Часто используется структура:
/en/dashboard
/ru/dashboard
/de/dashboard
Пример с React Router:
<Route path="/:lng/dashboard" />
Получение языка:
const { lng } = useParams();
useEffect(() => {
i18n.changeLanguage(lng);
}, [lng]);
При изменении локали необходимо обновлять URL.
navigate(`/${lng}/dashboard`);
Такой подход улучшает:
Если перевод отсутствует:
fallbackLng: 'en'
i18next автоматически использует резервный словарь.
Возможна цепочка:
fallbackLng: ['en', 'ru']
Метод:
i18n.exists('header.title');
Пример:
if (i18n.exists('error.network')) {
console.log('translation exists');
}
Иногда локаль переключается из:
Пример:
import i18n from './i18n';
i18n.changeLanguage('en');
changeLanguage возвращает Promise.
i18n.changeLanguage('de')
.then(() => {
console.log('language changed');
});
С async/await:
await i18n.changeLanguage('fr');
Это особенно важно при:
Метод:
i18n.loadLanguages(['de', 'fr']);
Позволяет заранее загрузить переводы.
Преимущества:
В SSR-приложениях язык определяется до рендера.
Пример для Node.js:
i18n.changeLanguage(req.language);
Особенно важно в:
Для Next.js часто используется:
Пример конфигурации:
module.exports = {
i18n: {
locales: ['en', 'ru'],
defaultLocale: 'ru'
}
};
Переключение:
router.push(router.pathname,
router.asPath,
{ locale: 'en' }
);
Основные проблемы:
Оптимизации:
useTranslation('auth');
при отсутствии auth.json вызовет ошибки загрузки.
t('profile.name')
Если ключ отсутствует, по умолчанию отображается сам ключ.
Ошибка:
changeLanguage('EN');
Правильно:
changeLanguage('en');
Коды ISO обычно записываются в нижнем регистре.
Включение логирования:
i18n.init({
debug: true
});
i18next начинает выводить:
Пример union type:
type Language =
| 'ru'
| 'en'
| 'de';
Функция:
const changeLanguage = (
lng: Language
) => {
i18n.changeLanguage(lng);
};
Можно создать строгую типизацию ключей:
type TranslationKeys =
| 'header.title'
| 'auth.login'
| 'auth.logout';
Использование:
const tKey = (
key: TranslationKeys
) => t(key);
Это снижает количество ошибок в крупных проектах.
src/
├── i18n/
│ ├── index.js
│ ├── config.js
│ ├── detector.js
│ └── backend.js
│
├── locales/
│ ├── en/
│ ├── ru/
│ └── de/
│
├── shared/
│ └── ui/
│
└── features/
├── auth/
├── dashboard/
└── profile/
Такое разделение облегчает: