Hydration и изоморфность

Роль Globalize в изоморфных приложениях

Globalize проектируется как библиотека интернационализации, рассчитанная на работу как в браузере, так и в среде Node.js, что делает её применимой в изоморфной архитектуре (universal rendering). В таких приложениях один и тот же код отвечает за рендеринг на сервере и последующую работу на клиенте.

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

Globalize опирается на CLDR (Unicode Common Locale Data Repository), что означает необходимость явной загрузки языковых данных. Именно этот факт делает задачу hydration не тривиальной: данные локали должны быть идентичны на сервере и клиенте.


Проблема несогласованного рендера

Hydration в контексте изоморфных приложений — процесс “оживления” HTML, сгенерированного на сервере, путём привязки к нему клиентской логики. Для интернационализации критично, чтобы результат форматирования совпадал побуквенно.

Основные источники расхождений:

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

Даже минимальное отличие, например в формате даты (01/02/2026 против 2.1.2026), приводит к нарушению целостности hydration.


Базовая модель изоморфного использования Globalize

Globalize требует явной инициализации и загрузки зависимостей:

  • CLDR данные (numbers, dates, plural rules);
  • locale-specific supplemental data;
  • messages и паттерны форматирования.

На сервере формируется локализованный HTML:

Globalize.locale("ru");

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

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


Hydration как синхронизация состояния локали

Hydration в Globalize не является встроенным механизмом, а реализуется на уровне архитектуры приложения. Основная цель — исключить повторное вычисление форматированных строк, если они уже были рассчитаны на сервере.

Подходы:

  • передача сериализованного состояния локали;
  • повторное использование CLDR-данных без перезагрузки;
  • контроль версий языковых пакетов;
  • фиксированная инициализация Globalize до рендера компонентов.

Типичная стратегия:

  1. Сервер загружает CLDR.
  2. Выполняет форматирование.
  3. Встраивает результаты в HTML и сериализует состояние локали.
  4. Клиент восстанавливает локаль из переданного состояния.
  5. Hydration происходит без пересчёта критичных значений.

Сериализация локали и данных CLDR

CLDR данные составляют основу корректного форматирования. В изоморфной системе они должны быть:

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

Практика сериализации:

  • экспорт используемой локали (ru, en, de);
  • фиксация набора загруженных CLDR JSON;
  • передача в глобальный bootstrap-объект;
  • повторное использование на клиенте без сетевых запросов.

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


Изоморфная инициализация Globalize

Корректная архитектура требует выделенного bootstrap-слоя:

  • инициализация CLDR до создания компонентов;
  • установка locale до первого форматирования;
  • единая точка конфигурации.

Пример структуры:

import Globalize from "globalize";

import cldrData from "./cldr-data";

cldrData.forEach(Globalize.load);

Globalize.locale("ru");

Важный аспект: инициализация должна быть детерминированной. Любые условные загрузки (например, по времени выполнения или окружению) нарушают синхронизацию hydration.


Проблема повторного форматирования при hydration

Типичная ошибка изоморфных приложений — повторное вычисление значений на клиенте:

  • сервер: форматирует дату;
  • клиент: заново вызывает formatter;
  • результат отличается из-за различий окружения.

Даже если locale совпадает, различия могут возникнуть из-за:

  • разных версий ICU/CLDR;
  • различий временной зоны;
  • несинхронных настроек Intl API.

Решение — перенос результата форматирования в HTML как готового значения:

<span data-formatted-date="2 мая 2026">
  2 мая 2026
</span>

Клиент использует это значение как источник истины, избегая повторного расчёта.


Двойной режим: server-first vs client-first

В изоморфной архитектуре Globalize может работать в двух режимах:

Server-first (предпочтительный для SEO)

  • форматирование выполняется на сервере;
  • клиент только восстанавливает состояние;
  • минимизация вычислений в браузере.

Client-first

  • сервер отдаёт «сырые» данные;
  • клиент выполняет форматирование;
  • риск расхождений выше, но проще инфраструктура.

Globalize лучше всего проявляет себя в server-first модели, где CLDR строго контролируется.


Гидрация сообщений и plural rules

Особую сложность представляет интернационализация сообщений:

Globalize.messageFormatter({
  one: "{count} элемент",
  few: "{count} элемента",
  many: "{count} элементов"
});

При hydration важно, чтобы:

  • plural rules совпадали;
  • CLDR содержал одинаковые правила множественного числа;
  • вычисления происходили в одном окружении.

Иначе возможны ситуации, когда сервер выводит одну форму, а клиент пересчитывает её в другую.


Границы ответственности hydration

Hydration не должна включать:

  • повторную загрузку CLDR;
  • повторное создание форматтеров без причины;
  • пересчёт уже сериализованных значений.

Hydration должна:

  • привязать DOM к логике;
  • восстановить состояние локали;
  • обеспечить идентичность окружения.

Любое отклонение от этого принципа превращает интернационализацию в источник рассинхронизации UI.


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

Практические подходы для стабильной работы Globalize:

  • фиксация версии CLDR во всём приложении;
  • единый модуль инициализации локали;
  • кэширование форматтеров;
  • предсоздание всех необходимых formatter-инстансов на сервере;
  • использование immutable конфигурации локали.

Дополнительно важно минимизировать динамические изменения locale после hydration, поскольку это почти всегда приводит к пересборке форматтеров и перерисовке DOM.


Распространённые архитектурные ошибки

  • ленивый импорт CLDR на клиенте после рендера;
  • разный порядок загрузки языковых файлов;
  • использование Intl API параллельно с Globalize без синхронизации;
  • отсутствие фиксации timezone между сервером и клиентом;
  • пересоздание Globalize instance на каждом рендере.

Каждая из этих ошибок разрушает изоморфность и приводит к расхождению hydration.


Связь hydration и предсказуемости локализации

Изоморфность в контексте Globalize напрямую связана с детерминированностью:

  • одинаковый вход → одинаковый вывод;
  • одинаковая локаль → одинаковое форматирование;
  • одинаковые CLDR данные → одинаковые правила.

Hydration в такой системе выступает не как преобразование DOM, а как проверка консистентности между серверной и клиентской интерпретацией локализации.