Интернационализация (i18n) — процесс подготовки приложения к работе на разных языках и в разных регионах. Название i18n: первая буква "i", потом 18 букв, потом "n".
Локализация (l10n) — адаптация под конкретную локаль: переводы, форматы дат, валюты, числа, RTL.
Типичные задачи i18n:
01/31/2024 (en-US) vs 31.01.2024 (ru-RU)1,000.50 (en) vs 1 000,50 (ru)Самая популярная библиотека для i18n в React:
npm install react-i18next i18next
npm install i18next-http-backend i18next-browser-languagedetector// i18n.ts — конфигурация
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
i18n
.use(LanguageDetector) // определяет язык браузера
.use(initReactI18next) // интеграция с React
.init({
fallbackLng: 'en', // язык по умолчанию
debug: process.env.NODE_ENV === 'development',
resources: {
en: {
translation: {
welcome: 'Welcome, {{name}}!',
items_one: '{{count}} item',
items_other: '{{count}} items',
}
},
ru: {
translation: {
welcome: 'Добро пожаловать, {{name}}!',
items_one: '{{count}} элемент',
items_few: '{{count}} элемента',
items_many: '{{count}} элементов',
}
}
}
})import { useTranslation } from 'react-i18next'
function Greeting({ name, itemCount }) {
const { t, i18n } = useTranslation()
return (
<div>
{/* Простой перевод */}
<h1>{t('welcome', { name })}</h1>
{/* Множественное число */}
<p>{t('items', { count: itemCount })}</p>
{/* Смена языка */}
<button onClick={() => i18n.changeLanguage('ru')}>RU</button>
<button onClick={() => i18n.changeLanguage('en')}>EN</button>
{/* Текущий язык */}
<span>Язык: {i18n.language}</span>
</div>
)
}// Разделяем переводы по файлам
// locales/ru/common.json
{ "save": "Сохранить", "cancel": "Отмена" }
// locales/ru/dashboard.json
{ "title": "Панель управления", "stats": "Статистика" }
// Использование
const { t } = useTranslation(['common', 'dashboard'])
t('save') // из common
t('dashboard:title') // из dashboard// Переменные
t('greeting', { name: 'Алексей' })
// "Привет, Алексей!"
// Форматирование через i18next-icu или встроенное
t('price', { price: 1999.99, formatParams: {
price: { currency: 'RUB', style: 'currency', locale: 'ru-RU' }
}})
// "1 999,99 ₽"
// Дата
t('date', { date: new Date(), formatParams: {
date: { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }
}})Правила очень разные в разных языках:
// Английский: one / other
// 1 apple, 2 apples
// Русский: one / few / many (+ дополнительные правила)
// 1 яблоко, 2 яблока, 5 яблок, 11 яблок (исключение!)
// 21 яблоко, 22 яблока, 25 яблок
// В файле переводов
{
"apples_one": "{{count}} яблоко",
"apples_few": "{{count}} яблока",
"apples_many": "{{count}} яблок",
"apples_other": "{{count}} яблока" // дробные числа
}
// Использование — i18next сам выберет форму!
t('apples', { count: 1 }) // "1 яблоко"
t('apples', { count: 3 }) // "3 яблока"
t('apples', { count: 11 }) // "11 яблок"
t('apples', { count: 21 }) // "21 яблоко"// Определяем направление по локали
const rtlLanguages = ['ar', 'he', 'fa', 'ur']
const isRTL = rtlLanguages.includes(i18n.language)
// В HTML
document.documentElement.dir = isRTL ? 'rtl' : 'ltr'
document.documentElement.lang = i18n.language
// В CSS — используем логические свойства
.card {
margin-inline-start: 16px; /* вместо margin-left */
padding-inline-end: 8px; /* вместо padding-right */
}// Ленивая загрузка локалей (не встраивать всё в бандл!)
import Backend from 'i18next-http-backend'
i18n.use(Backend).init({
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
}
})
// Suspense для ожидания загрузки
function App() {
return (
<Suspense fallback="Загрузка...">
<MyApp />
</Suspense>
)
}Полноценная i18n-система на ванильном JS: переводы, функция t(), интерполяция переменных, правила множественного числа и переключение локали
// Реализуем минималистичную i18n систему:
// переводы, t(), плюрализация и смена языка.
// --- Правила множественного числа ---
const pluralRules = {
en: (count) => count === 1 ? 'one' : 'other',
ru: (count) => {
const mod10 = count % 10
const mod100 = count % 100
if (mod100 >= 11 && mod100 <= 19) return 'many'
if (mod10 === 1) return 'one'
if (mod10 >= 2 && mod10 <= 4) return 'few'
return 'many'
},
de: (count) => count === 1 ? 'one' : 'other',
}
// --- Переводы ---
const translations = {
en: {
greeting: 'Hello, {{name}}!',
farewell: 'Goodbye, {{name}}!',
items_one: '{{count}} item',
items_other: '{{count}} items',
price: 'Price: USD {{amount}}',
},
ru: {
greeting: 'Привет, {{name}}!',
farewell: 'До свидания, {{name}}!',
items_one: '{{count}} элемент',
items_few: '{{count}} элемента',
items_many: '{{count}} элементов',
price: 'Цена: {{amount}} ₽',
},
de: {
greeting: 'Hallo, {{name}}!',
farewell: 'Auf Wiedersehen, {{name}}!',
items_one: '{{count}} Element',
items_other: '{{count}} Elemente',
},
}
// --- I18n движок ---
function createI18nEngine(defaultLocale = 'en') {
let currentLocale = defaultLocale
// Интерполяция: заменить {{variable}} на значения
function interpolate(str, params) {
return str.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return params[key] !== undefined ? params[key] : match
})
}
// Функция перевода
function t(key, params = {}) {
const locale = currentLocale
const dict = translations[locale] || translations['en']
// Если передан count — ищем форму множественного числа
if (params.count !== undefined) {
const rule = pluralRules[locale] || pluralRules['en']
const form = rule(params.count)
const pluralKey = key + '_' + form
const str = dict[pluralKey] || dict[key + '_other'] || dict[key]
return str ? interpolate(str, params) : key
}
const str = dict[key]
return str ? interpolate(str, params) : key
}
return {
t,
setLocale(locale) {
if (!translations[locale]) {
console.warn('Локаль не найдена:', locale)
return
}
currentLocale = locale
console.log('Локаль изменена на:', locale)
},
getLocale() { return currentLocale },
getSupportedLocales() { return Object.keys(translations) },
}
}
// --- Тесты ---
const i18n = createI18nEngine('en')
const { t, setLocale, getLocale } = i18n
console.log('=== English ===')
console.log(t('greeting', { name: 'Alex' })) // Hello, Alex!
console.log(t('items', { count: 1 })) // 1 item
console.log(t('items', { count: 5 })) // 5 items
console.log('
=== Русский ===')
setLocale('ru')
console.log(t('greeting', { name: 'Алексей' })) // Привет, Алексей!
console.log(t('price', { amount: 999 })) // Цена: 999 ₽
// Проверка всех форм множественного числа
const counts = [1, 2, 5, 11, 21, 22, 25]
counts.forEach(n => console.log(n + ': ' + t('items', { count: n })))
console.log('
=== Deutsch ===')
setLocale('de')
console.log(t('greeting', { name: 'Hans' }))
console.log(t('items', { count: 1 }))
console.log(t('items', { count: 3 }))
console.log('
Текущая локаль:', getLocale())
console.log('Доступные локали:', i18n.getSupportedLocales())Интернационализация (i18n) — процесс подготовки приложения к работе на разных языках и в разных регионах. Название i18n: первая буква "i", потом 18 букв, потом "n".
Локализация (l10n) — адаптация под конкретную локаль: переводы, форматы дат, валюты, числа, RTL.
Типичные задачи i18n:
01/31/2024 (en-US) vs 31.01.2024 (ru-RU)1,000.50 (en) vs 1 000,50 (ru)Самая популярная библиотека для i18n в React:
npm install react-i18next i18next
npm install i18next-http-backend i18next-browser-languagedetector// i18n.ts — конфигурация
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
i18n
.use(LanguageDetector) // определяет язык браузера
.use(initReactI18next) // интеграция с React
.init({
fallbackLng: 'en', // язык по умолчанию
debug: process.env.NODE_ENV === 'development',
resources: {
en: {
translation: {
welcome: 'Welcome, {{name}}!',
items_one: '{{count}} item',
items_other: '{{count}} items',
}
},
ru: {
translation: {
welcome: 'Добро пожаловать, {{name}}!',
items_one: '{{count}} элемент',
items_few: '{{count}} элемента',
items_many: '{{count}} элементов',
}
}
}
})import { useTranslation } from 'react-i18next'
function Greeting({ name, itemCount }) {
const { t, i18n } = useTranslation()
return (
<div>
{/* Простой перевод */}
<h1>{t('welcome', { name })}</h1>
{/* Множественное число */}
<p>{t('items', { count: itemCount })}</p>
{/* Смена языка */}
<button onClick={() => i18n.changeLanguage('ru')}>RU</button>
<button onClick={() => i18n.changeLanguage('en')}>EN</button>
{/* Текущий язык */}
<span>Язык: {i18n.language}</span>
</div>
)
}// Разделяем переводы по файлам
// locales/ru/common.json
{ "save": "Сохранить", "cancel": "Отмена" }
// locales/ru/dashboard.json
{ "title": "Панель управления", "stats": "Статистика" }
// Использование
const { t } = useTranslation(['common', 'dashboard'])
t('save') // из common
t('dashboard:title') // из dashboard// Переменные
t('greeting', { name: 'Алексей' })
// "Привет, Алексей!"
// Форматирование через i18next-icu или встроенное
t('price', { price: 1999.99, formatParams: {
price: { currency: 'RUB', style: 'currency', locale: 'ru-RU' }
}})
// "1 999,99 ₽"
// Дата
t('date', { date: new Date(), formatParams: {
date: { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }
}})Правила очень разные в разных языках:
// Английский: one / other
// 1 apple, 2 apples
// Русский: one / few / many (+ дополнительные правила)
// 1 яблоко, 2 яблока, 5 яблок, 11 яблок (исключение!)
// 21 яблоко, 22 яблока, 25 яблок
// В файле переводов
{
"apples_one": "{{count}} яблоко",
"apples_few": "{{count}} яблока",
"apples_many": "{{count}} яблок",
"apples_other": "{{count}} яблока" // дробные числа
}
// Использование — i18next сам выберет форму!
t('apples', { count: 1 }) // "1 яблоко"
t('apples', { count: 3 }) // "3 яблока"
t('apples', { count: 11 }) // "11 яблок"
t('apples', { count: 21 }) // "21 яблоко"// Определяем направление по локали
const rtlLanguages = ['ar', 'he', 'fa', 'ur']
const isRTL = rtlLanguages.includes(i18n.language)
// В HTML
document.documentElement.dir = isRTL ? 'rtl' : 'ltr'
document.documentElement.lang = i18n.language
// В CSS — используем логические свойства
.card {
margin-inline-start: 16px; /* вместо margin-left */
padding-inline-end: 8px; /* вместо padding-right */
}// Ленивая загрузка локалей (не встраивать всё в бандл!)
import Backend from 'i18next-http-backend'
i18n.use(Backend).init({
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
}
})
// Suspense для ожидания загрузки
function App() {
return (
<Suspense fallback="Загрузка...">
<MyApp />
</Suspense>
)
}Полноценная i18n-система на ванильном JS: переводы, функция t(), интерполяция переменных, правила множественного числа и переключение локали
// Реализуем минималистичную i18n систему:
// переводы, t(), плюрализация и смена языка.
// --- Правила множественного числа ---
const pluralRules = {
en: (count) => count === 1 ? 'one' : 'other',
ru: (count) => {
const mod10 = count % 10
const mod100 = count % 100
if (mod100 >= 11 && mod100 <= 19) return 'many'
if (mod10 === 1) return 'one'
if (mod10 >= 2 && mod10 <= 4) return 'few'
return 'many'
},
de: (count) => count === 1 ? 'one' : 'other',
}
// --- Переводы ---
const translations = {
en: {
greeting: 'Hello, {{name}}!',
farewell: 'Goodbye, {{name}}!',
items_one: '{{count}} item',
items_other: '{{count}} items',
price: 'Price: USD {{amount}}',
},
ru: {
greeting: 'Привет, {{name}}!',
farewell: 'До свидания, {{name}}!',
items_one: '{{count}} элемент',
items_few: '{{count}} элемента',
items_many: '{{count}} элементов',
price: 'Цена: {{amount}} ₽',
},
de: {
greeting: 'Hallo, {{name}}!',
farewell: 'Auf Wiedersehen, {{name}}!',
items_one: '{{count}} Element',
items_other: '{{count}} Elemente',
},
}
// --- I18n движок ---
function createI18nEngine(defaultLocale = 'en') {
let currentLocale = defaultLocale
// Интерполяция: заменить {{variable}} на значения
function interpolate(str, params) {
return str.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return params[key] !== undefined ? params[key] : match
})
}
// Функция перевода
function t(key, params = {}) {
const locale = currentLocale
const dict = translations[locale] || translations['en']
// Если передан count — ищем форму множественного числа
if (params.count !== undefined) {
const rule = pluralRules[locale] || pluralRules['en']
const form = rule(params.count)
const pluralKey = key + '_' + form
const str = dict[pluralKey] || dict[key + '_other'] || dict[key]
return str ? interpolate(str, params) : key
}
const str = dict[key]
return str ? interpolate(str, params) : key
}
return {
t,
setLocale(locale) {
if (!translations[locale]) {
console.warn('Локаль не найдена:', locale)
return
}
currentLocale = locale
console.log('Локаль изменена на:', locale)
},
getLocale() { return currentLocale },
getSupportedLocales() { return Object.keys(translations) },
}
}
// --- Тесты ---
const i18n = createI18nEngine('en')
const { t, setLocale, getLocale } = i18n
console.log('=== English ===')
console.log(t('greeting', { name: 'Alex' })) // Hello, Alex!
console.log(t('items', { count: 1 })) // 1 item
console.log(t('items', { count: 5 })) // 5 items
console.log('
=== Русский ===')
setLocale('ru')
console.log(t('greeting', { name: 'Алексей' })) // Привет, Алексей!
console.log(t('price', { amount: 999 })) // Цена: 999 ₽
// Проверка всех форм множественного числа
const counts = [1, 2, 5, 11, 21, 22, 25]
counts.forEach(n => console.log(n + ': ' + t('items', { count: n })))
console.log('
=== Deutsch ===')
setLocale('de')
console.log(t('greeting', { name: 'Hans' }))
console.log(t('items', { count: 1 }))
console.log(t('items', { count: 3 }))
console.log('
Текущая локаль:', getLocale())
console.log('Доступные локали:', i18n.getSupportedLocales())Создай React-приложение с переключателем языка. Используй Context API для хранения текущей локали и функции перевода t(). Компонент LanguageSwitcher позволяет переключаться между языками. Компонент Greeting отображает приветствие на выбранном языке.
В I18nProvider: используй translations[locale] для словаря и dict[key] для получения строки. В интерполяции проверяй params[varName]. Передай value в Provider. В LanguageSwitcher: onClick={() => setLocale(lang). В Greeting: используй t("greeting", { name }), t("welcome"), t("items", { count: itemCount }).