Динамическая смена языка

Локаль как центральная единица форматирования

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

  • ru-RU — русский (Россия)
  • en-US — английский (США)
  • de-DE — немецкий (Германия)

Каждый форматтер в Intl принимает локаль как первый аргумент:

const formatter = new Intl.DateTimeFormat('ru-RU');

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


Принцип неизменяемости форматтеров

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

let locale = 'ru-RU';

const formatter = new Intl.NumberFormat(locale);

locale = 'en-US';

formatter.format(1234567.89); // формат всё равно по ru-RU

Это поведение критично для производительности: форматтеры кэшируются движком и не пересобираются автоматически.


Проблема динамической смены языка

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

Типичная задача:

  • переключение интерфейса без перезагрузки страницы
  • изменение формата даты, чисел и сообщений в реальном времени

Без пересоздания Intl-объектов данные остаются на старом языке.


Централизованное хранение текущей локали

Базовый подход заключается в выделении глобального состояния локали:

let currentLocale = 'ru-RU';

function setLocale(newLocale) {
  currentLocale = newLocale;
}

Любой форматтер создаётся на основе этого значения:

function formatDate(date) {
  return new Intl.DateTimeFormat(currentLocale).format(date);
}

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


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

Наиболее важный механизм динамической локализации — обновление всех зависимых экземпляров Intl.

let currentLocale = 'ru-RU';

let numberFormatter = new Intl.NumberFormat(currentLocale);
let dateFormatter = new Intl.DateTimeFormat(currentLocale);

function setLocale(newLocale) {
  currentLocale = newLocale;

  numberFormatter = new Intl.NumberFormat(currentLocale);
  dateFormatter = new Intl.DateTimeFormat(currentLocale);
}

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


Ленивая генерация форматтеров

Вместо хранения множества экземпляров часто используется ленивое создание:

let currentLocale = 'ru-RU';

function getNumberFormatter() {
  return new Intl.NumberFormat(currentLocale);
}

function formatNumber(value) {
  return getNumberFormatter().format(value);
}

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


Кэширование с учётом локали

Оптимизированный вариант — кэш по ключу локали.

const numberFormatCache = new Map();

function getNumberFormatter(locale) {
  if (!numberFormatCache.has(locale)) {
    numberFormatCache.set(locale, new Intl.NumberFormat(locale));
  }
  return numberFormatCache.get(locale);
}

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

function formatNumber(value, locale) {
  return getNumberFormatter(locale).format(value);
}

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


Синхронизация UI и форматирования

Динамическая смена языка в приложении требует согласованного обновления:

  • текстовых строк
  • дат и времени
  • числовых форматов
  • сортировки списков

Пример:

function renderPrice(value) {
  return new Intl.NumberFormat(currentLocale, {
    style: 'currency',
    currency: currentLocale === 'ru-RU' ? 'RUB' : 'USD'
  }).format(value);
}

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


Intl и сортировка при смене языка

Intl.Collator отвечает за правила сортировки строк, которые также зависят от локали.

let currentLocale = 'ru-RU';

let collator = new Intl.Collator(currentLocale);

function sortItems(items) {
  return items.sort((a, b) => collator.compare(a, b));
}

После смены языка требуется обновление экземпляра:

function setLocale(newLocale) {
  currentLocale = newLocale;
  collator = new Intl.Collator(currentLocale);
}

Работа с датами и календарными нюансами

Intl.DateTimeFormat учитывает локальные особенности:

  • порядок компонентов даты
  • формат времени (12/24 часа)
  • названия месяцев и дней недели
let dateFormatter = new Intl.DateTimeFormat(currentLocale, {
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

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


Оптимизация при частых переключениях языка

При частой смене локали важны следующие принципы:

  • минимизация пересоздания форматтеров
  • кэширование по локали и опциям
  • разделение форматтеров по типам (date, number, currency)

Пример структурированного кэша:

const cache = {
  number: new Map(),
  date: new Map()
};

function getFormatter(type, locale) {
  if (!cache[type].has(locale)) {
    if (type === 'number') {
      cache[type].set(locale, new Intl.NumberFormat(locale));
    }
    if (type === 'date') {
      cache[type].set(locale, new Intl.DateTimeFormat(locale));
    }
  }
  return cache[type].get(locale);
}

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

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

  • обновление состояния locale
  • повторный вызов форматтеров
  • перерасчёт вычисляемых значений

Принцип:

function useLocale(locale) {
  return {
    formatNumber: (value) => new Intl.NumberFormat(locale).format(value),
    formatDate: (date) => new Intl.DateTimeFormat(locale).format(date)
  };
}

Каждое изменение локали автоматически создаёт новую «область форматирования».


Особенности поведения в браузере

Некоторые движки могут:

  • оптимизировать повторное создание форматтеров
  • кешировать внутренние таблицы локалей
  • переиспользовать ICU-данные

Однако логика разработчика не должна зависеть от этих оптимизаций. Поведение всегда строится на явном пересоздании объектов.


Типичные ошибки при динамической смене языка

1. Хранение устаревших форматтеров

const formatter = new Intl.NumberFormat(locale);

function updateLocale(newLocale) {
  locale = newLocale;
  // formatter не обновлён
}

2. Смешивание локалей в одном кэше

Кэш без учёта locale приводит к некорректному отображению данных.

3. Использование глобального formatter без пересоздания

Приводит к рассинхронизации интерфейса.


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

Intl учитывает не только локаль, но и набор опций:

new Intl.NumberFormat(locale, {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

При смене языка требуется учитывать, что комбинация locale + options определяет уникальный форматтер, а значит ключ кэша должен включать оба параметра.


Динамическая локализация строк через Intl (PluralRules)

Intl.PluralRules также зависит от локали:

let pluralRules = new Intl.PluralRules(currentLocale);

function getPluralForm(n) {
  return pluralRules.select(n);
}

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


Общая модель управления локалью

Динамическая смена языка в приложении на базе Intl строится на трёх принципах:

  • локаль хранится централизованно
  • все форматтеры создаются из текущей локали
  • любые изменения локали приводят к обновлению зависимых экземпляров

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