Серверный рендеринг в Globalize требует предварительной подготовки среды локализации, поскольку библиотека опирается на CLDR-данные и не может функционировать без явной загрузки ресурсов. В отличие от клиентского окружения, где данные часто подгружаются динамически и кэшируются браузером, серверная среда требует строгого контроля над инициализацией, повторным использованием экземпляров и изоляцией локалей между запросами.
В серверной среде отсутствуют встроенные данные локализации, поэтому первым шагом становится явная регистрация CLDR-данных. Globalize не поставляется с готовыми локалями и требует загрузки минимального набора JSON-файлов:
Инициализация выполняется один раз при старте процесса:
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 currencies from "cldr-data/main/ru/currencies.json";
Globalize.load(likelySubtags);
Globalize.load(numbers);
Globalize.load(caGregorian);
Globalize.load(timeZoneNames);
Globalize.load(currencies);
Globalize.locale("ru");
Ключевым моментом является то, что загрузка CLDR должна происходить один раз. Повторная загрузка на каждый запрос приводит к существенным потерям производительности и увеличению потребления памяти.
Серверный рендеринг требует строгого разделения локализованных
контекстов. Globalize не хранит состояние в экземплярах в классическом
смысле, однако привязка locale() влияет на глобальное
состояние модуля. Это делает небезопасным использование единственного
глобального локального контекста при обработке параллельных
запросов.
Типовой подход заключается в создании фабрики форматтеров:
import Globalize from "globalize";
const getFormatter = (locale) => {
const g = new Globalize(locale);
return {
number: g.numberFormatter(),
date: g.dateFormatter(),
currency: g.currencyFormatter("USD")
};
};
Однако в реальности Globalize чаще используется через глобальную инициализацию локали, а не через инстансы. Поэтому более устойчивый подход заключается в кэшировании функций форматирования по локали:
const formattersCache = new Map();
function getFormatters(locale) {
if (formattersCache.has(locale)) {
return formattersCache.get(locale);
}
Globalize.locale(locale);
const formatters = {
number: Globalize.numberFormatter(),
date: Globalize.dateFormatter({ datetime: "medium" }),
currency: Globalize.currencyFormatter("EUR")
};
formattersCache.set(locale, formatters);
return formatters;
}
Такой подход минимизирует повторные вычисления и снижает накладные расходы при SSR.
Node.js выполняет запросы асинхронно, и глобальное состояние
Globalize.locale() может быть перезаписано между
асинхронными операциями. Это приводит к потенциальным race condition,
когда один запрос может получить локаль другого.
Для устранения этой проблемы используется изоляция через контекст запроса:
function renderRequest(req) {
const locale = req.headers["accept-language"]?.split(",")[0] || "en";
const globalize = new Globalize(locale);
const formatNumber = globalize.numberFormatter();
const formatDate = globalize.dateFormatter();
return {
price: formatNumber(12345.67),
date: formatDate(new Date())
};
}
В таком варианте исключается глобальное состояние, а каждая операция выполняется в изолированном контексте.
CLDR-данные являются значительными по размеру, и их загрузка в SSR-окружении требует аккуратного управления памятью. Практика загрузки всех локалей одновременно часто приводит к избыточному потреблению RAM.
Оптимизированный подход заключается в загрузке только необходимых локалей:
function loadLocaleData(locale) {
const basePath = `cldr-data/main/${locale}`;
Globalize.load(
require(`${basePath}/numbers.json`),
require(`${basePath}/ca-gregorian.json`)
);
}
В серверных приложениях с ограниченным числом локалей целесообразно заранее определить поддерживаемый набор и загрузить их при старте процесса.
Форматирование дат требует учёта временной зоны сервера. Globalize использует CLDR, но не управляет системным временем напрямую. Поэтому необходимо нормализовать входные данные:
const g = new Globalize("ru");
const formatDate = g.dateFormatter({
datetime: "long"
});
const timestamp = Date.now();
const result = formatDate(new Date(timestamp));
При мульти-региональных приложениях необходимо дополнительно учитывать IANA time zone, так как Globalize сам по себе не выполняет полноценную работу с часовыми поясами.
Форматирование валюты в SSR часто используется для предварительного рендеринга цен:
const g = new Globalize("ru");
const formatCurrency = g.currencyFormatter("RUB", {
style: "symbol"
});
const price = formatCurrency(1999.99);
Особенность серверного использования заключается в том, что формат валюты должен быть согласован с клиентом, иначе возможна гидрационная несостыковка.
Одной из критических проблем SSR является расхождение между серверным и клиентским форматированием. Globalize должен использовать идентичные данные CLDR и одинаковую локаль на обеих сторонах.
Несоответствие может возникнуть из-за:
Для предотвращения таких ситуаций применяется сериализация локали:
const state = {
locale: "ru",
currency: "RUB"
};
const html = `
<script>
window.__LOCALE_STATE__ = ${JSON.stringify(state)};
</script>
`;
На клиенте Globalize инициализируется на основе переданного состояния.
При использовании сборщиков важно контролировать включение CLDR-данных в бандл. Неправильная конфигурация приводит к попаданию всех локалей в серверный пакет.
Рекомендуется разделять серверную и клиентскую сборку:
externals: {
"cldr-data": "commonjs cldr-data"
}
Также важно избегать дублирования загрузки JSON-файлов при SSR-рендере модулей React или Vue.
Создание форматтеров является относительно дорогой операцией. Поэтому серверные приложения используют кэширование по типу:
const cache = new Map();
function getNumberFormatter(locale, options) {
const key = `${locale}-${JSON.stringify(options)}`;
if (!cache.has(key)) {
const g = new Globalize(locale);
cache.set(key, g.numberFormatter(options));
}
return cache.get(key);
}
Такой подход уменьшает нагрузку при высокой частоте запросов.
Globalize не является потокобезопасной библиотекой в контексте
изменения глобальной локали. При использовании
Globalize.locale() в SSR необходимо избегать параллельных
модификаций глобального состояния.
Поэтому предпочтение отдаётся либо:
Мульти-язычные приложения требуют поддержки нескольких локалей одновременно. Это достигается через предварительную загрузку и разделение кэша:
const locales = ["ru", "en", "de"];
locales.forEach(loadLocaleData);
const formatterCache = new Map();
function get(locale) {
if (!formatterCache.has(locale)) {
const g = new Globalize(locale);
formatterCache.set(locale, {
number: g.numberFormatter(),
date: g.dateFormatter(),
currency: g.currencyFormatter("USD")
});
}
return formatterCache.get(locale);
}
Такой подход позволяет обслуживать разные регионы без повторной инициализации CLDR.
Основные факторы, влияющие на производительность:
Наиболее затратной операцией остаётся инициализация форматтеров, поэтому их кэширование является ключевым элементом оптимизации.
Дополнительно ускорение достигается за счёт предварительного прогрева:
function warmup() {
const g = new Globalize("ru");
g.numberFormatter();
g.dateFormatter();
g.currencyFormatter("RUB");
}
В приложениях на Express или Fastify Globalize обычно инициализируется на уровне middleware:
app.use((req, res, next) => {
req.locale = req.headers["accept-language"] || "en";
req.globalize = new Globalize(req.locale);
next();
});
Далее форматирование выполняется через объект запроса, исключая глобальные конфликты.
При использовании React SSR важно, чтобы форматирование происходило до сериализации HTML, иначе возникает риск рассинхронизации с клиентской гидрацией.