Локализация форматирования в приложениях на JavaScript в контексте
i18next опирается на разделение текстового перевода и представления
данных, зависящего от региональных правил. Перевод строк отвечает за
семантику, тогда как форматирование обеспечивает корректное отображение
чисел, дат, валют и других значений в соответствии с языковой средой,
заданной текущим lng.
i18next не ограничивается переводом ключей словаря. Встроенный
механизм интерполяции позволяет внедрять значения в переводимые строки,
а слой форматирования расширяет эту возможность, подключая
Intl API и пользовательские преобразователи. Это делает
возможным единообразное представление данных без дублирования логики в
каждом компоненте приложения.
Механизм интерполяции i18next использует шаблоны вида:
t('price', { value: 1234.56 })
И строку перевода:
{
"price": "{{value}}"
}
Без дополнительной настройки значение просто вставляется как есть. Однако интерполяция поддерживает форматирование через расширенный синтаксис:
t('price', { value: 1234.56 })
{
"price": "{{value, currency}}"
}
Здесь currency — идентификатор форматтера, который
обрабатывает значение до подстановки в строку.
Основой локализованного представления чисел является
Intl.NumberFormat. Он учитывает:
Интеграция с i18next обычно строится через регистрацию форматтера:
import i18next from 'i18next';
i18next.init({
lng: 'en',
resources: {
en: {
translation: {
price: '{{value, currency}}'
}
}
}
});
Добавление форматтера:
i18next.services.formatter.add('currency', (value, lng) => {
return new Intl.NumberFormat(lng, {
style: 'currency',
currency: lng === 'ru' ? 'RUB' : 'USD'
}).format(value);
});
При смене языка формат автоматически адаптируется:
1,234.56 $ для en1 234,56 ₽ для ruКлючевым моментом является использование lng внутри
форматтера, так как именно он определяет локаль.
Работа с датами требует учета календарных форматов, порядка
компонентов и локальных названий месяцев и дней недели. Встроенный
Intl.DateTimeFormat решает эти задачи:
i18next.services.formatter.add('date', (value, lng, options) => {
return new Intl.DateTimeFormat(lng, {
year: 'numeric',
month: 'long',
day: 'numeric',
...options
}).format(new Date(value));
});
Использование в переводах:
{
"created": "Создано: {{date, date}}"
}
Вызов:
t('created', { date: Date.now() })
Поведение зависит от локали:
29 May 2026 в английской локали29 мая 2026 г. в русской локалиФорматтер может принимать дополнительные параметры:
t('created', {
date: Date.now(),
formatParams: {
date: { weekday: 'long' }
}
})
Внутри i18next форматирование реализуется через
i18next.services.formatter. Он представляет собой слой,
который:
Регистрация базируется на имени:
i18next.services.formatter.add('uppercase', (value) => {
return String(value).toUpperCase();
});
Использование:
{
"welcome": "{{name, uppercase}}"
}
Такой подход позволяет создавать доменно-специфичные преобразования без изменения переводов.
Форматтеры могут принимать дополнительные аргументы через интерполяцию:
{
"bytes": "{{value, bytes}}"
}
i18next.services.formatter.add('bytes', (value, lng, options) => {
const units = ['B', 'KB', 'MB', 'GB'];
let i = 0;
let val = value;
while (val >= 1024 && i < units.length - 1) {
val /= 1024;
i++;
}
return `${val.toFixed(1)} ${units[i]}`;
});
Расширенные параметры позволяют учитывать контекст:
{
"download": "{{size, bytes}} файл"
}
Ключевая особенность i18next — синхронизация языка перевода и языка
форматирования. При смене lng:
i18next.changeLanguage('ru');
все форматтеры получают обновленный локальный контекст. Это важно, поскольку:
Форматтеры не хранят состояние языка, а получают его как аргумент, что исключает рассинхронизацию.
При отсутствии поддержки конкретной локали Intl
использует fallback-цепочку. В i18next это соответствует
fallbackLng:
i18next.init({
lng: 'kk',
fallbackLng: 'en'
});
Если форматтер получает локаль, которую Intl не
поддерживает напрямую, он автоматически переходит к ближайшему
доступному варианту.
Это важно для чисел и дат, поскольку:
i18next допускает цепочки преобразований через вложенную интерполяцию:
{
"report": "{{value, number}} ({{value, percent}})"
}
Хотя такой подход используется ограниченно, он позволяет отображать одно значение в разных представлениях без дублирования вычислений.
Форматтер может учитывать не только значение и язык, но и дополнительные параметры:
i18next.services.formatter.add('status', (value, lng, options) => {
if (options.context === 'api') {
return `[API] ${value}`;
}
return value;
});
В переводе:
{
"state": "{{state, status}}"
}
Вызов:
t('state', {
state: 'active',
formatParams: {
state: { context: 'api' }
}
});
Такой механизм позволяет разделять представление данных для разных слоев приложения.
Использование Intl может быть затратным при частых
вызовах, особенно при рендеринге списков. Оптимизация обычно строится
вокруг:
Intl.NumberFormat и
Intl.DateTimeFormatПример кэширования:
const cache = new Map();
function getFormatter(lng) {
if (!cache.has(lng)) {
cache.set(lng, new Intl.NumberFormat(lng));
}
return cache.get(lng);
}
Встроенные механизмы i18next также поддерживают повторное использование форматтеров внутри жизненного цикла приложения.
Некоторые локали используют альтернативные наборы цифр или
нестандартные разделители. Intl учитывает:
i18next не преобразует эти данные самостоятельно, а полагается на системный слой форматирования, что обеспечивает согласованность с браузерной или Node.js реализацией.
Форматирование в i18next часто используется как последний шаг перед отображением данных. Типичный поток:
t()Это позволяет отделить бизнес-логику от представления и исключить дублирование форматирования в разных компонентах интерфейса.
Форматтеры должны учитывать некорректные входные данные:
null и undefinedТипичная защита:
i18next.services.formatter.add('safeNumber', (value, lng) => {
const num = Number(value);
if (Number.isNaN(num)) return '0';
return new Intl.NumberFormat(lng).format(num);
});
Это предотвращает разрыв отображения при ошибках данных на уровне приложения.
Хотя i18next не занимается напрямую визуальным направлением текста, локаль влияет на форматирование чисел и дат в сочетании с RTL-языками. В таких сценариях:
Форматирование должно учитывать контекст рендеринга, особенно в UI-компонентах, где RTL включается на уровне документа.