React и Globalize

Использование Globalize в React-приложениях строится вокруг идеи отделения локализационной логики от UI-компонентов. React отвечает за рендеринг и управление состоянием, Globalize — за форматирование дат, чисел, валют, склонений и сообщений на основе CLDR-данных.

Ключевая особенность архитектуры заключается в том, что Globalize не является «реактивной» библиотекой. Изменение локали требует явного обновления состояния React, что делает интеграцию предсказуемой и прозрачной.


Базовая инициализация Globalize в React-проекте

Перед использованием Globalize необходимо загрузить CLDR-данные и инициализировать локали.

import Globalize from "globalize";
import likelySubtags from "cldr-core/supplemental/likelySubtags.json";
import plurals from "cldr-core/supplemental/plurals.json";
import numbers from "cldr-numbers-full/main/ru/numbers.json";
import currency from "cldr-numbers-full/main/ru/currencies.json";
import caGregorian from "cldr-dates-full/main/ru/ca-gregorian.json";
import timeZoneNames from "cldr-dates-full/main/ru/timeZoneNames.json";

Globalize.load(
  likelySubtags,
  plurals,
  numbers,
  currency,
  caGregorian,
  timeZoneNames
);

Globalize.locale("ru");

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


Хранение и управление текущей локалью

В React-проектах локаль обычно хранится в состоянии верхнего уровня или в контексте.

import React, { createContext, useContext, useState } from "react";
import Globalize from "globalize";

const LocaleContext = createContext();

export function LocaleProvider({ children }) {
  const [locale, setLocale] = useState("ru");

  const changeLocale = (nextLocale) => {
    Globalize.locale(nextLocale);
    setLocale(nextLocale);
  };

  return (
    <LocaleContext.Provider value={{ locale, changeLocale }}>
      {children}
    </LocaleContext.Provider>
  );
}

export const useLocale = () => useContext(LocaleContext);

Важный момент: вызов Globalize.locale() изменяет глобальное состояние библиотеки, поэтому синхронизация с React-состоянием обязательна.


Форматирование чисел и валют в компонентах

Globalize предоставляет фабрики форматтеров, которые создаются один раз для конкретной локали.

import Globalize from "globalize";

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

export function Price({ value }) {
  return (
    <span>
      {currencyFormatter(value)}
    </span>
  );
}

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


Реактивное обновление форматтеров через хуки

Для корректной работы с динамической сменой локали используется useMemo или useEffect.

import React, { useMemo } from "react";
import Globalize from "globalize";
import { useLocale } from "./LocaleProvider";

export function useNumberFormatter() {
  const { locale } = useLocale();

  return useMemo(() => {
    Globalize.locale(locale);
    return Globalize.numberFormatter();
  }, [locale]);
}

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

export function Stats({ value }) {
  const formatNumber = useNumberFormatter();

  return <div>{formatNumber(value)}</div>;
}

Форматирование дат в React-компонентах

Globalize поддерживает форматирование дат через CLDR-данные.

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

export function DateView({ date }) {
  return <time>{dateFormatter(new Date(date))}</time>;
}

При работе с React важно учитывать, что объект Date должен быть валидным и заранее нормализованным.


Поддержка множественных форм (plural rules)

Одной из ключевых возможностей Globalize является работа с правилами множественного числа.

const pluralGenerator = Globalize.pluralGenerator();

export function Message({ count }) {
  const form = pluralGenerator(count);

  const messages = {
    one: "сообщение",
    few: "сообщения",
    many: "сообщений",
    other: "сообщений"
  };

  return (
    <span>{count} {messages[form]}</span>
  );
}

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


Интеграция с форматированием сообщений

Globalize поддерживает ICU-подобные шаблоны через messageFormatter.

const messages = {
  welcome: "Добро пожаловать, {name}",
};

const messageFormatter = Globalize.messageFormatter(messages.welcome);

export function Welcome({ name }) {
  return <h1>{messageFormatter({ name })}</h1>;
}

Для масштабных приложений шаблоны обычно выносятся в отдельные JSON-файлы по локалям.


Переключение языка в интерфейсе

Смена локали требует обновления контекста и пересоздания форматтеров.

export function LanguageSwitcher() {
  const { locale, changeLocale } = useLocale();

  return (
    <select
      value={locale}
      onCha nge={(e) => changeLocale(e.target.value)}
    >
      <option value="ru">Русский</option>
      <option value="en">English</option>
      <option value="de">Deutsch</option>
    </select>
  );
}

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


Оптимизация производительности

Globalize создаёт форматтеры, которые могут быть дорогими при частом создании. В React важно минимизировать их пересоздание.

Практика:

  • кеширование форматтеров по локали
  • использование useMemo
  • вынесение форматтеров на уровень сервисов

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

const cache = new Map();

function getCurrencyFormatter(locale, currency) {
  const key = `${locale}-${currency}`;

  if (!cache.has(key)) {
    Globalize.locale(locale);
    cache.set(key, Globalize.currencyFormatter(currency));
  }

  return cache.get(key);
}

Использование в кастомных UI-компонентах

Компоненты-обёртки позволяют унифицировать форматирование.

export function Money({ value, currency }) {
  const { locale } = useLocale();

  const formatter = useMemo(() => {
    Globalize.locale(locale);
    return Globalize.currencyFormatter(currency);
  }, [locale, currency]);

  return <span>{formatter(value)}</span>;
}

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


Работа с асинхронной загрузкой CLDR

В крупных приложениях CLDR-данные могут загружаться динамически.

async function loadLocaleData(locale) {
  const [numbers, currencies] = await Promise.all([
    import(`cldr-numbers-full/main/${locale}/numbers.json`),
    import(`cldr-numbers-full/main/${locale}/currencies.json`)
  ]);

  Globalize.load(numbers, currencies);
  Globalize.locale(locale);
}

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


SSR и гидратация

При серверном рендеринге важно синхронизировать локаль между сервером и клиентом.

На сервере:

Globalize.locale("ru");
const formatted = Globalize.numberFormatter()(1234);

На клиенте:

Globalize.locale(window.__INITIAL_LOCALE__);

Несоответствие локали приводит к расхождению HTML при гидратации.


Архитектурная модель интеграции

Интеграция React и Globalize обычно строится по слоям:

  • слой UI (React компоненты)
  • слой локализации (Globalize)
  • слой контекста (LocaleProvider)
  • слой кеширования форматтеров

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