Intl API в JavaScript предоставляет набор специализированных объектов для локализации: форматирование чисел, дат, списков, сравнений строк, отображения языков и регионов, работы с множественными формами и сегментацией текста. Основная сложность при работе с этими инструментами возникает не в их использовании по отдельности, а в организации связного, повторно используемого слоя форматирования, который можно масштабировать, комбинировать и переиспользовать в приложении.
Композиция в контексте Intl API означает построение системы небольших функций-обёрток, каждая из которых инкапсулирует один аспект локализации, а затем объединение этих функций в единый поток преобразования данных.
Intl API изначально ориентирован на создание экземпляров форматтеров:
Intl.NumberFormatIntl.DateTimeFormatIntl.CollatorIntl.ListFormatIntl.RelativeTimeFormatIntl.DisplayNamesIntl.SegmenterКаждый из этих объектов выполняет строго ограниченную задачу. Именно это делает их удобными для композиции: они не смешивают ответственность.
Композиция строится вокруг двух идей:
1. Функция как единица локализации Каждый форматтер превращается в функцию:
const formatNumber = new Intl.NumberFormat("ru-RU").format;
const formatDate = new Intl.DateTimeFormat("ru-RU", {
year: "numeric",
month: "long",
day: "numeric",
}).format;
Теперь форматирование становится не объектным вызовом, а функцией, которую можно передавать, хранить и комбинировать.
2. Разделение данных и представления Данные остаются неизменными, а форматирование накладывается слоями:
const price = 1999.99;
const formatted = formatNumber(price);
Оборачивание Intl API в функции позволяет создавать предсказуемые интерфейсы.
Фабрика создаёт специализированную функцию с заранее заданной конфигурацией:
function createCurrencyFormatter(locale, currency) {
const formatter = new Intl.NumberFormat(locale, {
style: "currency",
currency,
});
return (value) => formatter.format(value);
}
const formatUSD = createCurrencyFormatter("en-US", "USD");
const formatEUR = createCurrencyFormatter("de-DE", "EUR");
Такая структура позволяет изолировать конфигурацию и повторно использовать форматтеры без пересоздания.
Композиция проявляется, когда результат одной функции становится входом другой.
const formatNumber = new Intl.NumberFormat("ru-RU").format;
const formatPercent = new Intl.NumberFormat("ru-RU", {
style: "percent",
}).format;
const toFixedTwo = (n) => Number(n.toFixed(2));
const pipe =
(...fns) =>
(value) =>
fns.reduce((acc, fn) => fn(acc), value);
const formatPipeline = pipe(toFixedTwo, formatPercent);
formatPipeline(0.1234);
Здесь формируется цепочка преобразований:
Intl API естественным образом поддерживает паттерн частичного применения через конфигурацию конструктора.
const ruDateFormatter = (options) =>
new Intl.DateTimeFormat("ru-RU", options).format;
const formatShortDate = ruDateFormatter({
day: "2-digit",
month: "2-digit",
year: "numeric",
});
const formatLongDate = ruDateFormatter({
day: "numeric",
month: "long",
year: "numeric",
});
Здесь конфигурация отделена от выполнения, что позволяет строить библиотечные уровни абстракции.
Одна из ключевых проблем — динамическая смена языка интерфейса.
function createI18n(locale) {
const number = new Intl.NumberFormat(locale).format;
const date = new Intl.DateTimeFormat(locale).format;
const list = new Intl.ListFormat(locale).format;
return {
number,
date,
list,
};
}
const i18nRU = createI18n("ru-RU");
const i18nEN = createI18n("en-US");
Такой подход позволяет заменить всю локализацию через одну точку входа.
Intl.ListFormat особенно полезен в композиционных
цепочках, когда требуется объединить массив данных в человекочитаемую
форму.
const formatList = new Intl.ListFormat("ru-RU", {
style: "long",
type: "conjunction",
}).format;
const users = ["Анна", "Иван", "Мария"];
formatList(users);
Композиция усиливается, когда список предварительно трансформируется:
const upper = (arr) => arr.map((x) => x.toUpperCase());
const format = (arr) => formatList(arr);
const pipeline = pipe(upper, format);
pipeline(users);
Intl.Collator используется для корректной сортировки
строк в зависимости от языка.
const collator = new Intl.Collator("ru-RU").compare;
const sorted = ["яблоко", "Ёж", "арбуз"].sort(collator);
Композиция возникает при объединении фильтрации и сортировки:
const normalize = (s) => s.toLowerCase();
const prepare = (arr) =>
arr.map(normalize).sort(collator);
prepare(["ЯБЛОКО", "ёж", "Арбуз"]);
Intl.RelativeTimeFormat позволяет выражать время
относительно текущего момента.
const rtf = new Intl.RelativeTimeFormat("ru-RU", {
numeric: "auto",
});
const formatRelativeDay = (days) => rtf.format(days, "day");
Композиция проявляется при расчёте разницы дат:
const diffInDays = (date) =>
Math.round((date - new Date()) / (1000 * 60 * 60 * 24));
const formatFromNow = pipe(diffInDays, formatRelativeDay);
Intl.Segmenter используется для разбиения текста на
символы, слова или предложения с учётом языка.
const segmenter = new Intl.Segmenter("ru", { granularity: "word" });
const segment = (text) =>
Array.from(segmenter.segment(text), (s) => s.segment);
Композиция становится особенно полезной при последующей обработке:
const filterEmpty = (arr) => arr.filter((x) => x.trim());
const count = (arr) => arr.length;
const wordCount = pipe(segment, filterEmpty, count);
Композиция Intl API наиболее эффективно проявляется в виде конвейеров преобразования данных.
const pipe =
(...fns) =>
(value) =>
fns.reduce((acc, fn) => fn(acc), value);
const formatPrice = createCurrencyFormatter("ru-RU", "RUB");
const round = (n) => Math.round(n);
const processPrice = pipe(round, formatPrice);
processPrice(1999.55);
Здесь каждый слой независим:
При построении сложных приложений Intl API часто выносится в отдельный слой доменной логики.
function createFormatterService(locale) {
const number = new Intl.NumberFormat(locale).format;
const date = new Intl.DateTimeFormat(locale).format;
const percent = new Intl.NumberFormat(locale, {
style: "percent",
}).format;
return {
number,
date,
percent,
};
}
Такой слой становится единственной точкой, через которую проходит вся локализация.
Intl объекты являются неизменяемыми после создания. Это свойство напрямую усиливает композиционный подход:
const fmt = new Intl.NumberFormat("ru-RU");
const f1 = fmt.format;
const f2 = fmt.format;
Обе функции идентичны по поведению и безопасны для многократного использования.
const withLocale =
(locale) =>
(formatter) =>
(...args) =>
formatter(locale)(...args);
const createLocalizedFormatters = (locale) => ({
number: new Intl.NumberFormat(locale).format,
date: new Intl.DateTimeFormat(locale).format,
list: new Intl.ListFormat(locale).format,
});
Композиция Intl API естественным образом ложится на слоистую модель:
Каждый слой изолирован и может быть заменён без изменения остальных.