React и Intl API

Интернационализация в современных фронтенд-приложениях опирается на два слоя: прикладной (React-логика отображения) и платформенный (возможности браузера и ECMAScript). В браузерной среде ключевую роль играет Intl API — встроенный набор объектов для форматирования дат, чисел, валют, сравнения строк и работы с локалями. Его применение в React позволяет отказаться от внешних тяжёлых библиотек в части базового форматирования и перенести часть логики в стандарт среды выполнения.

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


Intl API как основа локализации в интерфейсе

Intl API предоставляет набор конструкторов, каждый из которых решает конкретный класс задач.

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

Intl.NumberFormat используется для отображения чисел с учётом локали:

  • разделители тысяч
  • десятичные знаки
  • процентные значения
  • единицы измерения
const formatter = new Intl.NumberFormat('ru-RU');
formatter.format(1234567.89);
// "1 234 567,89"

Для валют:

const usd = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});

usd.format(99.99);
// "$99.99"

Ключевая особенность — отсутствие необходимости ручного управления форматами: локаль и правила кодируются в движке.


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

Intl.DateTimeFormat обеспечивает локализованное отображение дат.

const formatter = new Intl.DateTimeFormat('ru-RU', {
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

formatter.format(new Date());
// "26 мая 2026 г."

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

  • short
  • medium
  • long
  • full

Также доступны часы, минуты, секунды, часовые пояса.


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

Intl.RelativeTimeFormat применяется для интерфейсов с динамическими временными метками:

const rtf = new Intl.RelativeTimeFormat('ru', { numeric: 'auto' });

rtf.format(-1, 'day');
// "вчера"

rtf.format(3, 'hour');
// "через 3 часа"

Этот API особенно полезен в лентах активности, уведомлениях и чатах.


Лексикографическое сравнение строк

Intl.Collator обеспечивает корректную сортировку строк с учётом языка:

const collator = new Intl.Collator('ru', { sensitivity: 'base' });

['ёж', 'яблоко', 'ель'].sort(collator.compare);

Без него сортировка по Unicode-кодам приводит к некорректным результатам для многих языков.


Интеграция Intl API в React-компоненты

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

Создание экземпляров Intl.* — операция относительно дорогая. В React-компонентах при каждом рендере это может привести к лишним вычислениям.

Базовый подход — мемоизация:

const formatter = useMemo(() => {
  return new Intl.NumberFormat('ru-RU');
}, []);

Такой подход фиксирует форматтер на уровне жизненного цикла компонента.


Изоляция форматирования в утилиты

Часто форматирование выносится за пределы компонентов:

export const formatPrice = (value, locale) => {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: 'USD'
  }).format(value);
};

Это упрощает тестирование и снижает связность UI.


Контекст локали

В многоязычных приложениях используется глобальный контекст:

const LocaleContext = createContext('en-US');

Компоненты получают текущую локаль и используют её для создания форматтеров.

const locale = useContext(LocaleContext);

const formatter = useMemo(() => {
  return new Intl.NumberFormat(locale);
}, [locale]);

Изменение локали автоматически приводит к пересозданию форматтеров и обновлению интерфейса.


Динамическая локализация интерфейсов

Числовые значения в UI

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

  • цены
  • количество элементов
  • прогресс
function Price({ value }) {
  const locale = useContext(LocaleContext);

  const formatted = useMemo(() => {
    return new Intl.NumberFormat(locale, {
      style: 'currency',
      currency: 'EUR'
    }).format(value);
  }, [value, locale]);

  return <span>{formatted}</span>;
}

Даты в компонентах отображения

function DateLabel({ date }) {
  const locale = useContext(LocaleContext);

  const formatted = useMemo(() => {
    return new Intl.DateTimeFormat(locale, {
      year: 'numeric',
      month: 'short',
      day: 'numeric'
    }).format(new Date(date));
  }, [date, locale]);

  return <time>{formatted}</time>;
}

Относительное время в реактивных интерфейсах

Для обновления относительного времени требуется периодический пересчёт:

function useRelativeTime(date) {
  const locale = useContext(LocaleContext);
  const [, forceUpdate] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      forceUpdate(x => x + 1);
    }, 60000);

    return () => clearInterval(interval);
  }, []);

  return useMemo(() => {
    const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
    const diff = Math.round((new Date(date) - new Date()) / 60000);

    return rtf.format(diff, 'minute');
  }, [date, locale]);
}

Производительность и стратегические оптимизации

Кэширование форматтеров

Intl-объекты зависят от локали и опций. Это делает их идеальными кандидатами для кэширования:

const cache = new Map();

function getNumberFormatter(locale, options) {
  const key = locale + JSON.stringify(options);

  if (!cache.has(key)) {
    cache.set(key, new Intl.NumberFormat(locale, options));
  }

  return cache.get(key);
}

Такой подход уменьшает накладные расходы при частых рендерах списков.


Минимизация пересозданий в списках

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

const formatter = useMemo(() => {
  return new Intl.NumberFormat(locale);
}, [locale]);

return items.map(item => (
  <div key={item.id}>
    {formatter.format(item.value)}
  </div>
));

Форматирование и серверный рендеринг

При SSR (Server-Side Rendering) возникают особенности:

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

Стратегия стабилизации:

  • фиксированная локаль на сервере
  • передача локали в клиентский контекст
  • исключение случайных значений (например, “now”)
const locale = req.headers['accept-language'] || 'en-US';

Комбинирование Intl API с архитектурой i18n

Intl API решает форматирование, но не покрывает:

  • переводы строк
  • множественные формы текста
  • структурные различия языков

В React это приводит к разделению ответственности:

  • Intl API — формат данных
  • словари — текстовые ресурсы
  • компоненты — композиция вывода
const messages = {
  en: { items: 'Items' },
  ru: { items: 'Элементы' }
};

Обработка множественных форм через Intl.PluralRules

const plural = new Intl.PluralRules('ru');

const rule = plural.select(5);
// "many"

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

const forms = ['элемент', 'элемента', 'элементов'];

function formatItems(count) {
  const rule = plural.select(count);

  const index = {
    one: 0,
    few: 1,
    many: 2,
    other: 2
  }[rule];

  return `${count} ${forms[index]}`;
}

Инкапсуляция логики форматирования в UI-слое

В React часто выделяются специализированные компоненты:

  • FormattedNumber
  • FormattedDate
  • FormattedCurrency

Их задача — скрыть детали Intl API:

function FormattedCurrency({ value, currency }) {
  const locale = useContext(LocaleContext);

  const formatted = useMemo(() => {
    return new Intl.NumberFormat(locale, {
      style: 'currency',
      currency
    }).format(value);
  }, [value, currency, locale]);

  return <span>{formatted}</span>;
}

Ограничения Intl API в интерфейсной разработке

Несмотря на широкие возможности, существуют ограничения:

  • отсутствие встроенной поддержки перевода текста
  • ограниченные механизмы форматирования сложных грамматических конструкций
  • зависимость от окружения (браузер/Node.js версии ICU)

Это определяет роль Intl API как низкоуровневого слоя, поверх которого строятся i18n-фреймворки и архитектуры приложений на базе React.