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

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

Механизм интерполяции и роль форматтеров

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

i18next.t('welcome', { name: 'Alex' });

Шаблон:

{
  "welcome": "Hello, {{name}}"
}

Добавление форматирования расширяет эту модель:

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

Здесь currency — идентификатор форматтера, который обрабатывает значение перед подстановкой.

Базовая настройка кастомного форматтера

Ключевой механизм — функция interpolation.format, определяемая при инициализации i18next:

import i18next from 'i18next';

i18next.init({
  interpolation: {
    format: (value, format, lng) => {
      return value;
    }
  }
});

Параметры функции:

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

Именно format определяет логику преобразования значения.

Условная маршрутизация форматтеров

Типичный подход — использование switch для обработки разных форматов:

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

        case 'lowercase':
          return String(value).toLowerCase();

        case 'capitalize':
          return String(value).charAt(0).toUpperCase() + String(value).slice(1);

        default:
          return value;
      }
    }
  }
});

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

{
  "title": "{{text, uppercase}}"
}

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

Для числовых значений применяется Intl.NumberFormat, обеспечивающий локализацию валют, процентов и чисел.

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

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

      return value;
    }
  }
});

Пример использования:

{
  "balance": "Balance: {{amount, currency}}"
}

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

Работа с датами строится на Intl.DateTimeFormat:

i18next.init({
  interpolation: {
    format: (value, format, lng) => {
      if (value instanceof Date) {
        if (format === 'short') {
          return new Intl.DateTimeFormat(lng).format(value);
        }

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

      return value;
    }
  }
});

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

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

Поддержка цепочек форматирования

Несмотря на отсутствие встроенной композиции форматтеров, логика может быть расширена вручную:

i18next.init({
  interpolation: {
    format: (value, format) => {
      const formats = format.split(',');

      return formats.reduce((acc, f) => {
        switch (f.trim()) {
          case 'trim':
            return String(acc).trim();
          case 'uppercase':
            return String(acc).toUpperCase();
          default:
            return acc;
        }
      }, value);
    }
  }
});

Пример:

{
  "label": "{{text, trim, uppercase}}"
}

Форматирование сложных объектов

Форматтеры могут работать не только со строками и числами, но и со структурами данных:

i18next.init({
  interpolation: {
    format: (value, format) => {
      if (format === 'json') {
        return JSON.stringify(value, null, 2);
      }

      if (format === 'length') {
        return Array.isArray(value) ? value.length : 0;
      }

      return value;
    }
  }
});

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

{
  "items": "Total items: {{list, length}}"
}

Обработка языка и локализации внутри форматтеров

Параметр lng позволяет адаптировать форматирование под текущую локаль:

i18next.init({
  interpolation: {
    format: (value, format, lng) => {
      if (format === 'date') {
        return new Intl.DateTimeFormat(lng === 'ru' ? 'ru-RU' : 'en-US').format(value);
      }

      return value;
    }
  }
});

Такой подход обеспечивает согласованность локализации между переводами и форматированием.

Ограничения и особенности архитектуры

Механизм форматтеров встроен в слой интерполяции, поэтому:

  • форматирование выполняется на этапе рендера строки
  • отсутствует асинхронная обработка
  • результаты должны быть синхронными
  • форматтер не должен изменять исходные данные

Часто используемая практика — вынос сложной логики в отдельные утилиты:

const formatters = {
  currency: (value, lng) =>
    new Intl.NumberFormat(lng, { style: 'currency', currency: 'USD' }).format(value),

  uppercase: (value) => String(value).toUpperCase()
};

i18next.init({
  interpolation: {
    format: (value, format, lng) => {
      const fn = formatters[format];
      return fn ? fn(value, lng) : value;
    }
  }
});

Интеграция с React и другими фреймворками

В связке с UI-фреймворками форматтеры сохраняют ту же модель работы, так как выполняются на уровне i18next:

const price = i18next.t('price', { amount: 1200 });

Перевод:

{
  "price": "{{amount, currency}}"
}

Форматирование остаётся централизованным и не зависит от слоя представления.

Расширение системы форматтеров

При росте приложения форматтеры часто структурируются как модуль:

export const createFormatters = (lng) => ({
  dateShort: (value) =>
    new Intl.DateTimeFormat(lng).format(value),

  numberCompact: (value) =>
    new Intl.NumberFormat(lng, { notation: 'compact' }).format(value),

  boolean: (value) => (value ? 'true' : 'false')
});

Подключение:

i18next.init({
  interpolation: {
    format: (value, format, lng) => {
      const fns = createFormatters(lng);
      return fns[format] ? fns[format](value) : value;
    }
  }
});

Такой подход формирует из форматтеров отдельный слой представления данных, изолированный от бизнес-логики приложения.