Управление состоянием локали

Библиотека Globalize строит всю систему форматирования и парсинга данных вокруг понятия текущей локали. Локаль определяет правила отображения чисел, дат, валют, сообщений и единиц измерения. В отличие от простых утилит форматирования, здесь локаль — это управляемое состояние, влияющее на все операции экземпляра или глобального контекста.

В основе работы лежит связка:

  • CLDR-данные (Unicode Common Locale Data Repository)
  • выбранная локаль (например, en, ru, fr)
  • активный контекст Globalize

CLDR предоставляет структурированные правила локализации, а Globalize выступает как слой доступа к этим правилам.


Глобальная и экземплярная локаль

В Globalize существует два основных способа работы с локалью:

Глобальная локаль

Глобальная локаль задаётся через статический контекст и влияет на все операции, выполняемые без явного указания локали.

Globalize.locale("ru");

Globalize.formatDate(new Date(), { datetime: "medium" });
Globalize.formatNumber(1234567.89);

После установки "ru" все форматирования начинают использовать русские правила: разделители, порядок дат, названия месяцев и т.д.

Ключевая особенность: глобальная локаль изменяет поведение всей библиотеки в текущем контексте выполнения.


Локаль экземпляра

Альтернативный подход — создание экземпляра Globalize с фиксированной локалью:

const gRu = new Globalize("ru");
const gEn = new Globalize("en");

gRu.formatNumber(12345.67);
gEn.formatNumber(12345.67);

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


Управление сменой локали в рантайме

Смена локали — это операция, влияющая на все последующие вызовы форматирования.

Globalize.locale("en");

Globalize.formatNumber(1000); // 1,000

Globalize.locale("de");

Globalize.formatNumber(1000); // 1.000

Изменение происходит мгновенно, так как библиотека не хранит “снимки” состояния форматирования — только ссылку на текущую локаль.

Важная особенность: уже созданные экземпляры new Globalize(locale) не изменяются при смене глобальной локали.


Приоритет локали: глобальная, явная, экземплярная

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

  1. Явно переданная локаль в метод
  2. Локаль экземпляра
  3. Глобальная локаль (Globalize.locale())

Пример:

Globalize.locale("ru");

const g = new Globalize("en");

g.formatNumber(1000);          // английская локаль (экземпляр)
g.formatNumber(1000, "fr");    // французская локаль (явная)

Явная локаль всегда перекрывает любые другие уровни.


Управление локалью через CLDR загрузку

Globalize не содержит локализованных данных по умолчанию. Локаль становится активной только после загрузки соответствующих CLDR-фрагментов.

import likelySubtags from "cldr-data/supplemental/likelySubtags.json";
import numbers from "cldr-data/main/ru/numbers.json";
import timeZoneNames from "cldr-data/main/ru/timeZoneNames.json";

Globalize.load(likelySubtags);
Globalize.load(numbers);
Globalize.load(timeZoneNames);

Globalize.locale("ru");

Если локаль установлена без загрузки данных, форматирование может привести к ошибкам или fallback-поведению.


Fallback-механизм локалей

Globalize поддерживает цепочку fallback-локалей. Например:

  • pt-BR
  • fallback → pt
  • fallback → en

Если данных для конкретной локали недостаточно, система автоматически поднимается по цепочке.

Globalize.locale("pt-BR");

При отсутствии части CLDR-данных будут использованы базовые правила pt.


Изоляция состояния локали

Важный аспект архитектуры — отсутствие скрытого глобального состояния внутри экземпляров.

Поведение экземпляра

const g = new Globalize("ru");

Globalize.locale("en");

g.formatNumber(1000);

Результат остаётся русским, так как экземпляр хранит локаль отдельно.

Это делает возможными сценарии:

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

Смена локали в динамических интерфейсах

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

Типичный поток:

function setLanguage(lang) {
  Globalize.locale(lang);
  renderUI();
}

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


Локаль и форматирование сообщений

Globalize использует локаль не только для чисел и дат, но и для сообщений.

Globalize.loadMessages({
  ru: {
    greeting: "Привет, {name}!"
  },
  en: {
    greeting: "Hello, {name}!"
  }
});
Globalize.locale("ru");
Globalize.messageFormatter("greeting")({ name: "Иван" });

Смена локали автоматически переключает набор сообщений.


Согласованность локали в разных форматах

Одна и та же локаль влияет на несколько подсистем:

  • формат чисел (formatNumber)
  • формат дат (formatDate)
  • формат валют (formatCurrency)
  • сообщения (messageFormatter)
  • парсинг (parseNumber, parseDate)
Globalize.locale("de");

Globalize.formatNumber(1234.56);
Globalize.formatDate(new Date());
Globalize.formatCurrency(99.99, "EUR");

Единая локаль гарантирует согласованное поведение всех форматтеров.


Проблемы глобального состояния

Использование глобальной локали может приводить к побочным эффектам:

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

Пример проблемного сценария:

Globalize.locale("ru");

setTimeout(() => {
  Globalize.locale("en");
}, 0);

Globalize.formatNumber(1000);

Результат зависит от тайминга выполнения.


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

Более стабильная модель — использование экземпляров:

function formatPrice(value, locale) {
  const g = new Globalize(locale);
  return g.formatCurrency(value, "USD");
}

Каждый вызов получает собственный контекст, исключая влияние внешнего состояния.


Связь локали и загрузки модулей CLDR

Локаль в Globalize не является просто строкой идентификатора. Она активирует набор загруженных данных:

  • числа (numbers)
  • календарь (ca-gregorian)
  • часовые пояса (timeZoneNames)
  • валюты (currencies)
  • термины времени (dateFields)

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


Кэширование и локаль

Globalize использует кэширование форматтеров внутри локали. Это означает:

  • форматтеры создаются один раз на локаль
  • повторное использование ускоряет операции
  • смена локали инвалидирует кэш
Globalize.locale("en");
Globalize.formatNumber(1000);

Globalize.locale("ru");
Globalize.formatNumber(1000);

Каждая локаль имеет собственный набор кэшированных функций.


Локаль и поток выполнения

JavaScript выполняется в однопоточном контексте, однако асинхронность создаёт иллюзию параллельных состояний.

Globalize не защищает глобальную локаль от конкурентного изменения, поэтому архитектурно важно:

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

Явная передача локали как стратегия

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

Globalize.formatNumber(1000, { locale: "fr" });

или через экземпляр:

new Globalize("fr").formatNumber(1000);

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