При локализации интерфейсов далеко не всегда существует полный набор переводов, форматов дат, чисел и валют для каждого языка и региона. Библиотека Globalize использует fallback-механизмы для автоматического поиска ближайших подходящих данных, если точная локаль отсутствует.
Fallback позволяет:
Типичный пример:
const globalize = new Globalize("fr-CA");
Если для fr-CA отсутствуют определённые данные,
библиотека может использовать данные fr.
Globalize строит цепочку поиска данных на основе языковой и региональной структуры локали.
Например:
pt-BR → pt → root
или:
zh-Hant-TW → zh-Hant → zh → root
Порядок fallback:
Globalize полностью опирается на данные Unicode CLDR. Именно CLDR определяет:
Без CLDR fallback невозможен.
Подключение данных:
const Globalize = require("globalize");
const cldrData = require("cldr-data");
Globalize.load(
cldrData.entireSupplemental(),
cldrData.entireMainFor("en", "en-GB", "fr")
);
root — базовая локаль CLDR, содержащая универсальные
значения по умолчанию.
Если библиотека не находит данные ни в одной цепочке локалей,
используется root.
Пример fallback:
de-AT → de → root
В root обычно находятся:
const globalize = new Globalize("es-MX");
const formatter = globalize.numberFormatter();
console.log(formatter(12345.67));
Если специфичные мексиканские настройки отсутствуют, будут использованы испанские.
const globalize = new Globalize("en-AU");
const formatter = globalize.dateFormatter({
datetime: "medium"
});
console.log(formatter(new Date()));
При отсутствии австралийских шаблонов библиотека возьмёт данные
en.
const formatter = globalize.currencyFormatter("USD");
console.log(formatter(199.99));
Fallback применяется к:
Модуль globalize/message не выполняет автоматическое
наследование переводов между локалями. Этот механизм необходимо
реализовывать самостоятельно.
Пример структуры переводов:
Globalize.loadMessages({
en: {
hello: "Hello"
},
"en-GB": {
currency: "Pounds"
}
});
Проблема:
const globalize = new Globalize("en-GB");
globalize.formatMessage("hello");
Сообщение "hello" не будет найдено автоматически, потому
что en-GB не наследует en.
Наиболее распространённый подход — объединение словарей.
const messages = {
en: {
hello: "Hello",
save: "Save"
},
"en-GB": {
currency: "Pound"
}
};
function buildMessages(locale) {
const parts = locale.split("-");
const chain = [];
while (parts.length) {
chain.unshift(parts.join("-"));
parts.pop();
}
return Object.assign({}, ...chain.map(l => messages[l] || {}));
}
Globalize.loadMessages({
"en-GB": buildMessages("en-GB")
});
Результат:
{
hello: "Hello",
save: "Save",
currency: "Pound"
}
Практически всегда fallback организуют через массив локалей.
function localeChain(locale) {
const result = [];
const parts = locale.split("-");
while (parts.length) {
result.push(parts.join("-"));
parts.pop();
}
result.push("root");
return result;
}
console.log(localeChain("zh-Hant-TW"));
Результат:
[
"zh-Hant-TW",
"zh-Hant",
"zh",
"root"
]
Перед использованием локали полезно убедиться, что данные загружены.
function safeGlobalize(locale) {
try {
return new Globalize(locale);
} catch (e) {
return new Globalize("en");
}
}
Во многих приложениях fallback используется не только для переводов, но и для пользовательских конфигураций.
const settings = {
en: {
timezone: "UTC"
},
"en-US": {
currency: "USD"
}
};
Алгоритм:
function resolveSetting(locale, key) {
const chain = localeChain(locale);
for (const loc of chain) {
if (settings[loc] && settings[loc][key]) {
return settings[loc][key];
}
}
return null;
}
Plural rules также наследуются через CLDR.
Пример:
const globalize = new Globalize("ru-KZ");
const plural = globalize.pluralGenerator();
console.log(plural(5));
Если региональные правила отсутствуют, используются языковые:
ru-KZ → ru
Fallback не означает полное копирование данных родительской локали. CLDR позволяет переопределять отдельные поля.
Например:
en
├── date format
├── decimal separator
└── currency style
en-GB
└── currency symbol override
Globalize автоматически объединяет данные.
Используется только полная локаль.
if (!supportedLocales.includes(userLocale)) {
throw new Error("Unsupported locale");
}
Подходит для:
Постепенное снижение точности локализации.
fr-CA → fr → en
Подходит для:
Если локаль неизвестна — используется язык приложения.
const locale = supported.includes(userLocale)
? userLocale
: "en";
При динамической загрузке локалей fallback особенно важен.
async function loadLocale(locale) {
try {
await import(`./locales/${locale}.json`);
return locale;
} catch {
return locale.split("-")[0];
}
}
Часто fallback объединяет:
{
locale: "fr-CA",
fallback: [
"fr-CA",
"fr",
"en"
]
}
Построение цепочек локалей может вызываться очень часто.
Для оптимизации используют кэш.
const cache = new Map();
function getLocaleChain(locale) {
if (cache.has(locale)) {
return cache.get(locale);
}
const chain = localeChain(locale);
cache.set(locale, chain);
return chain;
}
function safeMessage(globalize, key) {
try {
return globalize.formatMessage(key);
} catch {
return `[missing:${key}]`;
}
}
function formatWithFallback(primary, fallback, key) {
try {
return primary.formatMessage(key);
} catch {
return fallback.formatMessage(key);
}
}
При сложной локализации важно видеть, какая локаль реально используется.
function resolveLocale(locale, available) {
const chain = localeChain(locale);
for (const item of chain) {
console.log("Checking:", item);
if (available.includes(item)) {
console.log("Resolved:", item);
return item;
}
}
return "root";
}
Пример:
fr-CA → fr
В результате могут смешиваться:
Если часть сообщений отсутствует:
Главная
Settings
Профиль
Logout
Интерфейс становится неоднородным.
Слишком глубокий fallback иногда приводит к неожиданным результатам:
zh-Hant-HK → zh-Hant → zh → root
На последнем этапе форматирование может стать слишком общим.
Минимальный набор переводов должен существовать для основного языка:
en
fr
de
const config = {
locale: "pt-BR",
fallbacks: [
"pt-BR",
"pt",
"en"
]
};
Вместо разрозненных проверок:
if (!message) {
...
}
лучше использовать единый resolver.
function missingKey(locale, key) {
console.warn(`[${locale}] Missing key: ${key}`);
}
Проверяются:
class LocaleResolver {
constructor(messages, defaultLocale = "en") {
this.messages = messages;
this.defaultLocale = defaultLocale;
}
chain(locale) {
const result = [];
const parts = locale.split("-");
while (parts.length) {
result.push(parts.join("-"));
parts.pop();
}
result.push(this.defaultLocale);
return result;
}
resolve(locale, key) {
const chain = this.chain(locale);
for (const loc of chain) {
const dict = this.messages[loc];
if (dict && key in dict) {
return dict[key];
}
}
return `[missing:${key}]`;
}
}
Использование:
const resolver = new LocaleResolver({
en: {
save: "Save"
},
fr: {
save: "Enregistrer"
}
});
console.log(
resolver.resolve("fr-CA", "save")
);
Результат:
Enregistrer
function createGlobalize(locale, supported) {
const chain = localeChain(locale);
for (const loc of chain) {
if (supported.includes(loc)) {
return new Globalize(loc);
}
}
return new Globalize("en");
}
При модульной архитектуре локали часто разбиваются по пакетам.
Пример:
auth/
dashboard/
profile/
billing/
Fallback может применяться отдельно для каждого модуля.
{
auth: ["fr-CA", "fr", "en"],
billing: ["fr-CA", "en"]
}
Иногда используется расширенный fallback:
es-MX
↓
es-419
↓
es
↓
en
Где:
es-419 — испанский для Латинской Америки;es — общий испанский;en — резервный язык приложения.Такая схема особенно распространена в международных enterprise-системах.