Sprintf постпроцессор

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

Постпроцессор sprintf применяется на финальном этапе формирования строки перевода — после того, как ключ перевода найден и базовая интерполяция (если она используется) уже выполнена. Это позволяет отделить получение перевода от его форматирования и применять строгие, позиционные или типизированные шаблоны.

Архитектура postProcessor в i18next

Механизм постобработки встроен в цепочку обработки переводов:

  1. Поиск ключа перевода
  2. Интерполяция значений (если включена)
  3. Применение postProcessors (в том числе sprintf)
  4. Возврат итоговой строки

PostProcessor — это функция, которая принимает строку и параметры и возвращает модифицированную строку.

Подключение sprintf postprocessor

Для использования требуется отдельный пакет:

npm install i18next-sprintf-postprocessor

Далее он подключается и регистрируется:

import i18next from 'i18next';
import sprintfPostProcessor from 'i18next-sprintf-postprocessor';

i18next
  .use(sprintfPostProcessor)
  .init({
    lng: 'ru',
    resources: {
      ru: {
        translation: {
          welcome: 'Привет, %s!',
        },
      },
    },
  });

Базовый синтаксис sprintf

Форматирование строится на шаблонах:

  • %s — строка
  • %d — целое число
  • %f — число с плавающей точкой
  • %0.2f — число с фиксированной точностью
  • %1$s, %2$s — позиционные аргументы

Простая подстановка значений

i18next.t('welcome', 'Иван');

Перевод:

Привет, Иван!

Шаблон:

{
  "welcome": "Привет, %s!"
}

Множественные параметры

sprintf позволяет передавать массив аргументов:

i18next.t('userInfo', ['Иван', 30]);

Шаблон:

{
  "userInfo": "Имя: %s, возраст: %d"
}

Результат:

Имя: Иван, возраст: 30

Позиционные аргументы

Позиционные спецификаторы позволяют менять порядок вставки без изменения аргументов:

{
  "order": "Файл %2$s загружен пользователем %1$s"
}
i18next.t('order', ['Иван', 'report.pdf']);

Результат:

Файл report.pdf загружен пользователем Иван

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

Поддерживается контроль точности:

{
  "price": "Цена: %0.2f ₽"
}
i18next.t('price', [199.956]);

Результат:

Цена: 199.96 ₽

Использование с postProcessor цепочкой

PostProcessor можно комбинировать с другими механизмами:

i18next.t('key', {
  postProcess: 'sprintf',
  sprintf: ['Иван', 42],
});

Это позволяет явно управлять порядком обработки.

Отличие от интерполяции i18next

Встроенная интерполяция:

{
  "welcome": "Привет, {{name}}"
}
i18next.t('welcome', { name: 'Иван' });

sprintf:

{
  "welcome": "Привет, %s"
}
i18next.t('welcome', 'Иван');

Ключевые различия:

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

Использование в реальных сценариях

Форматирование таблиц и логов

{
  "log": "[%s] %s: %d ошибок"
}
i18next.t('log', ['INFO', 'Сервер', 5]);
[INFO] Сервер: 5 ошибок

Финансовые данные

{
  "balance": "Баланс: %0.2f USD"
}
i18next.t('balance', [1024.5]);
Баланс: 1024.50 USD

Составные сообщения

{
  "report": "Пользователь %s выполнил %d операций за %0.1f секунд"
}
i18next.t('report', ['Иван', 12, 3.456]);
Пользователь Иван выполнил 12 операций за 3.5 секунд

Ограничения sprintf постпроцессора

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

Поведение при отсутствии аргументов

Если аргументы не переданы:

i18next.t('welcome');

Результат зависит от шаблона:

{
  "welcome": "Привет, %s!"
}

Вывод:

Привет, %s!

То есть строка остаётся неформатированной, без ошибок выполнения.

Комбинация с другими postProcessor

sprintf может использоваться вместе с кастомными постпроцессорами:

i18next
  .use(sprintfPostProcessor)
  .use(customPostProcessor);

Порядок применения влияет на итоговый результат, так как каждый postProcessor получает уже изменённую строку.

Типичные ошибки использования

  • передача объекта вместо массива аргументов
  • несоответствие количества %s и параметров
  • смешивание интерполяции и sprintf в одном ключе
  • использование %d для строковых значений

Пример ошибки:

i18next.t('user', { sprintf: { name: 'Иван' } });

Правильно:

i18next.t('user', ['Иван']);

Поведение при множественных вызовах

Каждый вызов t() обрабатывает строку независимо, postProcessor не сохраняет состояние между вызовами, что обеспечивает детерминированность результата при одинаковых входных данных.