Цепочки постпроцессоров

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

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

Ключевая особенность механизма — разделение ответственности:

  • интерполяция отвечает за подстановку значений
  • детекторы языка — за выбор языка
  • постпроцессоры — за финальную обработку результата

Базовая модель работы постпроцессоров

При вызове t('key') i18next выполняет несколько стадий:

  1. Поиск ресурса перевода
  2. Выбор языка (fallback chain)
  3. Интерполяция значений
  4. Применение постпроцессоров
  5. Возврат результата

На шаге постобработки формируется последовательность обработчиков, заданных либо глобально, либо в параметрах вызова.

i18next.t('welcome', {
  postProcess: ['upperCase']
})

В этом случае строка проходит через постпроцессор upperCase.


Регистрация постпроцессора

Постпроцессор регистрируется через API addPostProcessor. Он должен реализовывать метод process.

import i18next from 'i18next';

const upperCasePostProcessor = {
  type: 'postProcessor',
  name: 'upperCase',

  process(value, key, options, translator) {
    return value.toUpperCase();
  }
};

i18next
  .use(upperCasePostProcessor)
  .init({
    resources: {
      en: {
        translation: {
          hello: 'hello world'
        }
      }
    }
  });

Здесь process получает:

  • value — переведённая строка
  • key — ключ перевода
  • options — параметры t()
  • translator — экземпляр переводчика

Цепочки постпроцессоров

Главная особенность механизма — возможность применять несколько постпроцессоров подряд.

i18next.t('price', {
  postProcess: ['currencyFormat', 'round', 'appendSymbol']
});

В этом случае результат проходит последовательно:

  1. currencyFormat — приводит число к локальному формату валюты
  2. round — округляет значение
  3. appendSymbol — добавляет символ валюты

Каждый следующий постпроцессор работает уже с результатом предыдущего.


Порядок выполнения и его значение

Порядок в массиве postProcess строго определяет цепочку обработки. Перестановка элементов может радикально изменить результат.

postProcess: ['trim', 'upperCase']

и

postProcess: ['upperCase', 'trim']

могут давать разные результаты в зависимости от реализации.

Например:

  • trim удаляет пробелы
  • upperCase изменяет регистр

Если сначала применить upperCase, а затем trim, поведение может стать менее предсказуемым при наличии локализационных пробелов или специальных символов.


Использование контекста и options

Постпроцессоры часто используют options для адаптации поведения.

const suffixPostProcessor = {
  name: 'suffix',
  type: 'postProcessor',

  process(value, key, options) {
    if (options.suffix) {
      return `${value}${options.suffix}`;
    }
    return value;
  }
};

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

i18next.t('file', {
  postProcess: ['suffix'],
  suffix: '.txt'
});

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


Условные постпроцессоры

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

const pluralAwareProcessor = {
  name: 'pluralAware',
  type: 'postProcessor',

  process(value, key, options) {
    if (options.count === 1) {
      return value;
    }
    return value + 's';
  }
};

Хотя i18next уже имеет встроенную поддержку множественных форм, постпроцессоры применяются для нестандартных сценариев, например:

  • кастомные грамматические правила
  • бизнес-логика отображения
  • специфические форматы домена

Вложенные цепочки и композиция

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

postProcess: [
  'sanitize',
  'markdownToHtml',
  'highlightVariables'
]

Типичная цепочка в реальных приложениях:

  • очистка строки от небезопасного контента
  • преобразование разметки
  • подсветка динамических значений

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


Повторное применение и идемпотентность

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

Проблемные случаи:

  • повторное добавление символов
  • многократное форматирование дат
  • повторная HTML-энкодинг обработка
process(value) {
  if (value.endsWith('€')) return value;
  return value + '€';
}

Взаимодействие с интерполяцией

Интерполяция выполняется до постпроцессоров, поэтому постпроцессоры работают уже с готовыми значениями.

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

Если перевод:

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

то постпроцессор получит уже строку:

Hello Alex

Это важно учитывать при проектировании цепочек, особенно если требуется анализ плейсхолдеров — их на этом этапе уже нет.


Асинхронные постпроцессоры

i18next допускает асинхронную обработку, но требует явного указания.

const asyncProcessor = {
  name: 'asyncAppend',
  type: 'postProcessor',

  process(value, key, options, translator, cb) {
    setTimeout(() => {
      cb(null, value + '!');
    }, 100);
  }
};

Использование асинхронных цепочек требует осторожности:

  • увеличивает задержку t()
  • усложняет предсказуемость порядка
  • требует поддержки callback-стиля или промис-обёрток

Применение в реальных сценариях

Постпроцессоры часто используются для:

Форматирования данных

  • валюты
  • даты
  • чисел

UI-трансформаций

  • uppercase/lowercase
  • добавление иконок
  • вставка HTML

Безопасности

  • sanitization HTML
  • фильтрация запрещённых символов

Бизнес-логики

  • кастомные грамматические правила
  • доменные трансформации текста

Глобальное применение постпроцессоров

Постпроцессоры можно подключать глобально, чтобы они применялись ко всем переводам:

i18next.init({
  postProcess: ['sanitize']
});

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


Ошибки и диагностика цепочек

Типичные проблемы:

  • неправильный порядок цепочки
  • потеря контекста options
  • конфликт нескольких постпроцессоров
  • повторная обработка одного и того же значения

Для диагностики часто добавляют логирующий постпроцессор:

const logger = {
  name: 'logger',
  type: 'postProcessor',

  process(value, key, options) {
    console.log('postProcess:', { value, key, options });
    return value;
  }
};

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


Композиционная модель обработки строки

Цепочки постпроцессоров в i18next фактически формируют композицию функций:

f3(f2(f1(value)))

где:

  • f1, f2, f3 — постпроцессоры
  • value — результат перевода

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