Как обрабатывать резервирование локали, если предпочтительная локаль недоступна
Автоматический выбор поддерживаемых языков, если пользователи предпочитают неподдерживаемые локали
Введение
Не каждое веб-приложение поддерживает все языки мира. Когда пользователь предпочитает язык, который ваше приложение не поддерживает, необходимо использовать механизм резервного отображения, чтобы показывать контент на следующем подходящем языке вместо ошибок или непереведенного текста.
Резервное отображение локали — это процесс выбора альтернативной локали, когда предпочтительная локаль недоступна. API Intl в JavaScript автоматически обрабатывает это, принимая несколько вариантов локалей и выбирая первую из поддерживаемых. Это гарантирует, что ваше приложение всегда отображает правильно отформатированный контент, даже если точная предпочтительная локаль недоступна.
В этом уроке объясняется, как работает резервное отображение локалей в JavaScript, как эффективно его реализовать и как создать собственную логику резервного отображения для приложений с особыми требованиями к поддержке локалей.
Проблема с неподдерживаемыми локалями
Когда вы передаете идентификатор локали в API Intl, среда выполнения JavaScript должна поддерживать эту локаль, чтобы правильно форматировать контент. Если вы запрашиваете форматирование для норвежского нюнорска, но среда выполнения поддерживает только норвежский букмол, форматировщик должен уметь обработать это корректно.
Без резервного отображения приложения не смогли бы отображать контент или показывали бы непереведенный текст при встрече с неподдерживаемыми локалями. Пользователи из регионов с менее распространенными языковыми вариантами сталкивались бы с некорректными интерфейсами.
Рассмотрим пользователя, говорящего на канадском французском. Если ваше приложение поддерживает только европейский французский, вы хотите, чтобы форматировщик использовал европейские французские конвенции, а не полностью выходил из строя. Хотя это не идеально, это обеспечивает лучший опыт, чем отсутствие локализации вообще.
Как API Intl автоматически обрабатывает резервное отображение
Каждый конструктор Intl принимает либо одну строку локали, либо массив строк локалей. Когда вы передаете массив, среда выполнения оценивает каждую локаль по порядку и использует первую из поддерживаемых.
const locales = ["fr-CA", "fr-FR", "en-US"];
const formatter = new Intl.DateTimeFormat(locales);
const date = new Date("2025-03-15");
console.log(formatter.format(date));
// Использует fr-CA, если доступно
// Переходит на fr-FR, если fr-CA недоступно
// Переходит на en-US, если ни один из французских вариантов недоступен
Среда выполнения проверяет массив слева направо. Если она поддерживает канадский французский, то использует его. Если нет, то пробует европейский французский. Если ни один из французских вариантов недоступен, то переходит на американский английский.
Этот автоматический механизм резервного отображения означает, что вам не нужно вручную проверять поддержку или обрабатывать ошибки при запросе конкретных локалей. API Intl гарантирует выбор поддерживаемой локали или переход на системную локаль по умолчанию.
Предоставление нескольких вариантов локалей
Самый простой способ реализовать резервный вариант — передать массив локалей в порядке предпочтения. Это работает со всеми конструкторами Intl, включая DateTimeFormat, NumberFormat, Collator и другие.
const locales = ["es-MX", "es-ES", "es", "en"];
const numberFormatter = new Intl.NumberFormat(locales, {
style: "currency",
currency: "USD"
});
console.log(numberFormatter.format(1234.56));
// Использует мексиканский испанский, если доступен
// Переходит на европейский испанский
// Переходит на общий испанский
// Переходит на английский как последний вариант
Этот подход обеспечивает плавный путь деградации. Пользователи получают контент на предпочитаемом диалекте, если он доступен, затем на более широком варианте их языка, а затем на общем языке по умолчанию.
Порядок имеет значение. Среда выполнения выбирает первую поддерживаемую локаль, поэтому сначала указывайте наиболее специфичные и предпочтительные локали.
Понимание работы сопоставления локалей
Когда вы предоставляете несколько локалей, среда выполнения JavaScript использует алгоритм сопоставления локалей для выбора наилучшего доступного варианта. Этот алгоритм сравнивает запрошенные вами локали с набором локалей, поддерживаемых средой выполнения.
Если запрошенная локаль точно совпадает с поддерживаемой, среда выполнения использует её немедленно. Если точного совпадения нет, среда выполнения может выбрать связанную локаль на основе языковых и региональных кодов.
Например, если вы запрашиваете en-AU (австралийский английский), но среда выполнения поддерживает только en-US и en-GB, она выберет один из этих вариантов английского, а не перейдёт на совершенно другой язык.
const locales = ["en-AU", "en"];
const formatter = new Intl.DateTimeFormat(locales);
const resolvedLocale = formatter.resolvedOptions().locale;
console.log(resolvedLocale);
// Может показать "en-US" или "en-GB" в зависимости от среды выполнения
// Среда выполнения выбрала поддерживаемый вариант английского
Метод resolvedOptions() возвращает фактическую локаль, которую использует форматтер. Это позволяет вам проверить, какая локаль была выбрана после резервного перехода.
Проверка поддерживаемых локалей
Статический метод supportedLocalesOf() проверяет, какие локали из списка поддерживаются конкретным конструктором Intl. Этот метод возвращает массив, содержащий только поддерживаемые локали.
const requestedLocales = ["fr-CA", "fr-FR", "de-DE", "ja-JP"];
const supportedLocales = Intl.DateTimeFormat.supportedLocalesOf(requestedLocales);
console.log(supportedLocales);
// Вывод зависит от поддержки среды выполнения
// Пример: ["fr-FR", "de-DE", "ja-JP"]
// Канадский французский не поддерживается, остальные поддерживаются
Этот метод фильтрует запрашиваемые локали, чтобы показать, какие из них может использовать среда выполнения без использования значений по умолчанию. Неподдерживаемые локали удаляются из возвращаемого массива.
Вы можете использовать этот метод, чтобы проверить поддержку перед созданием форматтеров или чтобы показать, какие языковые опции доступны пользователям.
const availableLocales = ["en-US", "es-MX", "fr-FR", "de-DE", "ja-JP"];
const supported = Intl.NumberFormat.supportedLocalesOf(availableLocales);
console.log("Эта среда выполнения поддерживает:", supported);
// Показывает, какие локали вашего приложения работают в этой среде
Каждый конструктор Intl имеет свой собственный метод supportedLocalesOf(), так как поддержка локалей может различаться для различных функций интернационализации. Например, среда выполнения может поддерживать французский для форматирования чисел, но не для сегментации текста.
Построение цепочек резервных локалей из идентификаторов локалей
Когда вы знаете, что ваше приложение поддерживает определенные локали, вы можете построить цепочку резервных локалей, которая постепенно становится менее специфичной. Этот шаблон начинается с полного идентификатора локали и удаляет компоненты до тех пор, пока не будет найдено совпадение.
function buildFallbackChain(locale) {
const chain = [locale];
const parts = locale.split("-");
if (parts.length > 1) {
chain.push(parts[0]);
}
chain.push("en");
return chain;
}
const fallbacks = buildFallbackChain("zh-Hans-CN");
console.log(fallbacks);
// ["zh-Hans-CN", "zh", "en"]
const formatter = new Intl.DateTimeFormat(fallbacks);
// Пробует упрощенный китайский для Китая
// Переходит на общий китайский
// Переходит на английский
Эта функция создает цепочку резервных локалей, извлекая код языка из идентификатора локали и добавляя финальный резервный вариант на английском языке. Вы можете расширить эту логику, чтобы включить более сложные правила резервирования на основе поддерживаемых локалей вашего приложения.
Для приложений, которые поддерживают несколько вариантов одного языка, вы можете сначала перейти на связанные диалекты, прежде чем переходить на английский.
function buildSmartFallbackChain(locale) {
const chain = [locale];
if (locale.startsWith("es-")) {
chain.push("es-MX", "es-ES", "es");
} else if (locale.startsWith("fr-")) {
chain.push("fr-FR", "fr-CA", "fr");
} else if (locale.startsWith("zh-")) {
chain.push("zh-Hans-CN", "zh-Hant-TW", "zh");
}
const parts = locale.split("-");
if (parts.length > 1 && !chain.includes(parts[0])) {
chain.push(parts[0]);
}
if (!chain.includes("en")) {
chain.push("en");
}
return chain;
}
const fallbacks = buildSmartFallbackChain("es-AR");
console.log(fallbacks);
// ["es-AR", "es-MX", "es-ES", "es", "en"]
// Пробует аргентинский испанский
// Переходит на мексиканский испанский
// Переходит на европейский испанский
// Переходит на общий испанский
// Переходит на английский
Этот подход гарантирует, что пользователи увидят контент на связанном диалекте их языка, прежде чем переключиться на английский.
Выбор алгоритма сопоставления локали
API Intl поддерживает два алгоритма сопоставления локалей: точный поиск (lookup) и наилучшее соответствие (best fit). Вы можете указать, какой алгоритм использовать, через опцию localeMatcher при создании форматеров.
Алгоритм точного поиска следует спецификации BCP 47 Lookup. Он выполняет строгое сопоставление, систематически сравнивая идентификаторы локалей и выбирая первое точное совпадение.
const locales = ["de-DE", "en-US"];
const formatter = new Intl.NumberFormat(locales, {
localeMatcher: "lookup"
});
console.log(formatter.resolvedOptions().locale);
// Использует строгие правила сопоставления
Алгоритм наилучшего соответствия позволяет среде выполнения выбирать локаль, используя собственную логику сопоставления. Этот алгоритм может принимать интеллектуальные решения о том, какая локаль лучше всего соответствует потребностям пользователя, даже если это не точное совпадение.
const locales = ["de-DE", "en-US"];
const formatter = new Intl.NumberFormat(locales, {
localeMatcher: "best fit"
});
console.log(formatter.resolvedOptions().locale);
// Использует алгоритм наилучшего соответствия среды выполнения
// Может более разумно выбрать связанную локаль
Алгоритм по умолчанию — наилучшее соответствие. Большинство приложений должны использовать его, так как он обеспечивает лучшие результаты в разных средах выполнения JavaScript. Используйте точный поиск только в тех случаях, когда требуется предсказуемое строгое поведение сопоставления.
Использование языковых предпочтений браузера для автоматического резервирования
Свойство navigator.languages возвращает массив предпочтительных языков пользователя в порядке их приоритета. Вы можете передать этот массив напрямую в конструкторы Intl, чтобы реализовать автоматическое резервирование на основе настроек браузера.
const formatter = new Intl.DateTimeFormat(navigator.languages);
const date = new Date("2025-03-15");
console.log(formatter.format(date));
// Автоматически использует предпочтительный поддерживаемый язык пользователя
Этот подход позволяет браузеру обрабатывать всю логику резервирования. Если первый предпочтительный язык пользователя не поддерживается, API Intl автоматически попробует второй, затем третий и так далее.
Этот шаблон хорошо работает, если вы хотите учитывать все языковые предпочтения пользователя без ручного создания цепочек резервирования.
console.log(navigator.languages);
// ["fr-CA", "fr", "en-US", "en"]
const numberFormatter = new Intl.NumberFormat(navigator.languages, {
style: "currency",
currency: "USD"
});
console.log(numberFormatter.format(1234.56));
// Сначала пробует канадский французский
// Затем переходит к следующему предпочтению пользователя
API Intl оценивает каждый язык в массиве и выбирает первый поддерживаемый, что делает этот подход надежным решением для работы с разнообразными предпочтениями пользователей.
Совмещение поддержки приложения с предпочтениями пользователя
Для приложений с ограниченной поддержкой локалей вы можете фильтровать предпочтения пользователя, чтобы они соответствовали поддерживаемым локалям, прежде чем создавать форматтеры. Это гарантирует, что вы будете использовать только те локали, которые ваше приложение может обработать.
const supportedLocales = ["en-US", "es-MX", "fr-FR", "de-DE"];
function findBestLocale(userPreferences, appSupported) {
const preferences = userPreferences.map(pref => {
const parts = pref.split("-");
return [pref, parts[0]];
}).flat();
for (const pref of preferences) {
if (appSupported.includes(pref)) {
return pref;
}
}
return appSupported[0];
}
const userLocale = findBestLocale(navigator.languages, supportedLocales);
const formatter = new Intl.DateTimeFormat(userLocale);
console.log(formatter.resolvedOptions().locale);
// Выбранная локаль соответствует как предпочтениям пользователя, так и поддержке приложения
Эта функция находит первое совпадение между предпочтениями пользователя и поддерживаемыми локалями, включая попытки использовать языковые коды без региональных кодов для более широкого соответствия.
Для более сложного сопоставления вы можете проверить, какие из предпочтений пользователя поддерживаются API Intl в этом окружении выполнения.
const supportedLocales = ["en-US", "es-MX", "fr-FR", "de-DE"];
function findBestSupportedLocale(userPreferences, appSupported) {
const runtimeSupported = Intl.DateTimeFormat.supportedLocalesOf(appSupported);
for (const pref of userPreferences) {
if (runtimeSupported.includes(pref)) {
return pref;
}
const lang = pref.split("-")[0];
const match = runtimeSupported.find(s => s.startsWith(lang));
if (match) {
return match;
}
}
return runtimeSupported[0] || "en";
}
const userLocale = findBestSupportedLocale(navigator.languages, supportedLocales);
const formatter = new Intl.DateTimeFormat(userLocale);
Этот подход гарантирует, что выбранная локаль поддерживается как вашим приложением, так и средой выполнения JavaScript.
Обработка случаев, когда локаль не совпадает
Если ни одна из запрошенных локалей не поддерживается, API Intl использует локаль по умолчанию для системы. Этот параметр по умолчанию зависит от среды и определяется операционной системой, браузером или конфигурацией Node.js.
const unsupportedLocales = ["non-existent-locale"];
const formatter = new Intl.DateTimeFormat(unsupportedLocales);
console.log(formatter.resolvedOptions().locale);
// Показывает локаль по умолчанию для системы
// Может быть "en-US" или другой локаль в зависимости от системы
Локаль по умолчанию для системы гарантирует, что форматтеры всегда будут работать, даже с полностью недействительными или неподдерживаемыми списками локалей. Ваше приложение не будет выдавать ошибки из-за проблем с локалями.
Чтобы обеспечить конкретный резерв вместо использования системного значения по умолчанию, всегда включайте широко поддерживаемую локаль, такую как en или en-US, в конец вашего массива локалей.
const locales = ["xyz-INVALID", "en"];
const formatter = new Intl.DateTimeFormat(locales);
console.log(formatter.resolvedOptions().locale);
// Будет использоваться "en", так как первая локаль недействительна
// Гарантированный резерв на английский вместо системного значения по умолчанию
Этот шаблон делает поведение вашего приложения более предсказуемым в разных средах.
Практические шаблоны для производственных приложений
При создании производственных приложений комбинируйте несколько стратегий резервирования, чтобы обеспечить надежную обработку локалей для разнообразных пользовательских баз.
Обычный шаблон создает форматировщики с полной цепочкой резервирования, которая включает пользовательские предпочтения, поддерживаемые приложением локали и гарантированный окончательный резерв.
class LocaleManager {
constructor(supportedLocales) {
this.supportedLocales = supportedLocales;
this.defaultLocale = "en-US";
}
buildLocaleChain(userPreference) {
const chain = [];
if (userPreference) {
chain.push(userPreference);
const lang = userPreference.split("-")[0];
if (lang !== userPreference) {
chain.push(lang);
}
}
chain.push(...navigator.languages);
chain.push(...this.supportedLocales);
chain.push(this.defaultLocale);
const unique = [...new Set(chain)];
return unique;
}
createDateFormatter(userPreference, options = {}) {
const locales = this.buildLocaleChain(userPreference);
return new Intl.DateTimeFormat(locales, options);
}
createNumberFormatter(userPreference, options = {}) {
const locales = this.buildLocaleChain(userPreference);
return new Intl.NumberFormat(locales, options);
}
}
const manager = new LocaleManager(["en-US", "es-MX", "fr-FR", "de-DE"]);
const dateFormatter = manager.createDateFormatter("pt-BR");
console.log(dateFormatter.resolvedOptions().locale);
// Использует полную цепочку резервирования
// Пробует португальский для Бразилии
// Переходит на португальский
// Переходит через navigator.languages
// Переходит через поддерживаемые локали
// Гарантированно использует en-US, если ничего другого не подходит
Этот класс инкапсулирует логику резервирования локалей и обеспечивает согласованное поведение по всему вашему приложению.
Для приложений, которые должны реагировать на изменения локали пользователя во время выполнения, комбинируйте менеджер локалей с слушателями событий.
class LocaleAwareFormatter {
constructor(supportedLocales) {
this.supportedLocales = supportedLocales;
this.updateFormatters();
window.addEventListener("languagechange", () => {
this.updateFormatters();
});
}
updateFormatters() {
const locales = [...navigator.languages, ...this.supportedLocales, "en"];
this.dateFormatter = new Intl.DateTimeFormat(locales);
this.numberFormatter = new Intl.NumberFormat(locales);
}
formatDate(date) {
return this.dateFormatter.format(date);
}
formatNumber(number) {
return this.numberFormatter.format(number);
}
}
const formatter = new LocaleAwareFormatter(["en-US", "es-MX", "fr-FR"]);
console.log(formatter.formatDate(new Date()));
// Автоматически обновляется, когда пользователь меняет языковые предпочтения
Этот шаблон создает форматировщики, которые остаются синхронизированными с изменениями языка браузера, обеспечивая, чтобы ваше приложение всегда отображало контент в соответствии с текущими предпочтениями.
Резюме
Резервирование локали гарантирует, что ваше приложение отображает правильно форматированный контент, даже если пользователи предпочитают локали, которые вы явно не поддерживаете. API Intl автоматически обрабатывает резервирование, принимая массивы предпочтений локалей и выбирая первый поддерживаемый вариант.
Ключевые концепции:
- Передавайте массивы локалей в конструкторы Intl для автоматического резервирования
- Среда выполнения выбирает первую поддерживаемую локаль из массива
- Используйте
supportedLocalesOf(), чтобы проверить, какие локали доступны - Создавайте цепочки резервирования, которые переходят от конкретных локалей к более общим
- Опция
localeMatcherуправляет алгоритмом сопоставления - Передавайте
navigator.languagesнапрямую для автоматической обработки предпочтений пользователя - Всегда включайте широко поддерживаемую локаль в качестве окончательного резервного варианта, например, английский
- Среда выполнения использует системные настройки по умолчанию, если ни одна локаль не совпадает
Используйте автоматическое резервирование с массивами локалей для большинства приложений. Реализуйте пользовательскую логику резервирования, если вам нужен специфический контроль над тем, какие локали пробуются и в каком порядке.