Хуки useIntl, useFormatMessage

Контекст международализации в FormatJS

FormatJS предоставляет унифицированный подход к интернационализации приложений, объединяя форматирование сообщений, дат, чисел и плюрализации в рамках единого API. В React-экосистеме ключевым механизмом доступа к этим возможностям становятся хуки, работающие поверх intl-контекста.

Основой является IntlProvider, который прокидывает объект intl во всё дерево компонентов. Этот объект содержит методы форматирования и текущую локаль, а хуки лишь предоставляют удобный доступ к нему без необходимости использования injectIntl или HOC.


useIntl: доступ к низкоуровневому API интернационализации

Сигнатура и назначение

useIntl возвращает экземпляр объекта intl, содержащий полный набор методов FormatJS:

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

Главная задача хука — предоставить прямой доступ к API интернационализации внутри функциональных компонентов.


Базовое использование

import { useIntl } from 'react-intl';

function Header() {
  const intl = useIntl();

  return (
    <h1>
      {intl.formatMessage({
        id: 'app.title',
        defaultMessage: 'Главная страница'
      })}
    </h1>
  );
}

useIntl не принимает аргументов и возвращает один и тот же объект intl в рамках текущего контекста.


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

Основной сценарий использования — работа с formatMessage.

const intl = useIntl();

const label = intl.formatMessage(
  {
    id: 'button.save',
    defaultMessage: 'Сохранить'
  },
  {
    filename: 'document.txt'
  }
);

Второй аргумент позволяет передавать параметры интерполяции:

{
  "button.save": "Сохранить файл {filename}"
}

Результат: Сохранить файл document.txt


Форматирование чисел и дат

const intl = useIntl();

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

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

Эти методы используют Intl.NumberFormat и Intl.DateTimeFormat под капотом, обеспечивая локализованное форматирование без ручной настройки.


Плюрализация через formatMessage

intl.formatMessage(
  {
    id: 'cart.items',
    defaultMessage: 'В корзине {count, plural, one {# товар} few {# товара} many {# товаров} other {# товаров}}'
  },
  {
    count: 3
  }
);

ICU Message Syntax позволяет выражать сложные правила без внешней логики.


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

Стабильность объекта intl

Объект intl стабилен в пределах одного рендера локали. Однако изменение locale в IntlProvider приводит к пересозданию контекста и повторному рендеру компонентов.

Оптимизация

Частое вызовы formatMessage внутри render-функции допустимы, но при больших списках предпочтительнее мемоизация:

const intl = useIntl();

const columns = useMemo(() => [
  intl.formatMessage({ id: 'table.name' }),
  intl.formatMessage({ id: 'table.price' })
], [intl]);

useFormatMessage: специализированный хук для сообщений

Назначение

useFormatMessage — более узкий и декларативный хук, предоставляющий функцию форматирования сообщений без необходимости обращения к объекту intl.

Он предназначен для упрощения кода, когда требуется только formatMessage.


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

import { useFormatMessage } from 'react-intl';

function Button() {
  const formatMessage = useFormatMessage();

  return (
    <button>
      {formatMessage({
        id: 'button.submit',
        defaultMessage: 'Отправить'
      })}
    </button>
  );
}

Отличие от useIntl

Характеристика useIntl useFormatMessage
Доступ к API Полный intl объект Только formatMessage
Гибкость Максимальная Ограниченная
Размер кода Больше Меньше
Сценарий Сложная интернационализация Простые текстовые вставки

Использование с параметрами

const formatMessage = useFormatMessage();

const text = formatMessage(
  {
    id: 'greeting.user',
    defaultMessage: 'Привет, {name}'
  },
  {
    name: 'Alex'
  }
);

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

Хук удобно использовать в UI-библиотеках:

function UserCard({ name }) {
  const t = useFormatMessage();

  return (
    <div>
      {t({ id: 'user.name', defaultMessage: 'Имя' })}: {name}
    </div>
  );
}

Сравнение подходов внутри архитектуры приложения

Централизованный доступ через useIntl

Используется, когда требуется:

  • работа с датами и числами
  • сложная логика форматирования
  • доступ к locale, timeZone
  • кастомные formatter-ы

Локализованный доступ через useFormatMessage

Используется, когда:

  • требуется только текстовая интерполяция
  • важна краткость кода
  • нет необходимости в дополнительных методах intl

Работа с ICU-сообщениями

Оба хука опираются на единый формат ICU Message Syntax.

Интерполяция

{t({ id: 'order.summary', defaultMessage: 'Заказ {id} на сумму {price}' }, { id: 42, price: '$10' })}

Условные конструкции

intl.formatMessage({
  id: 'status.message',
  defaultMessage: '{status, select, success {Успешно} error {Ошибка} other {Неизвестно}}'
}, {
  status: 'success'
});

Плюрализация

formatMessage({
  id: 'files.count',
  defaultMessage: '{count, plural, one {# файл} few {# файла} many {# файлов} other {# файлов}}'
}, {
  count: 5
});

Поведение при смене локали

При обновлении locale в IntlProvider:

  • пересоздаётся объект intl
  • все компоненты, использующие useIntl и useFormatMessage, перерисовываются
  • все форматированные строки автоматически адаптируются

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


Интеграция с TypeScript

Типизация useIntl

import { useIntl, IntlShape } from 'react-intl';

const intl: IntlShape = useIntl();

Типизация сообщений

type Messages = {
  'app.title': string;
  'button.save': string;
};

Хотя FormatJS не требует строгой типизации ключей, в крупных проектах часто применяется генерация типов из JSON-файлов локализации.


Ошибки и особенности

Отсутствие IntlProvider

Использование хуков без провайдера приводит к ошибке выполнения:

  • useIntl требует контекст react-intl
  • аналогично useFormatMessage

Частое создание объектов сообщений

Нежелательно создавать объект сообщения внутри глубоко вложенных render-функций без мемоизации:

intl.formatMessage({ id: 'key' }); // допустимо

но в больших списках предпочтительнее:

const msg = useMemo(
  () => intl.formatMessage({ id: 'key' }),
  [intl]
);

Потенциальные узкие места

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

Архитектурные паттерны использования

Паттерн “локальный форматтер”

function Component() {
  const t = useFormatMessage();

  return <div>{t({ id: 'text' })}</div>;
}

Минимальная зависимость от intl-API.


Паттерн “универсальный intl”

function Component() {
  const intl = useIntl();

  return intl.formatNumber(1000);
}

Используется для компонентов с разноплановой локализацией.


Паттерн “гибридный”

function Component() {
  const intl = useIntl();
  const t = useFormatMessage();

  return (
    <div>
      {t({ id: 'label' })}
      {intl.formatNumber(1000)}
    </div>
  );
}

Позволяет разделять ответственность внутри компонента.