В системе интернационализации i18next шаблоны сообщений строятся
вокруг интерполяции значений, где динамические данные подставляются в
строки перевода. Базовый механизм опирается на конструкцию вида
{{value}}, однако для практических сценариев локализации
этого недостаточно: требуется форматирование чисел, дат, падежей, единиц
измерения и комбинированных преобразований. Одним из удобных
архитектурных подходов становится pipe-подобная модель обработки
значений внутри темплейтов.
Механизм интерполяции в i18next позволяет вставлять переменные прямо в строку перевода:
{
"price": "Цена: {{value}}"
}
t('price', { value: 150 })
На этом уровне данные подставляются без преобразований. Любая логика форматирования вынесена наружу или реализуется через дополнительные механизмы.
При усложнении интерфейсов возникают задачи:
Прямое внедрение логики в шаблоны приводит к разрастанию кода, а передача уже отформатированных значений снижает гибкость локализации.
Pipe-подход вводит концепцию последовательного преобразования значения внутри шаблона:
{{value | format1 | format2 | format3}}
Каждый pipe представляет собой функцию преобразования, которая принимает результат предыдущего шага и возвращает новое значение.
В контексте i18next такая модель реализуется не нативно, а через кастомную интерполяцию или форматтеры.
i18next предоставляет механизм форматирования через функцию
interpolation.format, которая может быть
переопределена:
import i18next from 'i18next';
i18next.init({
interpolation: {
format: (value, format, lng) => {
if (format === 'upper') return String(value).toUpperCase();
if (format === 'lower') return String(value).toLowerCase();
return value;
}
}
});
Шаблон:
{{name, upper}}
Такой формат уже позволяет реализовать простейшие трансформации, однако он ограничен одним преобразованием.
Чтобы приблизиться к pipe-синтаксису, формат может интерпретироваться как последовательность операций:
const formatters = {
upper: v => String(v).toUpperCase(),
trim: v => String(v).trim(),
exclaim: v => `${v}!`
};
i18next.init({
interpolation: {
format: (value, format) => {
if (!format) return value;
const steps = format.split('|');
return steps.reduce((acc, step) => {
const fn = formatters[step.trim()];
return fn ? fn(acc) : acc;
}, value);
}
}
});
Шаблон:
{{name, upper|trim|exclaim}}
Значение проходит последовательно через цепочку функций, что повторяет поведение pipe-оператора.
Pipe-модель строится на принципе композиции функций:
Такой подход делает локализацию более декларативной.
Pipe особенно полезен при форматировании числовых значений:
const formatters = {
currency: (v, lng) =>
new Intl.NumberFormat(lng, {
style: 'currency',
currency: 'USD'
}).format(v),
percent: (v) => `${v * 100}%`
};
Шаблон:
{{price, currency}}
Или цепочка:
{{value, percent|exclaim}}
Функция форматирования получает текущую локаль, что позволяет адаптировать pipe-операции под язык:
interpolation: {
format: (value, format, lng) => {
const steps = format.split('|');
return steps.reduce((acc, step) => {
if (step === 'currency') {
return new Intl.NumberFormat(lng, {
style: 'currency',
currency: lng === 'ru' ? 'RUB' : 'USD'
}).format(acc);
}
return acc;
}, value);
}
}
Таким образом, pipe становится контекстно-зависимым.
Pipe-цепочки позволяют строить сложные трансформации без изменения шаблонов перевода:
{{username, trim|lower|exclaim}}
или
{{date, formatDate|relativeTime}}
Каждый шаг инкапсулирует отдельную ответственность:
Несмотря на выразительность, pipe-модель сталкивается с рядом ограничений:
Особенно критичным становится контроль порядка выполнения и предсказуемость результата при вложенных форматах.
При проектировании pipe-цепочек важным становится ограничение набора доступных функций:
const formatters = Object.freeze({
upper: v => String(v).toUpperCase(),
lower: v => String(v).toLowerCase()
});
Запрещается динамическое выполнение произвольного кода из строки формата, так как шаблон перевода может поступать из внешних источников.
Форматтеры могут быть вынесены в отдельные модули:
export const dateFormatters = {
shortDate: (v, lng) =>
new Intl.DateTimeFormat(lng).format(new Date(v))
};
Далее они объединяются в общий реестр:
const formatters = {
...dateFormatters,
...stringFormatters,
...numberFormatters
};
Это превращает pipe-модель в расширяемую систему преобразований.
В i18next также используется формат:
{{value, formatName}}
Он более строгий и безопасный, но менее выразительный, чем pipe-цепочки. Pipe-модель выигрывает в гибкости композиции, но проигрывает в стандартизации и читаемости при длинных выражениях.
Pipe-логика остаётся частью слоя представления и не должна проникать в бизнес-логику. В шаблонах переводов сохраняется декларативность:
{
"welcome": "Привет, {{name, trim|capitalize}}"
}
Вся трансформация определяется исключительно контекстом отображения, не затрагивая данные источника.