Пользовательские паттерны

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


Базовая модель шаблонов сообщений

В основе системы сообщений FormatJS лежит строковый шаблон, содержащий плейсхолдеры и управляющие конструкции:

const message = "У вас {count} новых сообщений";

При этом шаблон может включать сложные конструкции ICU:

const message = "{count, plural, one {# сообщение} few {# сообщения} many {# сообщений}}";

Пользовательские паттерны возникают там, где стандартных plural, select, number, date становится недостаточно для описания логики вывода.


Расширение через именованные форматы

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

import { createIntl, createIntlCache } from 'react-intl';

const cache = createIntlCache();

const intl = createIntl(
  {
    locale: 'ru',
    messages: {}
  },
  cache
);

intl.formatNumber(12345.67, {
  style: 'currency',
  currency: 'RUB'
});

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


Доменные форматтеры как пользовательские паттерны

В прикладной архитектуре часто вводятся функции-обёртки, инкапсулирующие правила отображения:

const formatPrice = (intl, value) =>
  intl.formatNumber(value, {
    style: 'currency',
    currency: 'USD',
    minimumFractionDigits: 2
  });

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

Далее он используется внутри сообщений:

intl.formatMessage(
  { id: 'product.price' },
  { price: formatPrice(intl, 99.99) }
);

Пользовательские паттерны через ICU select

Одним из ключевых механизмов является select, позволяющий строить условные языковые конструкции:

const message = "{gender, select, male {Он добавил} female {Она добавила} other {Пользователь добавил}}";

На уровне паттернов это превращается в стратегию локализации поведения, а не текста.

Расширение этого механизма через доменные значения позволяет формировать более сложные паттерны:

const statusMessage =
  "{status, select, pending {Ожидание} success {Успешно} error {Ошибка} other {Неизвестно}}";

Композиция паттернов

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

const messages = {
  invoice: "Счёт {id}: {amount, number, currency}",
  invoiceDetailed: "Счёт {id} от {date, date, long}: {amount, number, currency}"
};

На уровне архитектуры приложения это может быть обёрнуто:

const formatInvoice = (intl, invoice) =>
  intl.formatMessage(
    { id: 'invoiceDetailed' },
    {
      id: invoice.id,
      date: invoice.date,
      amount: invoice.amount
    }
  );

Пользовательские паттерны и react-intl

react-intl расширяет механизм FormatJS за счёт декларативных компонентов, в которых паттерны становятся частью UI-слоя.

<FormattedMessage
  id="cart.summary"
  values={{
    count: items.length,
    total: totalPrice
  }}
/>

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

  • обёртки над <FormattedMessage>
  • кастомные функции formatMessage
  • предрасчитанные значения в values

Паттерны с функциями форматирования значений

FormatJS позволяет передавать не только значения, но и уже отформатированные строки. Это создаёт слой пользовательских паттернов, где форматирование становится промежуточной стадией.

const values = {
  price: intl.formatNumber(1200, { style: 'currency', currency: 'EUR' }),
  date: intl.formatDate(new Date(), { year: 'numeric', month: 'long' })
};

Далее они используются в сообщении без дополнительной логики:

intl.formatMessage({ id: 'order.summary' }, values);

Переиспользуемые шаблоны сообщений

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

const TEMPLATES = {
  USER_STATUS: "{name} сейчас {status}",
  FILE_UPLOAD: "Файл {fileName} загружен на {date, date, short}",
  NOTIFICATION:
    "{level, select, info {Информация} warning {Предупреждение} error {Ошибка} other {Сообщение}}: {text}"
};

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


Условные паттерны и вычисляемые значения

В некоторых случаях паттерны включают вычисляемые значения, подготовленные до передачи в FormatJS:

const buildValues = (user) => ({
  name: user.name,
  status: user.active ? 'активен' : 'неактивен'
});

И далее:

intl.formatMessage(
  { id: 'user.status' },
  buildValues(user)
);

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


Паттерны форматирования списков

ICU ListFormat используется для создания языковых правил перечислений:

intl.formatList(['яблоко', 'банан', 'груша'], { type: 'conjunction' });

Пользовательские паттерны здесь строятся вокруг выбора типа соединения:

  • conjunction
  • disjunction
  • unit

Абстрагирование:

const formatAndList = (intl, items) =>
  intl.formatList(items, { type: 'conjunction' });

Кастомные абстракции поверх IntlMessageFormat

На низком уровне FormatJS использует IntlMessageFormat, который парсит ICU-строки в AST и выполняет их интерпретацию.

Пользовательские паттерны могут быть реализованы как слой компиляции сообщений:

import IntlMessageFormat from 'intl-messageformat';

const mf = new IntlMessageFormat(
  "{count, plural, one {# файл} other {# файлов}}",
  'ru'
);

mf.format({ count: 5 });

Оборачивание этого слоя позволяет создавать доменные DSL:

const createPluralPattern = (one, other) =>
  `{count, plural, one {${one}} other {${other}}}`;

Динамические паттерны

Паттерны могут формироваться динамически на основе конфигурации:

const buildMessage = (label) =>
  `{value, number} ${label}`;

Это позволяет создавать адаптивные шаблоны без изменения исходного набора сообщений.


Паттерны с вложенной интерполяцией

FormatJS поддерживает вложенные структуры, где значения могут содержать заранее отформатированные строки:

intl.formatMessage(
  { id: 'nested.example' },
  {
    outer: intl.formatMessage({ id: 'inner.message' })
  }
);

Такая модель позволяет строить каскадные паттерны отображения.


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

В прикладных системах пользовательские паттерны часто отражают бизнес-логику:

  • статусы заказов
  • уровни уведомлений
  • состояния загрузки
  • финансовые категории

Пример:

const ORDER_STATUS = {
  created: "Создан",
  paid: "Оплачен",
  shipped: "Отправлен",
  delivered: "Доставлен"
};

Использование в FormatJS:

intl.formatMessage(
  { id: 'order.status' },
  { status: ORDER_STATUS[order.status] }
);

Интеграция паттернов в архитектуру приложения

Пользовательские паттерны становятся частью слоя i18n-инфраструктуры, где выделяются:

  • слой сообщений (ICU-шаблоны)
  • слой форматтеров (числа, даты, списки)
  • слой доменных паттернов (бизнес-логика)
  • слой UI-интеграции (react-intl компоненты)

Такая структура позволяет изолировать локализацию от бизнес-кода, сохраняя при этом гибкость расширения шаблонов без изменения ядра системы.