В архитектуре Globalize форматирование строится вокруг данных CLDR и набора специализированных модулей (числа, даты, сообщения, валюты). Однако реальная разработка часто требует выходить за пределы стандартных форматов: объединять несколько значений, внедрять доменные правила отображения, создавать локализованные представления сложных структур.
Пользовательский форматтер в этом контексте представляет собой функцию или объект, который принимает исходные данные и возвращает строку, сформированную с учётом локали и правил форматирования Globalize.
Ключевая особенность подхода заключается в том, что локализация отделяется от логики преобразования, а форматтер становится тонким слоем над уже локализованными примитивами.
Наиболее распространённый способ создания пользовательских форматтеров в Globalize основан на системе сообщений. Сообщение может включать параметры, плейсхолдеры и вложенные форматтеры.
Инициализация базового сообщения:
Globalize.loadMessages({
ru: {
orderSummary: "Заказ №{orderId}, сумма: {total}",
}
});
Получение форматтера:
const formatOrder = Globalize("ru").messageFormatter("orderSummary");
formatOrder({
orderId: 1523,
total: "1200 ₽"
});
Внутри системы сообщений параметры обрабатываются через ICU-подобный синтаксис, что позволяет расширять форматирование без изменения кода.
Пользовательские форматтеры часто комбинируются с встроенными механизмами Globalize, такими как форматирование чисел и дат.
Пример с числом:
Globalize.loadMessages({
ru: {
price: "Цена: {price, number, currency}"
}
});
Использование:
const formatPrice = Globalize("ru").messageFormatter("price");
formatPrice({ price: 1250 });
Здесь number, currency использует внутренний модуль
форматирования чисел, но структура сообщения остаётся пользовательской.
Это фактически первый уровень композиции форматтеров.
В более сложных сценариях создаются явные функции-форматтеры, которые комбинируют несколько вызовов Globalize.
const cldr = Globalize("ru");
const formatUserCard = (user) => {
const name = user.name;
const balance = cldr.formatNumber(user.balance, { style: "currency", currency: "RUB" });
const lastLogin = cldr.formatDate(user.lastLogin, { datetime: "medium" });
return `${name} | Баланс: ${balance} | Последний вход: ${lastLogin}`;
};
Такой подход используется, когда сообщение становится слишком сложным для ICU-шаблонов или требует условной логики.
Для масштабируемых приложений форматтеры часто оформляются как фабрики, привязанные к локали.
const createUserFormatter = (locale) => {
const globalize = Globalize(locale);
return {
formatBalance: (value) =>
globalize.formatNumber(value, { style: "currency", currency: "RUB" }),
formatDate: (date) =>
globalize.formatDate(date, { datetime: "short" }),
formatProfile: (user) =>
`${user.name} — ${globalize.formatNumber(user.score)}`
};
};
Такой слой позволяет:
Globalize поддерживает расширенные ICU-форматы, где можно подключать форматтеры через именованные функции.
Globalize.loadMessages({
ru: {
distance: "{value, number, ::unit-meter}"
}
});
При этом unit-meter может быть переопределён через
кастомный форматтер-слой:
const cldr = Globalize("ru");
const formatDistance = (meters) => {
if (meters >= 1000) {
return cldr.formatNumber(meters / 1000) + " км";
}
return cldr.formatNumber(meters) + " м";
};
Далее такой форматтер интегрируется на уровне бизнес-логики или сообщения-обёртки.
ICU-синтаксис позволяет создавать условные ветвления, что фактически заменяет часть кастомной логики форматтеров.
Globalize.loadMessages({
ru: {
notifications: "{count, plural, one {# уведомление} few {# уведомления} many {# уведомлений} other {# уведомления}}"
}
});
Форматтер:
const formatNotifications = Globalize("ru").messageFormatter("notifications");
formatNotifications({ count: 5 });
При этом пользовательский форматтер здесь заключается не в коде, а в декларативном описании правил.
Ключевая техника построения сложных форматтеров — композиция.
const cldr = Globalize("ru");
const formatInvoice = (invoice) => {
const header = `Счёт: ${invoice.id}`;
const amount = cldr.formatNumber(invoice.amount, { style: "currency", currency: "RUB" });
const date = cldr.formatDate(invoice.date, { date: "long" });
const status = invoice.paid ? "Оплачен" : "Ожидает оплаты";
return [header, amount, date, status].join("\n");
};
Композиция позволяет:
Форматтеры могут быть «захвачены» замыканием, что снижает стоимость их повторного создания.
const cldr = Globalize("ru");
const currencyFormatter = (value) =>
cldr.formatNumber(value, { style: "currency", currency: "RUB" });
const dateFormatter = (date) =>
cldr.formatDate(date, { datetime: "medium" });
const formatTransaction = (tx) =>
`${dateFormatter(tx.date)} | ${currencyFormatter(tx.amount)}`;
Такой подход особенно эффективен при обработке списков и таблиц.
Форматтер может принимать параметры конфигурации, расширяя поведение без изменения реализации.
const createCurrencyFormatter = (locale, currency) => {
const cldr = Globalize(locale);
return (value) =>
cldr.formatNumber(value, { style: "currency", currency });
};
const formatUSD = createCurrencyFormatter("en", "USD");
const formatEUR = createCurrencyFormatter("de", "EUR");
Подобная конструкция используется для мультивалютных интерфейсов и SaaS-систем.
Форматтеры часто становятся частью слоя представления доменных объектов.
const cldr = Globalize("ru");
const formatProduct = (product) => ({
title: product.name,
price: cldr.formatNumber(product.price, { style: "currency", currency: "RUB" }),
availability: product.stock > 0 ? "В наличии" : "Нет в наличии"
});
Такой подход позволяет отделить доменную логику от локализационного слоя и избежать дублирования правил отображения.
При высокой нагрузке имеет смысл кэшировать форматтеры или их результаты.
const cldr = Globalize("ru");
const numberCache = new Map();
const cachedFormatNumber = (value) => {
if (!numberCache.has(value)) {
numberCache.set(
value,
cldr.formatNumber(value, { minimumFractionDigits: 2 })
);
}
return numberCache.get(value);
};
Кэширование особенно эффективно при повторяющихся значениях (таблицы, отчёты, списки транзакций).
В некоторых системах форматтеры могут подменяться в зависимости от контекста (например, режим отображения).
const createFormatter = (mode) => {
const cldr = Globalize("ru");
if (mode === "compact") {
return (value) => cldr.formatNumber(value / 1000) + " тыс.";
}
return (value) =>
cldr.formatNumber(value, { minimumFractionDigits: 2 });
};
Это позволяет адаптировать представление данных под разные UI-сценарии без изменения бизнес-логики.
Пользовательские форматтеры в Globalize формируют промежуточный слой между:
Такой слой обеспечивает единообразие представления данных, снижает дублирование логики и позволяет масштабировать систему локализации без роста сложности бизнес-кода.