Globalize предназначена для построения многоязычных интерфейсов на основе данных CLDR и предоставляет единый API для форматирования чисел, дат, валют, сообщений и правил плюрализации. В SSR-архитектуре её поведение отличается от клиентского окружения прежде всего управлением локалями, загрузкой CLDR-данных и отсутствием глобального состояния браузера.
Server-Side Rendering в контексте JavaScript чаще всего реализуется через Node.js. Каждый запрос обрабатывается изолированно, что накладывает требования на международные библиотеки:
Globalize на каждый запрос или на
пул локалейGlobalize не хранит локаль глобально в классическом смысле, однако его конфигурация и загруженные CLDR-данные часто инициализируются на уровне приложения, что требует аккуратной архитектуры в SSR.
Globalize полностью зависит от данных Unicode CLDR. В SSR важно заранее определить стратегию загрузки:
Пример загрузки CLDR в Node.js:
import Globalize from "globalize";
import likelySubtags from "cldr-data/supplemental/likelySubtags.json";
import numbers from "cldr-data/main/ru/numbers.json";
import caGregorian from "cldr-data/main/ru/ca-gregorian.json";
import timeZoneNames from "cldr-data/main/ru/timeZoneNames.json";
import plurals from "cldr-data/supplemental/plurals.json";
// загрузка supplemental данных
Globalize.load(likelySubtags);
Globalize.load(plurals);
// загрузка локальных данных
Globalize.load(numbers);
Globalize.load(caGregorian);
Globalize.load(timeZoneNames);
В SSR важно выполнять эту загрузку один раз на процесс, иначе нагрузка на память и время старта становится критической.
Ключевая ошибка при использовании Globalize в серверной среде — создание глобальной локали:
// небезопасный подход
Globalize.locale("ru");
В многопоточном или многозапросном окружении это приводит к гонкам состояния.
Корректная модель — создание экземпляра через
Globalize(locale):
import Globalize from "globalize";
function getFormatter(locale) {
return new Globalize(locale);
}
Каждый запрос получает собственный экземпляр форматтера, что исключает перекрёстное влияние локалей.
const globalize = new Globalize("ru");
const numberFormatter = globalize.numberFormatter();
numberFormatter(1234567.89);
В SSR это часто используется для предрендеринга UI:
const dateFormatter = globalize.dateFormatter({
datetime: "medium"
});
dateFormatter(new Date());
Особенность SSR — дата должна быть вычислена с учётом локали пользователя, а не сервера.
В архитектуре SSR локаль обычно определяется на основе:
Accept-Language/ru/products)Пример:
function detectLocale(req) {
return req.headers["accept-language"]?.split(",")[0] || "en";
}
Далее локаль передаётся в слой рендеринга:
function renderPage(req) {
const locale = detectLocale(req);
const globalize = new Globalize(locale);
const formattedPrice = globalize.numberFormatter()(1999.99);
return `<div>${formattedPrice}</div>`;
}
Создание форматтеров дорогостоящая операция, особенно при большом количестве запросов. В SSR применяется кэширование по локали:
const formatterCache = new Map();
function getNumberFormatter(locale) {
if (!formatterCache.has(locale)) {
const g = new Globalize(locale);
formatterCache.set(locale, g.numberFormatter());
}
return formatterCache.get(locale);
}
Однако кэшировать следует аккуратно:
Globalize поддерживает ICU-подобные сообщения:
const messageFormatter = globalize.messageFormatter(
"You have {count, plural, one {# item} other {# items}}"
);
messageFormatter({ count: 5 });
В SSR это позволяет заранее формировать текст без дополнительной клиентской логики.
При использовании React серверный рендеринг часто строится через Node.js поток рендера. Globalize внедряется через контекст:
function createRenderContext(locale) {
const globalize = new Globalize(locale);
return {
formatNumber: globalize.numberFormatter(),
formatDate: globalize.dateFormatter()
};
}
Далее контекст передаётся в компоненты:
function Price({ value, ctx }) {
return `<span>${ctx.formatNumber(value)}</span>`;
}
Одна из критичных проблем SSR — расхождение между серверным и клиентским форматированием.
Если на сервере используется Globalize, на клиенте должна быть:
Иначе при гидратации возможны:
Решение — сериализация локали и конфигурации:
window.__LOCALE__ = "ru";
window.__CLDR_LOADED__ = true;
Для больших приложений загрузка всех локалей сразу неэффективна. Используется динамическая загрузка:
async function loadLocaleData(locale) {
const data = await import(`cldr-data/main/${locale}/numbers.json`);
Globalize.load(data);
}
Проблема SSR здесь — асинхронность перед рендером. Поэтому загрузка должна завершаться до отправки HTML.
В потоковом SSR важно заранее подготовить Globalize:
async function handleRequest(req, res) {
const locale = detectLocale(req);
await loadLocaleData(locale);
const globalize = new Globalize(locale);
res.end(render(globalize));
}
Основные узкие места SSR с Globalize:
Оптимизации:
SSR требует строгой изоляции:
Без этого возникают race conditions при параллельных запросах.
Типичная схема:
Такое разделение позволяет масштабировать SSR горизонтально без деградации качества локализации.