Библиотека Globalize строится поверх данных CLDR (Common Locale Data Repository), которые содержат огромный набор информации: форматы дат, чисел, валют, правила плюрализации, временные зоны и языковые особенности. Полный набор CLDR для всех локалей измеряется мегабайтами данных, что делает их загрузку целиком на старте приложения неэффективной.
Основная проблема без разделения:
Code splitting по локалям решает задачу изоляции языковых данных в отдельные чанки, загружаемые только при необходимости.
Globalize не работает «из коробки» без внешних данных. Для каждой локали требуется загрузка:
Типичная ошибка — импортировать все локали сразу:
import Globalize from "globalize";
import "cldr-data";
import "cldr-data/main/ru";
import "cldr-data/main/en";
import "cldr-data/main/ar";
Такой подход приводит к росту начального бандла и потере преимуществ tree-shaking.
Code splitting в контексте Globalize строится на идее:
Globalize.load() и
Globalize.locale().Ключевой момент: CLDR данные не являются автоматически ленивыми, их необходимо явно разделять на модули.
Инициализация общего слоя:
import Globalize from "globalize";
import cldrSupplemental from "cldr-data/supplemental/likelySubtags.json";
import cldrWeekData from "cldr-data/supplemental/weekData.json";
import cldrNumberingSystems from "cldr-data/supplemental/numberingSystems.json";
Globalize.load(cldrSupplemental, cldrWeekData, cldrNumberingSystems);
Этот слой остаётся в основном бандле и используется всеми локалями.
Каждая локаль выносится в отдельный модуль.
locales/
ru.js
en.js
ar.js
// locales/ru.js
import Globalize from "globalize";
import cldrNumbers from "cldr-data/main/ru/numbers.json";
import cldrDates from "cldr-data/main/ru/ca-gregorian.json";
import cldrTimeData from "cldr-data/main/ru/timeZoneNames.json";
import messages from "./messages/ru.json";
Globalize.load(cldrNumbers, cldrDates, cldrTimeData);
Globalize.loadMessages(messages);
export function activateRu() {
Globalize.locale("ru");
}
Каждая локаль содержит только свои данные, что позволяет эффективно разделять код.
export async function loadLocale(locale) {
switch (locale) {
case "ru": {
const mod = await import("./locales/ru.js");
mod.activateRu();
return;
}
case "en": {
const mod = await import("./locales/en.js");
mod.activateEn();
return;
}
case "ar": {
const mod = await import("./locales/ar.js");
mod.activateAr();
return;
}
default:
throw new Error("Unsupported locale");
}
}
Каждый import() создаёт отдельный chunk, который
загружается только при первом обращении.
При большом количестве языков switch-case становится непрактичным. Используется маппинг:
const loaders = {
ru: () => import("./locales/ru.js"),
en: () => import("./locales/en.js"),
ar: () => import("./locales/ar.js"),
};
export async function setLocale(locale) {
const loader = loaders[locale];
if (!loader) {
throw new Error(`Locale ${locale} not found`);
}
const mod = await loader();
mod.activate();
}
Более строгий вариант — каждая локаль сама загружает CLDR:
// locales/en.js
import Globalize from "globalize";
import numbers from "cldr-data/main/en/numbers.json";
import dates from "cldr-data/main/en/ca-gregorian.json";
import timeZoneNames from "cldr-data/main/en/timeZoneNames.json";
import messages from "./messages/en.json";
export function activate() {
Globalize.load(numbers, dates, timeZoneNames);
Globalize.loadMessages(messages);
Globalize.locale("en");
}
Такой подход упрощает поддержку, но требует аккуратного контроля дублирования данных.
Без кеширования динамические импорты могут приводить к повторной загрузке модулей в некоторых runtime-сценариях (особенно при SPA-навигации).
Используется простой кеш:
const cache = new Map();
export async function setLocale(locale) {
if (cache.has(locale)) {
cache.get(locale)();
return;
}
const mod = await import(`./locales/${locale}.js`);
cache.set(locale, mod.activate);
mod.activate();
}
Кэш хранит не данные CLDR, а функцию активации, что минимизирует память.
В Globalize важно разделять:
Сообщения часто обновляются чаще, чем CLDR, поэтому их удобно хранить отдельно:
Globalize.loadMessages({
"ru": {
hello: "Привет",
date_format: "{0}"
}
});
При code splitting сообщения также выносятся:
export async function loadMessages(locale) {
const messages = await import(`./messages/${locale}.json`);
Globalize.loadMessages(messages.default);
}
Обычно локаль хранится в глобальном state:
let currentLocale = "en";
export async function changeLocale(locale) {
await setLocale(locale);
currentLocale = locale;
}
Важно избегать повторной инициализации Globalize без необходимости. Переключение локали не требует пересоздания экземпляра, достаточно смены контекста.
Разделение локалей даёт следующие эффекты:
Типичная разница:
При серверном рендеринге локаль должна быть известна заранее:
export function createGlobalize(locale) {
const Globalize = require("globalize");
require(`cldr-data/main/${locale}/numbers.json`);
require(`cldr-data/main/${locale}/ca-gregorian.json`);
Globalize.locale(locale);
return Globalize;
}
SSR-стратегия обычно предполагает отсутствие динамического
import(), так как все данные должны быть доступны
синхронно.
Для улучшения UX применяются гибридные подходы:
if (navigator.language.startsWith("ru")) {
import("./locales/ru.js");
}
Типичные проблемы:
Globalize.load() без контроля;При росте количества языков используется генерация маппинга:
const locales = ["en", "ru", "ar", "fr", "de", "zh"];
const loaders = Object.fromEntries(
locales.map(l => [l, () => import(`./locales/${l}.js`)])
);
Такой подход хорошо сочетается с автоматической генерацией через build-скрипты.