Налаштування 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
},
},
// Не виводити попередження при відсутніх ключах у розробці
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 дні.







