i18n (internationalization) — процесс подготовки
приложения к поддержке нескольких языков, регионов и культурных
особенностей без изменения исходного кода. Число 18
обозначает количество букв между i и n в слове
internationalization.
В веб-разработке интернационализация включает:
LTR и
RTL);i18n не равен переводу. Перевод — лишь часть локализации. Полноценная интернационализация затрагивает архитектуру приложения, структуру компонентов и работу с пользовательскими данными.
Подготовка приложения к локализации:
Адаптация под конкретный язык или регион:
Пример:
| Регион | Формат даты | Валюта |
|---|---|---|
| США | 12/31/2026 |
USD |
| Германия | 31.12.2026 |
EUR |
| Казахстан | 31.12.2026 |
KZT |
Современные веб-приложения редко ограничиваются одной страной. Даже внутренние корпоративные системы часто используются международными командами.
Без i18n возникают проблемы:
Интернационализация решает эти проблемы на уровне архитектуры.
Вместо:
<button>Save</button>
используется система сообщений:
<button>{intl.formatMessage({ id: 'button.save' })}</button>
Разные страны используют разные разделители:
| Локаль | Число |
|---|---|
en-US |
1,234.56 |
de-DE |
1.234,56 |
fr-FR |
1 234,56 |
Пример:
new Intl.NumberFormat('de-DE').format(1234.56)
Результат:
1.234,56
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(1000)
Результат:
$1,000.00
Для Казахстана:
new Intl.NumberFormat('kk-KZ', {
style: 'currency',
currency: 'KZT'
}).format(1000)
new Intl.DateTimeFormat('ru-RU').format(new Date())
Пример результата:
28.05.2026
Английский:
1 file
2 files
Русский:
1 файл
2 файла
5 файлов
Правила множественного числа различаются между языками. Простая конкатенация строк не подходит.
Плохой пример:
const text = count + ' files uploaded'
Для русского языка такая конструкция не работает.
Правильный подход:
intl.formatMessage(
{
id: 'files.uploaded',
defaultMessage:
'{count, plural, one {# файл} few {# файла} many {# файлов}}'
},
{ count }
)
Плохой пример:
alert('User created')
Правильный:
alert(
intl.formatMessage({
id: 'user.created'
})
)
Плохая практика:
const locale = 'en'
Лучше:
const locale = navigator.language
Основа большинства i18n-библиотек — встроенный API
Intl.
Основные возможности:
| API | Назначение |
|---|---|
Intl.NumberFormat |
Форматирование чисел |
Intl.DateTimeFormat |
Форматирование дат |
Intl.RelativeTimeFormat |
Относительное время |
Intl.ListFormat |
Форматирование списков |
Intl.PluralRules |
Правила множественного числа |
const formatter = new Intl.NumberFormat('ru-RU')
formatter.format(1234567.89)
Результат:
1 234 567,89
new Intl.NumberFormat('en-US', {
style: 'percent'
}).format(0.56)
Результат:
56%
const formatter = new Intl.DateTimeFormat('ru-RU', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
formatter.format(new Date())
Результат:
28 мая 2026 г.
const rtf = new Intl.RelativeTimeFormat('ru', {
numeric: 'auto'
})
rtf.format(-1, 'day')
Результат:
вчера
const plural = new Intl.PluralRules('ru-RU')
plural.select(5)
Результат:
many
FormatJS — набор библиотек для интернационализации Javascript-приложений.
Основные задачи:
FormatJS построен поверх API Intl.
| Пакет | Назначение |
|---|---|
react-intl |
i18n для React |
intl-messageformat |
ICU MessageFormat |
@formatjs/cli |
CLI-инструменты |
@formatjs/intl |
polyfill и утилиты |
Обычно структура выглядит так:
src/
├── locales/
│ ├── en.json
│ ├── ru.json
│ └── kk.json
│
├── i18n/
│ └── config.js
│
└── components/
FormatJS использует descriptors:
{
id: 'app.title',
defaultMessage: 'Dashboard'
}
| Поле | Назначение |
|---|---|
id |
уникальный идентификатор |
defaultMessage |
текст по умолчанию |
description |
описание для переводчика |
FormatJS основан на стандарте ICU.
Пример:
Hello {name}
Подстановка:
intl.formatMessage(
{
id: 'greeting',
defaultMessage: 'Hello {name}'
},
{
name: 'John'
}
)
User {name} has {count} messages
Использование:
intl.formatMessage(
{
id: 'messages'
},
{
name: 'Alex',
count: 10
}
)
{count, plural,
one {# file}
other {# files}
}
Для русского:
{count, plural,
one {# файл}
few {# файла}
many {# файлов}
}
Позволяют выбирать текст по условию.
{gender, select,
male {He}
female {She}
other {They}
}
Today is {date, date, short}
Price: {price, number, ::currency/USD}
react-intl — основной пакет FormatJS для React.
Установка:
npm install react-intl
Главный провайдер локализации.
<IntlProvider locale="ru" messages={messages}>
<App />
</IntlProvider>
Компонент для отображения перевода.
<FormattedMessage
id="app.welcome"
defaultMessage="Welcome"
/>
<FormattedMessage
id="user.greeting"
defaultMessage="Hello, {name}"
values={{
name: 'Alex'
}}
/>
<FormattedNumber value={1000} />
<FormattedNumber
value={1000}
style="currency"
currency="USD"
/>
<FormattedDate
value={new Date()}
year="numeric"
month="long"
day="2-digit"
/>
<FormattedRelativeTime
value={-1}
unit="day"
/>
Хук предоставляет доступ к API.
const intl = useIntl()
Пример:
const title = intl.formatMessage({
id: 'dashboard.title'
})
Позволяет централизовать сообщения.
const messages = defineMessages({
title: {
id: 'app.title',
defaultMessage: 'Dashboard'
}
})
{
"app.title": "Dashboard"
}
{
"app.title": "Панель управления"
}
const messages = await import(`./locales/${locale}.json`)
Позволяет уменьшить размер bundle.
async function loadLocale(locale) {
return import(`./locales/${locale}.json`)
}
navigator.language
navigator.languages
Если перевод отсутствует:
locale = 'ru'
fallback = 'en'
Некоторые языки используют направление справа налево:
Поддержка:
<html dir="rtl">
Переводы могут быть длиннее оригинала.
Пример:
| Английский | Немецкий |
|---|---|
| Save | Änderungen speichern |
Интерфейс должен учитывать это.
FormatJS CLI позволяет автоматически извлекать строки.
Пример:
formatjs extract "src/**/*.{js,jsx,ts,tsx}"
formatjs compile-folder locales compiled-locales
Плохо:
id: 'Save button text'
Хорошо:
id: 'button.save'
Плохо:
id: 'title'
Хорошо:
id: 'dashboard.title'
Плохо:
'Hello ' + name
Хорошо:
Hello {name}
i18n может влиять на производительность:
Плохой вариант:
new Intl.NumberFormat('ru-RU')
при каждом рендере.
Лучше:
const formatter = useMemo(
() => new Intl.NumberFormat('ru-RU'),
[]
)
Старые браузеры могут не поддерживать часть API.
FormatJS предоставляет polyfills:
npm install @formatjs/intl-pluralrules
При SSR локаль должна быть известна заранее.
Пример:
<IntlProvider
locale={locale}
messages={messages}
>
<App />
</IntlProvider>
Для SEO важно:
lang;Пример:
<html lang="ru">
Самый распространённый вариант.
Преимущества:
Переводы хранятся во внешней системе:
Пример структуры:
auth.login.title
auth.login.button
dashboard.header.title
Преимущества:
При удалении сообщений появляются:
Обычно используются:
Примеры:
| Локаль | Значение |
|---|---|
en |
английский |
en-US |
английский США |
en-GB |
английский Великобритания |
ru-RU |
русский |
kk-KZ |
казахский Казахстан |
Стандарт кодов локалей:
language-REGION
Пример:
kk-KZ
Пример:
setLocale('ru')
Часто используется Context API:
<LocaleContext.Provider value={locale}>
Пример:
/ru/dashboard
/en/dashboard
/kk/dashboard
new Intl.ListFormat('ru').format([
'JavaScript',
'React',
'FormatJS'
])
Результат:
JavaScript, React и FormatJS
new Intl.NumberFormat('ru', {
style: 'unit',
unit: 'kilometer'
}).format(10)
Проверяются:
Техника искусственного удлинения текста.
Пример:
[Šávíñĝ…]
Помогает выявлять проблемы интерфейса.
Современные проекты автоматизируют процесс:
Плохо:
Click <b>here</b>
Хорошо:
profile.settings.title
Плохо:
saveButtonText
saveLabel
saveCaption
Это помогает:
В крупных приложениях появляются:
Поэтому архитектура i18n должна проектироваться заранее.
FormatJS стал стандартным решением для React-экосистемы благодаря:
Intl;