Комбинирование форматтеров

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

Внутри Globalize форматтеры создаются как функции, сохраняющие локаль и правила CLDR. Каждый тип форматтера решает узкую задачу:

  • числовой форматтер преобразует числа в локализованное представление
  • форматтер валют учитывает символы и порядок отображения
  • форматтер дат и времени применяет календарные и региональные правила
  • message formatter управляет структурой текста и вставками значений

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

Простейший уровень композиции — вложение форматированных значений в строку сообщения:

const Globalize = require("globalize");

Globalize.locale("ru");

const numberFormatter = Globalize.numberFormatter();
const currencyFormatter = Globalize.currencyFormatter("USD");

const messageFormatter = Globalize.messageFormatter(
  "{count} товаров на сумму {total}"
);

const count = numberFormatter(1250);
const total = currencyFormatter(1250);

messageFormatter({
  count,
  total
});

Здесь форматирование числа и валюты выполняется отдельно, после чего результаты объединяются в message formatter.


Композиция через ICU MessageFormat

Наиболее устойчивый способ комбинирования форматтеров — использование ICU-синтаксиса, встроенного в message formatter. Он позволяет включать числовые и текстовые форматтеры прямо в шаблон, устраняя необходимость ручного форматирования промежуточных значений.

ICU-сообщение поддерживает вложенные правила:

  • pluralization (множественные формы)
  • select (ветвления)
  • number formatting (через встроенные подформаты)

Пример комбинирования числа и множественного числа:

const msg = Globalize.messageFormatter(
  "{count, plural, one {# файл} few {# файла} other {# файлов}}"
);

msg({ count: 1 });
msg({ count: 3 });
msg({ count: 10 });

В данном случае число одновременно участвует в двух процессах:

  1. определяет ветку plural-правила
  2. подставляется внутрь строки через #

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


Встраивание числовых форматтеров в сообщения

ICU-синтаксис позволяет комбинировать числовое форматирование и текст в одном выражении:

const msg = Globalize.messageFormatter(
  "Баланс: {balance, number, currency}"
);

msg({ balance: 15320.5 });

Здесь происходит автоматическое применение currency formatter, соответствующего текущей локали. Это означает, что явное создание currencyFormatter не требуется, если форматирование описано в message template.

Поддерживаются различные стили:

  • number
  • integer
  • percent
  • currency

Пример комбинирования нескольких числовых представлений:

const msg = Globalize.messageFormatter(
  "Доход: {income, number, currency}, рост: {growth, number, percent}"
);

msg({
  income: 5000,
  growth: 0.12
});

Каждое значение форматируется независимо, но итоговая строка формируется единым механизмом.


Комбинирование дат и сообщений

Форматтер дат часто используется совместно с текстовыми шаблонами. В отличие от чисел, даты требуют контекста локали и календаря.

const dateFormatter = Globalize.dateFormatter({ datetime: "medium" });

const msg = Globalize.messageFormatter(
  "Последнее обновление: {date}"
);

msg({
  date: dateFormatter(new Date(2026, 4, 27))
});

Такой способ является базовым уровнем композиции: форматтер даты применяется отдельно, затем результат вставляется в сообщение.

Однако ICU позволяет встроить форматирование даты напрямую:

const msg = Globalize.messageFormatter(
  "Последнее обновление: {date, date, medium}"
);

msg({
  date: new Date(2026, 4, 27)
});

Здесь отсутствует промежуточный форматтер, а композиция происходит внутри message engine.


Вложенные форматтеры и структурирование данных

Более сложная композиция возникает при работе с объектами, содержащими несколько полей локализации.

const msg = Globalize.messageFormatter(
  "Заказ {id}: {quantity, plural, one {# позиция} other {# позиций}} на сумму {total, number, currency}"
);

msg({
  id: 9821,
  quantity: 3,
  total: 2599.99
});

В одном сообщении комбинируются:

  • идентификатор без форматирования
  • plural-логика
  • валютное форматирование

Здесь композиция становится декларативной: форматтеры не вызываются вручную, а описываются как часть структуры сообщения.


Функциональная композиция форматтеров

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

const Globalize = require("globalize");

Globalize.locale("ru");

const formatPrice = Globalize.currencyFormatter("USD");
const formatCount = Globalize.numberFormatter();

function formatOrder(order) {
  return `Заказ ${order.id}: ${formatCount(order.items)} шт., ${formatPrice(order.total)}`;
}

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

  • переиспользовать их в разных контекстах
  • тестировать отдельно
  • комбинировать с произвольной бизнес-логикой

Такая модель ближе к функциональному стилю, чем к ICU-шаблонам.


Композиция через промежуточные представления

В сложных интерфейсах часто возникает необходимость создавать промежуточные структуры данных, содержащие уже частично отформатированные значения.

const numberFormatter = Globalize.numberFormatter();
const percentFormatter = Globalize.numberFormatter({ style: "percent" });

const stats = {
  users: numberFormatter(12000),
  conversion: percentFormatter(0.034)
};

const msg = Globalize.messageFormatter(
  "Пользователи: {users}, конверсия: {conversion}"
);

msg(stats);

Такой подход фиксирует форматирование на уровне модели данных, а message formatter отвечает только за структуру текста.


Конфликты и порядок применения форматтеров

При комбинировании форматтеров важно учитывать, что повторное форматирование уже отформатированной строки приводит к потере типовой информации.

Например:

const nf = Globalize.numberFormatter();
const msg = Globalize.messageFormatter("{value, number}");

msg({ value: nf(1000) });

Здесь результат nf(1000) уже является строкой, поэтому ICU не может применить числовое форматирование корректно.

Правильная модель — передавать исходные значения:

msg({ value: 1000 });

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


Динамическое комбинирование форматтеров

В прикладных системах часто требуется динамическое формирование сообщений, где структура заранее неизвестна.

function buildMessage(data) {
  const msg = Globalize.messageFormatter(
    "{title}: {value}"
  );

  return msg({
    title: data.title,
    value: data.isCurrency
      ? Globalize.currencyFormatter(data.currency)(data.value)
      : Globalize.numberFormatter()(data.value)
  });
}

Здесь форматтер выбирается динамически, а итоговая композиция формируется на уровне функции.

Такой подход используется при:

  • условном форматировании
  • работе с API-ответами разной структуры
  • адаптации под пользовательские настройки

Многоуровневая композиция форматтеров

В сложных интерфейсах форматтеры образуют многоуровневую систему:

  1. уровень данных (сырой объект)
  2. уровень числовых и датных форматтеров
  3. уровень message formatter
  4. уровень UI-рендеринга

Пример цепочки:

const price = Globalize.currencyFormatter("EUR");
const date = Globalize.dateFormatter({ datetime: "short" });

const msg = Globalize.messageFormatter(
  "От {date} стоимость составила {price}"
);

msg({
  date: date(new Date()),
  price: price(199.99)
});

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


Производительность при комбинировании форматтеров

Создание форматтеров в Globalize связано с затратами на инициализацию CLDR-данных. При частом комбинировании важно учитывать:

  • форматтеры следует кэшировать
  • избегается создание внутри горячих циклов
  • message formatter создаётся один раз на шаблон

Пример корректного кэширования:

const formatters = {
  price: Globalize.currencyFormatter("USD"),
  count: Globalize.numberFormatter(),
  message: Globalize.messageFormatter(
    "{count} items for {price}"
  )
};

Повторное использование снижает нагрузку и упрощает композицию.


Итоговая модель комбинирования

Комбинирование форматтеров в Globalize формируется вокруг трёх устойчивых стратегий:

  • композиция через ICU message templates
  • функциональная композиция форматтеров
  • смешанная модель с промежуточными данными

Каждая стратегия применима в зависимости от сложности структуры данных и требований к локализации, при этом базовый принцип остаётся неизменным: форматирование всегда разделяется на независимые операции, объединяемые на уровне сообщения или функции.