Императивное форматирование

Императивное форматирование в контексте FormatJS опирается на прямое использование API форматирования без декларативных конструкций React-компонентов. В отличие от <FormattedMessage />, <FormattedNumber /> и других компонентов, императивный подход предполагает явное создание экземпляра международного контекста и вызов методов форматирования напрямую в коде.

Основой служит пакет react-intl, предоставляющий API createIntl, который позволяет формировать локализованные сообщения вне React-дерева.


Создание императивного контекста intl

Для работы вне компонентов требуется инициализация объекта intl, содержащего все методы форматирования.

Ключевые сущности:

  • createIntl — создание экземпляра intl
  • createIntlCache — кэширование сообщений и форматтеров
  • IntlShape — типизация объекта intl

Базовая инициализация

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

const cache = createIntlCache();

const intl = createIntl(
  {
    locale: 'ru-RU',
    messages: {
      greeting: 'Привет, {name}',
      balance: 'Баланс: {value, number, currency}'
    }
  },
  cache
);

Объект intl теперь содержит полный набор методов:

  • formatMessage
  • formatNumber
  • formatDate
  • formatTime
  • formatRelativeTime
  • formatPlural

Форматирование сообщений через formatMessage

Метод formatMessage является центральной точкой императивного API. Он позволяет получать локализованные строки без JSX.

Простая подстановка значений

const text = intl.formatMessage(
  { id: 'greeting' },
  { name: 'Алексей' }
);

console.log(text); // Привет, Алексей

Сообщение может быть определено не только в messages, но и динамически:

const text = intl.formatMessage(
  {
    id: 'welcome',
    defaultMessage: 'Добро пожаловать, {name}'
  },
  { name: 'Мария' }
);

Императивное числовое форматирование

Метод formatNumber использует стандарты Intl.NumberFormat, но добавляет слой абстракции FormatJS.

Базовый пример

const result = intl.formatNumber(123456.78);

console.log(result);

Формат валюты

const price = intl.formatNumber(1999.99, {
  style: 'currency',
  currency: 'USD'
});

Поддерживаются все параметры Intl.NumberFormat:

  • style
  • currency
  • minimumFractionDigits
  • maximumFractionDigits
  • useGrouping

Форматирование дат и времени

Императивное форматирование дат осуществляется через formatDate и formatTime.

Формат даты

const date = intl.formatDate(new Date(), {
  year: 'numeric',
  month: 'long',
  day: '2-digit'
});

Формат времени

const time = intl.formatTime(new Date(), {
  hour: '2-digit',
  minute: '2-digit'
});

Эти методы являются обёртками над Intl.DateTimeFormat.


Относительное время

Метод formatRelativeTime позволяет выражать временные интервалы относительно текущего момента.

const relative = intl.formatRelativeTime(-3, 'day');

console.log(relative); // 3 дня назад

Параметры:

  • значение (число)
  • единица измерения: 'second' | 'minute' | 'hour' | 'day' | 'month' | 'year'

Императивное управление множественным числом

Pluralization реализуется через formatPlural, который возвращает категорию множественного числа для текущей локали.

const category = intl.formatPlural(5);

console.log(category); // few / many / other (зависит от локали)

Применение чаще всего комбинируется с formatMessage:

const message = intl.formatMessage(
  {
    id: 'items'
  },
  {
    count: 5,
    type: intl.formatPlural(5)
  }
);

Использование formatters вне React-проекта

Императивный API особенно полезен в следующих сценариях:

  • утилитные функции форматирования
  • серверный рендеринг
  • логирование локализованных сообщений
  • работа в Node.js без React
  • обработка данных в middleware

Пример утилиты форматирования

export function formatUserBalance(intl, balance) {
  return intl.formatMessage(
    {
      id: 'balance',
      defaultMessage: 'Баланс: {value}'
    },
    {
      value: intl.formatNumber(balance, {
        style: 'currency',
        currency: 'EUR'
      })
    }
  );
}

Передача intl в бизнес-логику

Императивный подход подразумевает явную инъекцию intl в функции и сервисы.

Пример сервисного слоя

class InvoiceService {
  constructor(intl) {
    this.intl = intl;
  }

  formatInvoiceTotal(amount) {
    return this.intl.formatMessage(
      {
        id: 'invoice.total',
        defaultMessage: 'Итого: {amount}'
      },
      {
        amount: this.intl.formatNumber(amount, {
          style: 'currency',
          currency: 'USD'
        })
      }
    );
  }
}

Такой подход обеспечивает:

  • тестируемость
  • независимость от React
  • контроль над локализацией в доменной логике

Создание изолированных intl-инстансов

В системах с мультиарендностью или динамической сменой локали создаются отдельные экземпляры intl.

function createUserIntl(locale, messages) {
  const cache = createIntlCache();

  return createIntl(
    {
      locale,
      messages
    },
    cache
  );
}

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


Динамическая смена локали

Императивный API позволяет пересоздавать intl без перезагрузки приложения.

let intl = createIntl({ locale: 'en', messages }, cache);

function switchLocale(locale, messages) {
  intl = createIntl({ locale, messages }, cache);
}

Особенности кеширования

createIntlCache снижает накладные расходы на создание форматтеров:

  • повторное использование Intl.* объектов
  • оптимизация форматирования при множественных вызовах
  • уменьшение GC нагрузки

Без кеша каждое форматирование приводит к пересозданию внутренних структур.


Отличия от декларативного подхода

Императивный формат отличается от React-компонентного следующим:

  • отсутствие JSX-обёрток
  • явный контроль вызова форматирования
  • возможность использования вне UI-слоя
  • пригодность для серверных и утилитных сценариев

Декларативный подход:

<FormattedMessage id="greeting" values={{ name }} />

Императивный подход:

intl.formatMessage({ id: 'greeting' }, { name });

Ограничения императивного API

Несмотря на гибкость, существуют особенности:

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

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


Интеграция с серверным окружением

В Node.js императивное форматирование часто применяется для генерации текстов:

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

const cache = createIntlCache();

const intl = createIntl({
  locale: 'ru',
  messages: {
    report: 'Отчёт за {date}'
  }
}, cache);

const output = intl.formatMessage(
  { id: 'report' },
  { date: intl.formatDate(new Date()) }
);

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


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

Императивный API позволяет вкладывать форматтеры друг в друга:

const message = intl.formatMessage(
  {
    id: 'summary',
    defaultMessage: '{count} товаров на сумму {total}'
  },
  {
    count: 3,
    total: intl.formatNumber(1500, {
      style: 'currency',
      currency: 'USD'
    })
  }
);

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


Использование в условиях отсутствия React

Императивный форматировщик становится базовым инструментом в средах:

  • чистый JavaScript
  • Node.js микросервисы
  • CLI-утилиты
  • edge-функции

Он обеспечивает доступ к инфраструктуре FormatJS без зависимости от UI-фреймворков, сохраняя совместимость с Intl-стандартами ECMAScript.