Настройка i18n-фреймворка (i18next) для веб-приложения
i18next — наиболее распространённый i18n-фреймворк для JavaScript-экосистемы. Работает с React, Vue, Angular, Svelte, Node.js и vanilla JS. Огромная экосистема плагинов: бэкенды для загрузки переводов, детекторы языка, форматтеры. В отличие от react-intl, не привязан к одному фреймворку.
Установка
# Базовый пакет
npm install i18next
# React-интеграция
npm install react-i18next
# Загрузка переводов через HTTP
npm install i18next-http-backend
# Определение языка браузера
npm install i18next-browser-languagedetector
# Кеширование в localStorage
npm install i18next-localstorage-backend
Инициализация
// src/i18n/config.ts
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import HttpBackend from 'i18next-http-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
i18n
.use(HttpBackend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
// Языки приложения
supportedLngs: ['ru', 'en', 'de', 'uk'],
fallbackLng: 'ru',
defaultNS: 'common',
// Неймспейсы — разбивка переводов по доменам
ns: ['common', 'catalog', 'checkout', 'account'],
// Загрузка с сервера
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
addPath: '/locales/add/{{lng}}/{{ns}}', // для i18next-parser
},
// Определение языка
detection: {
order: ['querystring', 'cookie', 'localStorage', 'navigator', 'htmlTag'],
lookupQuerystring: 'lng',
lookupCookie: 'locale',
lookupLocalStorage: 'i18nextLng',
caches: ['localStorage', 'cookie'],
},
interpolation: {
escapeValue: false,
// Форматтеры для дат, чисел, валюты
format(value, format, lng) {
if (format === 'date') {
return new Intl.DateTimeFormat(lng, { dateStyle: 'medium' }).format(value)
}
if (format === 'currency') {
const currency = lng === 'ru' ? 'RUB' : lng === 'uk' ? 'UAH' : 'USD'
return new Intl.NumberFormat(lng, { style: 'currency', currency, maximumFractionDigits: 0 }).format(value)
}
return value
},
},
// Не выводить предупреждения при отсутствии ключа в dev
saveMissing: process.env.NODE_ENV === 'development',
missingKeyHandler(lngs, ns, key) {
console.warn(`[i18next] Missing key: ${ns}:${key} for ${lngs.join(',')}`)
},
})
export default i18n
// src/main.tsx
import './i18n/config' // импорт до App
import { Suspense } from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')!).render(
<Suspense fallback={<div>Загрузка...</div>}>
<App />
</Suspense>
)
Файлы переводов
public/locales/
ru/
common.json
catalog.json
checkout.json
en/
common.json
catalog.json
checkout.json
// public/locales/ru/catalog.json
{
"title": "Каталог",
"filter": {
"title": "Фильтры",
"reset": "Сбросить",
"apply": "Применить",
"price_from": "Цена от",
"price_to": "до"
},
"sort": {
"label": "Сортировка",
"price_asc": "Дешевле",
"price_desc": "Дороже",
"popular": "Популярные",
"new": "Новинки"
},
"items_count_one": "{{count}} товар",
"items_count_few": "{{count}} товара",
"items_count_many": "{{count}} товаров",
"items_count_other": "{{count}} товаров",
"empty": "По вашему запросу ничего не найдено",
"price": "{{price, currency}}"
}
Использование в React
import { useTranslation, Trans } from 'react-i18next'
function CatalogPage({ count, price }: { count: number; price: number }) {
// Загрузка конкретного неймспейса
const { t, i18n } = useTranslation('catalog')
return (
<main>
<h1>{t('title')}</h1>
{/* Плюрализация */}
<p>{t('items_count', { count })}</p>
{/* ru, count=3: "3 товара" */}
{/* Форматирование через интерполяцию */}
<p>{t('price', { price })}</p>
{/* ru: "14 990 ₽" */}
{/* Несколько неймспейсов */}
<FilterPanel />
</main>
)
}
// Trans компонент для текста с HTML/компонентами внутри
function PrivacyNote() {
const { t } = useTranslation('common')
return (
<Trans
i18nKey="privacy_note"
components={{
link: <a href="/privacy" className="underline" />,
}}
/>
// JSON: "Нажимая кнопку, вы принимаете <link>политику конфиденциальности</link>"
)
}
Переключение языка
function LanguageSwitcher() {
const { i18n } = useTranslation()
const changeLanguage = async (lng: string) => {
await i18n.changeLanguage(lng)
// i18next автоматически:
// 1. Загрузит нужные JSON файлы если ещё не загружены
// 2. Сохранит в localStorage/cookie
// 3. Обновит все компоненты через React context
document.documentElement.lang = lng
}
return (
<div>
{['ru', 'en', 'de', 'uk'].map(lng => (
<button
key={lng}
onClick={() => changeLanguage(lng)}
disabled={i18n.resolvedLanguage === lng}
>
{lng.toUpperCase()}
</button>
))}
</div>
)
}
SSR: i18next с Node.js
// server/i18n.ts — отдельный инстанс для SSR
import i18next from 'i18next'
import Backend from 'i18next-fs-backend'
const serverI18n = i18next.createInstance()
await serverI18n
.use(Backend)
.init({
lng: 'ru',
fallbackLng: 'ru',
ns: ['common', 'catalog'],
backend: {
loadPath: './public/locales/{{lng}}/{{ns}}.json',
},
interpolation: { escapeValue: false },
})
export function createI18nForRequest(locale: string) {
return serverI18n.cloneInstance({ lng: locale })
}
Автоматическое извлечение ключей
npm install --save-dev i18next-parser
# i18next-parser.config.js
module.exports = {
locales: ['ru', 'en', 'de'],
output: 'public/locales/$LOCALE/$NAMESPACE.json',
input: ['src/**/*.{ts,tsx}'],
keepRemoved: false,
sort: true,
}
npx i18next-parser
Парсер найдёт все вызовы t('key'), useTranslation('ns'), <Trans i18nKey="..."> и обновит JSON-файлы: добавит новые ключи, оставит имеющиеся, удалит неиспользуемые.
Неймспейсы по маршрутам
// Загрузка неймспейса только для нужной страницы
export async function loader() {
// React Router v6 loader
await i18n.loadNamespaces('checkout')
return null
}
function CheckoutPage() {
const { t } = useTranslation('checkout')
// Неймспейс гарантированно загружен
}
Сроки
Установка и базовая конфигурация с 2 языками — 1 день. С несколькими неймспейсами, ленивой загрузкой по маршрутам, i18next-parser и CI-проверкой — 2–3 дня.







