Разработка кастомных постпроцессоров

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

Постпроцессоры выполняются после:

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

И до финального возврата строки потребителю.


Контракт постпроцессора

Кастомный постпроцессор в i18next реализуется через регистрацию объекта с определённым интерфейсом:

  • name — уникальное имя постпроцессора
  • type — тип регистрации (обычно не требуется явно задавать)
  • process — функция преобразования строки

Сигнатура функции обработки:

process(value, key, options, translator)

Где:

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

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

Механизм регистрации осуществляется через addPostProcessor:

import i18next from "i18next";

const reversePostProcessor = {
  name: "reverse",

  process(value) {
    return value.split("").reverse().join("");
  }
};

i18next.addPostProcessor(reversePostProcessor);

После регистрации постпроцессор становится доступным в цепочке postProcess.


Подключение постпроцессора к переводу

Использование постпроцессора задаётся через опцию postProcess:

i18next.t("welcome_message", {
  postProcess: "reverse"
});

Поддерживается указание массива, что формирует цепочку обработки:

i18next.t("welcome_message", {
  postProcess: ["trim", "uppercase"]
});

Порядок выполнения соответствует порядку элементов массива.


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

При наличии нескольких зарегистрированных постпроцессоров i18next последовательно прогоняет значение через каждый из них.

Модель обработки:

raw translation
  → interpolation
  → postProcessor[0]
  → postProcessor[1]
  → ...
  → result

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


Изменение регистра символов

Типовой пример постпроцессора — приведение строки к верхнему регистру:

const upperCasePostProcessor = {
  name: "uppercase",

  process(value) {
    return typeof value === "string"
      ? value.toUpperCase()
      : value;
  }
};

i18next.addPostProcessor(upperCasePostProcessor);

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

i18next.t("status_ready", {
  postProcess: "uppercase"
});

Очистка и нормализация строк

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

const trimPostProcessor = {
  name: "trim",

  process(value) {
    return value.replace(/\s+/g, " ").trim();
  }
};

i18next.addPostProcessor(trimPostProcessor);

Такая обработка полезна при:

  • агрегации переводов из внешних источников
  • динамическом формировании строк
  • объединении фрагментов текста

Форматирование HTML и безопасность

Постпроцессоры часто применяются для управления HTML-вставками и экранирования.

HTML-экранирование

const escapeHtmlPostProcessor = {
  name: "escapeHtml",

  process(value) {
    return value
      .replace(/&/g, "&")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
  }
};

i18next.addPostProcessor(escapeHtmlPostProcessor);

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


Условные преобразования на основе опций

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

const genderPostProcessor = {
  name: "gender",

  process(value, key, options) {
    if (options.gender === "female") {
      return value.replace("{actor}", "она");
    }
    if (options.gender === "male") {
      return value.replace("{actor}", "он");
    }
    return value.replace("{actor}", "они");
  }
};

i18next.addPostProcessor(genderPostProcessor);

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

i18next.t("actor_message", {
  postProcess: "gender",
  gender: "female"
});

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

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

const numberFormatPostProcessor = {
  name: "numberFormat",

  process(value, key, options) {
    const number = Number(value);

    if (Number.isNaN(number)) return value;

    return new Intl.NumberFormat(options.locale || "ru-RU").format(number);
  }
};

i18next.addPostProcessor(numberFormatPostProcessor);

Интеграция с интерполяцией

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

i18next.t("balance", {
  amount: 1500,
  postProcess: "numberFormat"
});

При этом интерполяция:

"balance": "Баланс: {{amount}}"

Превращается в промежуточную строку, которая затем модифицируется постпроцессором.


Условное применение внутри postProcess цепочки

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

const conditionalPostProcessor = {
  name: "conditional",

  process(value, key, options) {
    if (options.skipPostProcess) {
      return value;
    }

    return `[processed] ${value}`;
  }
};

i18next.addPostProcessor(conditionalPostProcessor);

Совместимость с fallback-цепочкой i18next

Постпроцессоры применяются уже после выбора языка и fallback-цепочки. Это означает, что:

  • сначала определяется язык
  • затем берётся перевод или fallback
  • затем выполняется интерполяция
  • затем применяются постпроцессоры

Такая последовательность исключает влияние постпроцессора на выбор перевода.


Производительность и побочные эффекты

Постпроцессоры выполняются синхронно в основном потоке обработки перевода. Это накладывает ограничения:

  • нежелательно выполнять I/O операции
  • не рекомендуется использовать тяжёлые вычисления
  • требуется детерминированность результата

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


Комбинация с форматтерами i18next

В экосистеме i18next существуют параллельные механизмы форматирования:

  • интерполяционные форматтеры (interpolation.format)
  • постпроцессоры (postProcess)

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


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

Полный pipeline обработки перевода:

  1. загрузка ресурса
  2. выбор языка
  3. разрешение ключа
  4. fallback при необходимости
  5. интерполяция значений
  6. postProcess цепочка
  7. возврат результата

Любое вмешательство постпроцессора происходит на последнем этапе логической обработки строки.


Расширенные сценарии использования

Кастомные постпроцессоры часто применяются для:

  • внедрения markdown-разметки
  • пост-валидации переводов
  • динамической локализации дат и чисел
  • внедрения бизнес-правил отображения текста
  • нормализации внешних переводческих систем

Markdown-постпроцессор

import { marked } from "marked";

const markdownPostProcessor = {
  name: "markdown",

  process(value) {
    return marked.parse(value);
  }
};

i18next.addPostProcessor(markdownPostProcessor);

Такая схема позволяет хранить переводы в markdown-формате и рендерить их в HTML на финальном этапе.


Модульная композиция постпроцессоров

Система позволяет строить переиспользуемые блоки обработки:

i18next.t("text", {
  postProcess: ["trim", "escapeHtml", "uppercase"]
});

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


Ошибки и деградация поведения

При возникновении ошибки внутри process поведение зависит от реализации конкретной версии i18next, но в типовой модели:

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

Поэтому постпроцессоры проектируются с защитой от исключений:

process(value) {
  try {
    return customTransform(value);
  } catch (e) {
    return value;
  }
}