Formatter плагины

В i18next форматирование значений является частью механизма интерполяции и позволяет преобразовывать данные непосредственно в строках переводов. Форматтеры применяются на этапе подстановки переменных и отвечают за приведение чисел, дат, строк и других типов данных к локализованному виду без усложнения логики приложения.

Основная идея заключается в разделении перевода и отображения данных: ключи переводов содержат шаблоны, а форматтеры определяют правила преобразования значений.


Интерполяция как основа форматирования

Интерполяция в i18next работает через плейсхолдеры:

t('welcome_user', { name: 'Alex' })

Шаблон перевода:

{
  "welcome_user": "Hello, {{name}}"
}

Форматтеры расширяют этот механизм:

{
  "price": "Price: {{value, currency}}"
}

Здесь currency — форматтер, применяемый к value.


Базовый механизм форматирования

Внутри i18next форматтеры вызываются через функцию:

format: (value, format, lng, options) => {}

Где:

  • value — исходное значение
  • format — имя или тип форматирования
  • lng — текущий язык
  • options — дополнительные параметры интерполяции

Простейшая реализация:

import i18next from 'i18next';

i18next.init({
  interpolation: {
    format: (value, format, lng) => {
      if (format === 'uppercase') return String(value).toUpperCase();
      return value;
    }
  }
});

Использование:

i18next.t('key', { name: 'john' });
// при шаблоне {{name, uppercase}} → JOHN

Встроенные форматтеры через Intl API

Часто форматирование делегируется встроенному Intl.

Форматирование чисел

format: (value, format, lng) => {
  if (format === 'number') {
    return new Intl.NumberFormat(lng).format(value);
  }
  return value;
}

Шаблон:

{
  "users": "Users: {{count, number}}"
}

Валютное форматирование

if (format === 'currency') {
  return new Intl.NumberFormat(lng, {
    style: 'currency',
    currency: 'USD'
  }).format(value);
}

Более гибкий вариант через options:

format: (value, format, lng, options) => {
  if (format === 'currency') {
    return new Intl.NumberFormat(lng, {
      style: 'currency',
      currency: options?.currency || 'USD'
    }).format(value);
  }
  return value;
}

Форматирование дат

if (format === 'date') {
  return new Intl.DateTimeFormat(lng, {
    year: 'numeric',
    month: 'long',
    day: '2-digit'
  }).format(value);
}

Использование:

{
  "today": "Today is {{date, date}}"
}

Цепочки форматирования

i18next поддерживает последовательное применение форматов:

{
  "value": "{{amount, number, currency}}"
}

Логика обработки:

  1. number — базовое преобразование
  2. currency — дополнительное оформление

Реализация цепочки:

format: (value, format, lng) => {
  const formats = format.split(',');

  return formats.reduce((acc, f) => {
    const fmt = f.trim();

    if (fmt === 'number') {
      return new Intl.NumberFormat(lng).format(acc);
    }

    if (fmt === 'currency') {
      return new Intl.NumberFormat(lng, {
        style: 'currency',
        currency: 'USD'
      }).format(acc);
    }

    return acc;
  }, value);
}

Плагинная архитектура форматтеров

В i18next форматтеры часто выносятся в отдельные плагины или модули. Это позволяет:

  • переиспользовать форматирование между проектами
  • подключать ICU или Moment.js
  • изолировать бизнес-логику форматирования

Пример подключения кастомного форматтера

const customFormatter = {
  type: 'formatter',
  init: () => {},
  add(name, fn) {
    this[name] = fn;
  }
};

Регистрация:

i18next.init({
  interpolation: {
    format: customFormatter
  }
});

ICU форматирование через i18next-icu

Одним из наиболее мощных подходов является использование ICU MessageFormat через i18next-icu.

ICU позволяет выразить:

  • множественные формы
  • условия
  • вложенные структуры

Пример шаблона:

{
  "items": "{count, plural, one {# item} other {# items}}"
}

Интеграция:

import ICU from 'i18next-icu';
import i18next from 'i18next';

i18next.use(ICU).init({
  lng: 'en'
});

ICU полностью заменяет необходимость ручных форматтеров для сложных случаев.


Локализация через форматирование без перевода

Форматтеры часто используются для значений, не требующих перевода:

  • даты
  • числа
  • проценты
  • единицы измерения

Пример процентов:

if (format === 'percent') {
  return new Intl.NumberFormat(lng, {
    style: 'percent'
  }).format(value);
}

Контекст языка и влияние локали

Форматтеры получают lng, что позволяет учитывать особенности языка:

if (lng === 'ru') {
  return new Intl.NumberFormat('ru-RU').format(value);
}

Это особенно важно для:

  • разделителей тысяч
  • порядка даты
  • валютных символов

Безопасность и контроль вывода

Форматтеры часто становятся точкой контроля над данными:

  • предотвращение некорректных строк
  • нормализация числовых значений
  • защита от неожиданного типа данных

Пример проверки:

if (typeof value !== 'number') {
  return value;
}

Производительность форматтеров

Форматирование через Intl может быть затратным при частых вызовах. Оптимизация включает:

Кэширование форматтеров

const cache = new Map();

function getNumberFormatter(lng) {
  if (!cache.has(lng)) {
    cache.set(lng, new Intl.NumberFormat(lng));
  }
  return cache.get(lng);
}

Использование:

if (format === 'number') {
  return getNumberFormatter(lng).format(value);
}

Интеграция с React и UI слоями

В React-приложениях форматтеры используются внутри переводов:

const { t } = useTranslation();

t('price', { value: 1200 });

Шаблон:

{
  "price": "{{value, currency}}"
}

Форматирование остаётся в i18next, а UI получает уже готовую строку.


Кастомные доменные форматтеры

В сложных системах вводятся предметные форматтеры:

Форматирование веса

if (format === 'weight') {
  return `${value} kg`;
}

Форматирование статусов

if (format === 'status') {
  const map = {
    active: 'Active',
    disabled: 'Disabled'
  };

  return map[value] || value;
}

Расширение через сторонние плагины

Распространённые подходы:

  • ICU форматирование (i18next-icu)
  • Moment.js / Day.js интеграции
  • кастомные format pipelines
  • postprocessors (альтернативный слой обработки)

Форматтеры часто комбинируются с postprocessor-цепочкой для сложной трансформации текста.


Разделение ответственности в архитектуре

Корректная организация форматтеров обычно предполагает:

  • переводчики управляют текстом
  • форматтеры управляют представлением данных
  • бизнес-логика не смешивается с UI
  • локализация не содержит вычислительных операций

Такое разделение упрощает масштабирование интернационализации и снижает связанность модулей приложения.