Любой объект из семейства Intl работает с локалями —
специальными идентификаторами языка и региональных настроек. Наиболее
распространённый формат локали:
ru-RU
en-US
de-DE
fr-CA
Структура состоит из нескольких частей:
ru, en, de);RU, US, DE);Пример:
new Intl.DateTimeFormat("ru-RU")
В этом случае движок пытается найти точное соответствие локали
ru-RU. Если поддержка отсутствует, запускается механизм
fallback — последовательного поиска подходящей альтернативы.
Fallback — это механизм автоматического подбора резервной локали, если указанная локаль недоступна.
Пример:
new Intl.NumberFormat("fr-CA")
Если движок не поддерживает канадский французский
(fr-CA), он может:
fr);Fallback особенно важен:
При поиске подходящей локали Intl использует цепочку деградации.
Например:
zh-Hant-TW
Возможная последовательность:
zh-Hant-TW
zh-Hant
zh
default locale
Каждый следующий шаг делает локаль менее специфичной.
const formatter = new Intl.DateTimeFormat(
["ban", "id"]
)
console.log(formatter.resolvedOptions().locale)
Результат:
id
Разбор:
ban — балийский язык;id — индонезийский.Массив локалей позволяет заранее задавать резервные варианты.
Intl API принимает:
Пример:
const locales = [
"fr-CA",
"fr-FR",
"en-US"
]
const formatter =
new Intl.NumberFormat(locales)
console.log(
formatter.resolvedOptions().locale
)
Алгоритм:
fr-CA;fr-FR;en-US;Это называется locale negotiation — согласование локали.
supportedLocalesOfМетод позволяет проверить, какие локали реально поддерживаются.
Пример:
const locales = [
"ru-RU",
"fr-CA",
"ban",
"xx-YY"
]
console.log(
Intl.DateTimeFormat.supportedLocalesOf(
locales
)
)
Возможный результат:
["ru-RU", "fr-CA"]
Неподдерживаемые локали автоматически исключаются.
resolvedOptionsМетод resolvedOptions() показывает итоговую локаль после
всех fallback-переходов.
Пример:
const formatter =
new Intl.DateTimeFormat(
["ban", "id"]
)
console.log(
formatter.resolvedOptions()
)
Результат:
{
locale: "id",
calendar: "gregory",
numberingSystem: "latn",
timeZone: "UTC"
}
Это главный способ диагностики поведения Intl API.
Важно различать:
fr-CA → fr
Язык сохраняется, меняется только региональная специфика.
ban → id
Происходит переход к другому языку из цепочки локалей.
Intl поддерживает два алгоритма поиска локали:
lookup;best fit.По умолчанию используется best fit.
Пример:
new Intl.DateTimeFormat(
["en-GB"],
{
localeMatcher: "lookup"
}
)
lookuplookup использует строгую последовательную
деградацию.
Например:
zh-Hant-TW
↓
zh-Hant
↓
zh
Без эвристик и дополнительных сопоставлений.
best fitbest fit зависит от реализации движка.
Он может:
Например:
en-AU
может быть сопоставлен с:
en-GB
или:
en-US
в зависимости от платформы.
Пример сравнения:
const lookup =
new Intl.DateTimeFormat(
["en-XX"],
{
localeMatcher: "lookup"
}
)
const bestFit =
new Intl.DateTimeFormat(
["en-XX"],
{
localeMatcher: "best fit"
}
)
console.log(
lookup.resolvedOptions().locale
)
console.log(
bestFit.resolvedOptions().locale
)
Результаты могут различаться в разных средах.
Перед fallback локали нормализуются.
Пример:
console.log(
Intl.getCanonicalLocales(
["EN-us"]
)
)
Результат:
["en-US"]
Intl автоматически:
Некоторые языковые коды заменяются современными.
Пример:
console.log(
Intl.getCanonicalLocales("iw")
)
Результат:
["he"]
Здесь:
iw — старый код иврита;he — современный код.Локали могут содержать Unicode-расширения.
Пример:
ja-JP-u-ca-japanese
Здесь:
u — Unicode extension;ca — calendar;japanese — японский календарь.Если расширение не поддерживается, Intl пытается сохранить основную локаль.
Пример:
const formatter =
new Intl.DateTimeFormat(
"ru-RU-u-ca-unknown"
)
console.log(
formatter.resolvedOptions()
)
Расширение будет проигнорировано, но ru-RU
сохранится.
Опции конструктора имеют больший приоритет.
Пример:
const formatter =
new Intl.DateTimeFormat(
"ja-JP-u-ca-japanese",
{
calendar: "gregory"
}
)
console.log(
formatter.resolvedOptions().calendar
)
Результат:
gregory
Несмотря на расширение локали.
Пример:
const formatter =
new Intl.NumberFormat(
"xxx-YYY"
)
console.log(
formatter.resolvedOptions().locale
)
Обычно движок использует:
Например:
en-US
navigator.languagesВ браузере можно получить предпочтительные локали пользователя.
Пример:
console.log(
navigator.languages
)
Возможный результат:
[
"fr-CA",
"fr",
"en-US",
"en"
]
Такой массив идеально подходит для Intl fallback.
Пример:
const userLocales =
navigator.languages
const formatter =
new Intl.DateTimeFormat(
[
...userLocales,
"en-US"
]
)
Здесь:
Механизм одинаков для большинства API:
Intl.DateTimeFormat;Intl.NumberFormat;Intl.Collator;Intl.RelativeTimeFormat;Intl.ListFormat;Intl.DisplayNames;Intl.PluralRules.Intl.RelativeTimeFormatconst rtf =
new Intl.RelativeTimeFormat(
["xx", "ru"]
)
console.log(
rtf.format(-1, "day")
)
Результат:
1 день назад
Intl.ListFormatconst formatter =
new Intl.ListFormat(
["unknown", "en"],
{
style: "long",
type: "conjunction"
}
)
console.log(
formatter.format([
"A",
"B",
"C"
])
)
Результат:
A, B, and C
В Node.js fallback зависит от ICU-данных.
Некоторые сборки содержат:
Проверка:
console.log(
Intl.DateTimeFormat.supportedLocalesOf([
"ru",
"fr",
"de",
"ja"
])
)
Для расширенной локализации используется:
node --icu-data-dir=...
или сборки Node.js с full-icu.
Без полного ICU fallback может слишком быстро переходить к английскому.
Плохо:
new Intl.NumberFormat(userLocale)
Лучше:
new Intl.NumberFormat([
userLocale,
"en-US"
])
Плохо:
"ru_RU"
Правильно:
"ru-RU"
Intl использует BCP 47.
resolvedOptionsБез проверки невозможно понять:
Intl API основан на стандарте BCP 47.
Локаль может включать:
language-script-region-variant
Пример:
sr-Cyrl-RS
Где:
sr — сербский;Cyrl — кириллица;RS — Сербия.Если конкретный script не поддерживается:
sr-Cyrl-RS
↓
sr-Cyrl
↓
sr
Script играет важную роль для:
Пример:
zh-Hans-CN
Hans — упрощённое письмо;CN — Китай.Fallback:
zh-Hans-CN
↓
zh-Hans
↓
zh
Для традиционного письма:
zh-Hant-TW
Типичная цепочка:
const locales = [
"fr-CA",
"fr",
"en-US"
]
Логика:
Крупные приложения часто используют:
user locale
↓
language locale
↓
regional corporate locale
↓
English
↓
system locale
Это обеспечивает:
Объект Intl.Locale помогает анализировать локали.
Пример:
const locale =
new Intl.Locale(
"sr-Cyrl-RS"
)
console.log(locale.language)
console.log(locale.script)
console.log(locale.region)
Результат:
sr
Cyrl
RS
const locale =
new Intl.Locale(
"en-US"
)
const british =
locale.maximize()
console.log(british.toString())
Методы:
maximize();minimize().Они работают через ICU-данные и помогают при locale negotiation.
maximize() и fallbackconst locale =
new Intl.Locale("en")
console.log(
locale.maximize().toString()
)
Возможный результат:
en-Latn-US
Метод добавляет недостающие части локали.
minimize() и
сокращение локалиconst locale =
new Intl.Locale(
"en-Latn-US"
)
console.log(
locale.minimize().toString()
)
Результат:
en
Это полезно для построения компактных fallback-цепочек.
Частая схема:
const locales = [
user.selectedLocale,
...navigator.languages,
"en-US"
]
Такой подход:
Fallback касается только:
Перевод интерфейса должен реализовываться отдельно:
Intl API в большинстве движков основан на ICU.
ICU отвечает за:
Поведение разных платформ может немного отличаться именно из-за версии ICU.
Разные браузеры могут:
best fit.Особенно заметны различия:
Практический способ:
function supports(locale) {
return Intl.DateTimeFormat
.supportedLocalesOf(locale)
.length > 0
}
console.log(supports("ru"))
console.log(supports("ban"))
["fr-CA", "fr", "en"]
Чаще всего:
en-US
resolvedOptions().locale
Нельзя гарантировать:
best fit;Intl.getCanonicalLocales()
Из-за ICU-сборок поведение может отличаться от браузеров.