В библиотеке Globalize расширение поведения строится вокруг композиции функций форматирования, парсинга и локализационных преобразований. Несмотря на отсутствие формального механизма middleware в классическом смысле серверных фреймворков, архитектура позволяет выстраивать аналогичные цепочки обработки данных за счёт обёрток, пользовательских форматов и перехвата вызовов на уровне API.
Globalize опирается на данные CLDR и предоставляет набор чистых функций: форматирование чисел, дат, сообщений и разбор строк обратно в структурированные значения. Вся логика разделена на два слоя:
Расширяемость достигается не через изменение внутреннего кода, а через композицию поверх API. Это создаёт основу для middleware-подобных решений, где каждый слой может модифицировать входные или выходные данные.
Ключевой принцип:
На практике middleware в контексте Globalize реализуется через обёртывание функций форматирования.
const baseNumberFormatter = Globalize.numberFormatter();
function loggingFormatter(value) {
const start = performance.now();
const result = baseNumberFormatter(value);
const end = performance.now();
console.log("Format duration:", end - start);
return result;
}
Здесь создаётся поведенческий слой, который не изменяет сам Globalize, но вмешивается в процесс выполнения. Такая модель масштабируется:
Композиция остаётся прозрачной и управляемой.
Middleware-подход усиливается при построении цепочек функций. Каждая функция принимает результат предыдущей и возвращает модифицированное значение.
function trimWhitespace(next) {
return function (value) {
return next(value.trim());
};
}
function toUpperCase(next) {
return function (value) {
return next(value.toUpperCase());
};
}
const baseMessageFormatter = Globalize.messageFormatter("greeting");
const enhancedFormatter = trimWhitespace(
toUpperCase(
baseMessageFormatter
)
);
Такая структура позволяет формировать слой локализационной обработки без изменения исходного форматтера.
Числовые форматтеры Globalize часто используются в приложениях с высокой нагрузкой на UI. Middleware здесь применяется для стандартизации входных данных и контроля диапазонов значений.
const formatter = Globalize.numberFormatter({
minimumFractionDigits: 2
});
function safeNumberFormatter(value) {
if (typeof value !== "number" || Number.isNaN(value)) {
return formatter(0);
}
if (!Number.isFinite(value)) {
return formatter(0);
}
return formatter(value);
}
Подобная обёртка формирует защитный слой, предотвращающий распространение некорректных значений в UI-уровень.
Форматирование дат в Globalize также может быть включено в middleware-конвейер. Поскольку форматтеры возвращают чистые строки, они легко комбинируются с дополнительной логикой.
const dateFormatter = Globalize.dateFormatter({
datetime: "medium"
});
function timezoneMiddleware(next, offsetHours) {
return function (date) {
const shifted = new Date(date.getTime());
shifted.setHours(shifted.getHours() + offsetHours);
return next(shifted);
};
}
const formatterWithTimezone = timezoneMiddleware(dateFormatter, 3);
Здесь добавляется слой модификации временной зоны без изменения исходного механизма форматирования.
MessageFormatter в Globalize работает на основе ICU Message syntax. Это создаёт точку расширения, где можно внедрять промежуточную обработку сообщений до их форматирования.
const formatter = Globalize.messageFormatter("items");
function pluralizationMiddleware(next) {
return function (data) {
if (data.count < 0) {
data = { ...data, count: 0 };
}
return next(data);
};
}
const enhancedMessage = pluralizationMiddleware(formatter);
Такие обёртки используются для:
Отдельный уровень расширения связан с загрузкой и подготовкой CLDR данных. Хотя Globalize не навязывает единый lifecycle, приложения часто вводят собственные хуки инициализации.
Типичный сценарий:
async function loadLocale(locale) {
const data = await fetch(`/cldr/${locale}.json`).then(r => r.json());
Globalize.load(data);
return initializeFormatters(locale);
}
Такая функция выступает как hook, через который можно:
Одним из наиболее практичных применений middleware в Globalize является кэширование форматтеров. Поскольку создание форматтеров может быть дорогостоящим, добавляется слой мемоизации.
const cache = new Map();
function cachedNumberFormatter(options) {
const key = JSON.stringify(options);
if (cache.has(key)) {
return cache.get(key);
}
const formatter = Globalize.numberFormatter(options);
cache.set(key, formatter);
return formatter;
}
Такой подход снижает количество повторных вычислений и стабилизирует производительность при большом количестве локализованных элементов.
Middleware часто используется для приведения входных данных к ожидаемому виду перед передачей в Globalize.
function normalizeInput(next) {
return function (value) {
if (typeof value === "string") {
value = Number(value.replace(",", "."));
}
return next(value);
};
}
Это особенно важно в интерфейсах, где данные поступают из разных источников:
Наиболее гибкая модель возникает при объединении нескольких middleware в один pipeline.
function compose(...fns) {
return fns.reduce((prev, next) => next(prev));
}
const formatter = compose(
normalizeInput,
safeNumberFormatter
);
Каждый слой выполняет строго ограниченную задачу, а итоговая функция остаётся предсказуемой и тестируемой.
Globalize сам по себе не имеет побочных эффектов в форматировании, но middleware-слой может их вводить. Поэтому распространённой практикой является разделение:
function sideEffectLogger(next) {
return function (value) {
const result = next(value);
sendMetric("format_used");
return result;
};
}
Такая структура требует дисциплины, так как усложнение цепочки может привести к скрытым зависимостям.
В прикладных архитектурах middleware вокруг Globalize часто используется на уровне сервисов локализации:
class I18nService {
constructor(globalize) {
this.globalize = globalize;
this.numberFormatter = cachedNumberFormatter({ maximumFractionDigits: 2 });
}
formatPrice(value) {
return this.numberFormatter(value);
}
}
Такой сервис становится слоем между библиотекой и интерфейсом приложения, обеспечивая контролируемую точку расширения поведения.