Обработка чисел и валют в i18next строится вокруг механизма
интерполяции и форматирования значений, основанного на возможностях
Intl в JavaScript. Библиотека не навязывает собственный
форматтер, а предоставляет расширяемую систему, в которую можно встроить
локализованное отображение чисел, процентов и денежных единиц.
Ключевой элемент — интерполяция:
i18next.t('key', { value: 1234.56 })
Шаблон перевода:
{
"key": "Значение: {{value}}"
}
Без дополнительной настройки число будет выведено как есть, что не учитывает локаль. Для корректного отображения используется форматирование.
Стандартный способ локализации чисел в i18next реализуется через кастомный форматтер.
Инициализация i18next с поддержкой форматирования:
import i18next from 'i18next';
i18next.init({
lng: 'ru',
resources: {
ru: {
translation: {
price: 'Цена: {{value, number}}',
amount: 'Количество: {{value, number}}'
}
}
},
interpolation: {
format: (value, format, lng) => {
if (format === 'number') {
return new Intl.NumberFormat(lng).format(value);
}
return value;
}
}
});
Пример использования:
i18next.t('price', { value: 1234567.89 });
Результат для ru:
Цена: 1 234 567,89
В i18next перевод должен оставаться независимым от форматирования. Форматирование внедряется через интерполяцию.
Шаблон:
{
"balance": "Баланс: {{amount, number}}"
}
Код:
i18next.t('balance', { amount: 10000 });
Такой подход позволяет менять локаль без изменения текстов.
Поддержка валют реализуется через параметр currency в
Intl.NumberFormat.
Расширение форматтера:
i18next.init({
lng: 'en',
interpolation: {
format: (value, format, lng) => {
if (format?.startsWith('currency')) {
const [, currency] = format.split(':');
return new Intl.NumberFormat(lng, {
style: 'currency',
currency
}).format(value);
}
if (format === 'number') {
return new Intl.NumberFormat(lng).format(value);
}
return value;
}
}
});
Шаблоны:
{
"price_usd": "Стоимость: {{value, currency:USD}}",
"price_eur": "Стоимость: {{value, currency:EUR}}"
}
Использование:
i18next.t('price_usd', { value: 1999.99 });
i18next.t('price_eur', { value: 1999.99 });
Результаты зависят от локали:
$1,999.991.999,99 €1 999,99 $ (в зависимости от настроек локали)Intl.NumberFormat позволяет управлять точностью
чисел.
Расширенный форматтер:
if (format === 'number_fixed') {
return new Intl.NumberFormat(lng, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(value);
}
Пример шаблона:
{
"weight": "Вес: {{value, number_fixed}} кг"
}
Результат:
Вес: 12,00 кг
Процентное форматирование реализуется через
style: 'percent'.
Добавление в форматтер:
if (format === 'percent') {
return new Intl.NumberFormat(lng, {
style: 'percent',
minimumFractionDigits: 0,
maximumFractionDigits: 2
}).format(value);
}
Использование:
{
"progress": "Прогресс: {{value, percent}}"
}
Входное значение должно быть в диапазоне 0–1:
i18next.t('progress', { value: 0.42 });
Результат:
Прогресс: 42%
В ряде систем требуется отображение не только символа, но и ISO-кода валюты.
Расширенный вариант:
if (format?.startsWith('currencyCode')) {
const [, currency] = format.split(':');
return new Intl.NumberFormat(lng, {
style: 'currency',
currency,
currencyDisplay: 'code'
}).format(value);
}
Шаблон:
{
"invoice": "Счёт: {{value, currencyCode:USD}}"
}
Результат:
Счёт: 1,999.99 USD
Разные локали используют разные разделители:
Пример:
new Intl.NumberFormat('de-DE').format(1234567.89);
Результат:
1.234.567,89
В i18next это достигается через передачу lng в
форматтер, что обеспечивает автоматическое соответствие региональным
правилам.
Форматы могут комбинироваться через расширение логики:
if (format?.startsWith('currency_fixed')) {
const [, currency] = format.split(':');
return new Intl.NumberFormat(lng, {
style: 'currency',
currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(value);
}
Шаблон:
{
"total": "Итого: {{value, currency_fixed:EUR}}"
}
Форматтеры в i18next выполняются синхронно и должны быть детерминированными. Любая зависимость от внешнего состояния приводит к непредсказуемым результатам при серверном рендеринге.
Правильный подход — использование чистых функций:
format: (value, format, lng) => {
const formatter = new Intl.NumberFormat(lng);
return formatter.format(value);
}
При SSR важно фиксировать локаль:
i18next.init({
lng: 'ru-RU',
interpolation: {
format: (value, format, lng) => {
return new Intl.NumberFormat(lng).format(value);
}
}
});
На сервере и клиенте должна использоваться одинаковая локаль для исключения расхождений в отображении валют и чисел.
В экосистеме i18next существуют расширения, упрощающие работу с форматированием:
Общий принцип остаётся неизменным: значения проходят через интерполяцию, а форматирование определяется отдельным слоем логики.
JavaScript использует IEEE 754, что приводит к неточностям:
0.1 + 0.2 // 0.30000000000000004
Форматирование через Intl.NumberFormat скрывает эти
особенности:
new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2
}).format(0.1 + 0.2);
Результат:
0.30
В крупных приложениях форматтеры выносятся в отдельный модуль:
export const formatters = {
number: (value, lng) =>
new Intl.NumberFormat(lng).format(value),
currency: (value, lng, currency) =>
new Intl.NumberFormat(lng, {
style: 'currency',
currency
}).format(value)
};
И подключаются в i18next:
format: (value, format, lng) => {
if (format === 'number') return formatters.number(value, lng);
if (format?.startsWith('currency')) {
const [, currency] = format.split(':');
return formatters.currency(value, lng, currency);
}
return value;
}
Если формат не распознан, значение возвращается без изменений. Это обеспечивает устойчивость системы при ошибках в ключах перевода или неправильных параметрах интерполяции:
return value;