Как добавить вероятные подметки к неполным локалям
Используйте JavaScript для дополнения частичных идентификаторов локалей наиболее вероятными скриптом и регионом
Введение
При работе с идентификаторами локалей иногда вы получаете неполную информацию. Пользователь может указать только код языка, например ja, не указывая скрипт или регион. Хотя такой частичный идентификатор допустим, ему не хватает конкретики для некоторых операций, например, сравнения локалей или определения форматов.
JavaScript позволяет дополнить такие частичные идентификаторы, добавив наиболее вероятные недостающие компоненты. Для этого используется языковая статистика, чтобы определить скрипт и регион, которые чаще всего используются для этого языка.
В этом гайде объясняется, что такое вероятные подметки, как JavaScript их определяет и когда стоит использовать эту функцию в своих приложениях.
Что такое вероятные подметки
Вероятные подметки — это коды скрипта и региона, которые чаще всего встречаются с определённым языком. Эти ассоциации основаны на реальных данных о языковом использовании, которые поддерживает Unicode Consortium.
Например, английский обычно пишется латиницей и чаще всего используется в США. Если у вас есть только код языка en, вероятные подметки — это Latn для скрипта и US для региона, что даёт полный идентификатор en-Latn-US.
Вероятность определяется на основе численности носителей и исторических паттернов использования. Алгоритм всегда возвращает самую статистически распространённую комбинацию.
Зачем добавлять вероятные подметки
Частичные идентификаторы локалей подходят для большинства операций форматирования. API Intl.DateTimeFormat и Intl.NumberFormat принимают en и применяют разумные значения по умолчанию. Но бывают ситуации, когда нужны именно полные идентификаторы.
Сравнение идентификаторов локалей
При сравнении двух идентификаторов локалей, чтобы понять, относятся ли они к одному и тому же языку и региону, частичные идентификаторы создают неоднозначность. Означает ли en то же самое, что и en-US, или это разные значения, потому что в одном указан регион, а в другом — нет?
Добавление вероятных подметок (likely subtags) устраняет эту неоднозначность. И en, и en-US максимизируются до en-Latn-US, что делает их напрямую сопоставимыми.
const locale1 = new Intl.Locale("en");
const locale2 = new Intl.Locale("en-US");
console.log(locale1.baseName === locale2.baseName);
// false - they look different
const maximized1 = locale1.maximize();
const maximized2 = locale2.maximize();
console.log(maximized1.baseName === maximized2.baseName);
// true - both are "en-Latn-US"
Хранение канонических форм
При хранении идентификаторов локалей в базах данных или конфигурационных файлах использование полных форм обеспечивает единообразие. Любая французская локаль становится fr-Latn-FR, любая японская — ja-Jpan-JP и так далее.
Такое единообразие делает поиск, фильтрацию и группировку по локали более надёжными.
Определение поведения, зависящего от письменности
Некоторые языки используют несколько письменностей, и это влияет на отображение текста, выбор шрифта и сортировку. Например, китайский может быть написан упрощёнными или традиционными иероглифами, а сербский — кириллицей или латиницей.
Добавление вероятных подметок делает письменность явной. Если пользователь указывает zh без указания письменности, максимизация приводит к zh-Hans-CN, что означает использование упрощённых китайских иероглифов.
Как работает алгоритм
Алгоритм Add Likely Subtags использует базу данных информации об использовании языков для определения недостающих компонентов. Эта база поддерживается Unicode Consortium как часть Common Locale Data Repository.
Алгоритм анализирует предоставленную вами информацию и заполняет пробелы:
- Если вы указываете только язык, добавляется наиболее распространённая письменность и регион для этого языка
- Если вы указываете язык и письменность, добавляется наиболее распространённый регион для этой комбинации
- Если вы указываете язык и регион, добавляется наиболее распространённая письменность для этой комбинации
- Если указаны все три компонента, они остаются без изменений
Решения принимаются на основе статистических данных об использовании языков по всему миру.
Использование метода maximize
Метод maximize() доступен для объектов Intl.Locale. Он возвращает новый объект локали с добавленными вероятными подметками к базовому имени.
const locale = new Intl.Locale("ja");
const maximized = locale.maximize();
console.log(locale.baseName);
// "ja"
console.log(maximized.baseName);
// "ja-Jpan-JP"
Метод не изменяет исходный объект локали. Он создаёт и возвращает новый объект.
Примеры для разных языков
Для разных языков вероятные подметки различаются в зависимости от того, где они в основном используются и какие алфавиты применяются.
Европейские языки
Французский максимизируется до Франции с латинским алфавитом:
const french = new Intl.Locale("fr");
const maximized = french.maximize();
console.log(maximized.baseName);
// "fr-Latn-FR"
Немецкий максимизируется до Германии с латинским алфавитом:
const german = new Intl.Locale("de");
const maximized = german.maximize();
console.log(maximized.baseName);
// "de-Latn-DE"
Языки с нелатинскими алфавитами
Японский максимизируется до Японии с японским письмом:
const japanese = new Intl.Locale("ja");
const maximized = japanese.maximize();
console.log(maximized.baseName);
// "ja-Jpan-JP"
Арабский максимизируется до Египта с арабским письмом:
const arabic = new Intl.Locale("ar");
const maximized = arabic.maximize();
console.log(maximized.baseName);
// "ar-Arab-EG"
Китайский без указания письма максимизируется до упрощённых иероглифов и Китая:
const chinese = new Intl.Locale("zh");
const maximized = chinese.maximize();
console.log(maximized.baseName);
// "zh-Hans-CN"
Частичные идентификаторы с регионом
Если вы указываете язык и регион, но не указываете письмо, алгоритм добавляет письмо:
const britishEnglish = new Intl.Locale("en-GB");
const maximized = britishEnglish.maximize();
console.log(maximized.baseName);
// "en-Latn-GB"
Регион остаётся указанным. Добавляется только недостающее письмо.
Частичные идентификаторы с письмом
Если вы указываете язык и письмо, но не указываете регион, алгоритм добавляет наиболее распространённый регион для этого письма:
const traditionalChinese = new Intl.Locale("zh-Hant");
const maximized = traditionalChinese.maximize();
console.log(maximized.baseName);
// "zh-Hant-TW"
Традиционные китайские иероглифы в основном используются на Тайване, поэтому в качестве региона добавляется TW.
Теги расширения сохраняются
Теги расширения Unicode указывают предпочтения форматирования, такие как системы календарей, системы счисления и форматы времени. Эти теги идут после -u- в идентификаторе локали.
Метод maximize() не изменяет теги расширения. Он влияет только на язык, сценарий и региональные компоненты.
const locale = new Intl.Locale("fr", {
calendar: "gregory",
numberingSystem: "latn",
hourCycle: "h23"
});
console.log(locale.toString());
// "fr-u-ca-gregory-hc-h23-nu-latn"
const maximized = locale.maximize();
console.log(maximized.toString());
// "fr-Latn-FR-u-ca-gregory-hc-h23-nu-latn"
Базовое имя меняется с fr на fr-Latn-FR, но теги расширения остаются прежними.
Когда использовать maximize
Используйте метод maximize(), когда вам нужны полные идентификаторы локалей для согласованности или сравнения.
Нормализация пользовательского ввода
Пользователи могут вводить локали по-разному. Кто-то напишет en, кто-то en-US, а кто-то en-Latn-US. Максимизация всех вариантов приводит к единому формату:
function normalizeLocale(input) {
try {
const locale = new Intl.Locale(input);
const maximized = locale.maximize();
return maximized.baseName;
} catch (error) {
return null;
}
}
console.log(normalizeLocale("en"));
// "en-Latn-US"
console.log(normalizeLocale("en-US"));
// "en-Latn-US"
console.log(normalizeLocale("en-Latn-US"));
// "en-Latn-US"
Все три варианта дают одинаковую нормализованную форму.
Построение цепочек отката локалей
Когда конкретная локаль недоступна, приложения переходят к более общим локалям. Максимизация помогает правильно строить такие цепочки:
function buildFallbackChain(localeString) {
const locale = new Intl.Locale(localeString);
const maximized = locale.maximize();
const chain = [maximized.toString()];
if (maximized.script && maximized.region) {
const withoutRegion = new Intl.Locale(
`${maximized.language}-${maximized.script}`
);
chain.push(withoutRegion.toString());
}
if (maximized.region) {
chain.push(maximized.language);
}
chain.push("en");
return chain;
}
console.log(buildFallbackChain("zh-TW"));
// ["zh-Hant-TW", "zh-Hant", "zh", "en"]
Это создает корректную цепочку отката от самой специфичной локали к самой общей.
Сопоставление пользовательских предпочтений с доступными локалями
Когда у вас есть набор доступных переводов и нужно найти лучший вариант под предпочтения пользователя, максимизация обеих сторон позволяет точно сравнить:
function findBestMatch(userPreference, availableLocales) {
const userMaximized = new Intl.Locale(userPreference).maximize();
const matches = availableLocales.map(available => {
const availableMaximized = new Intl.Locale(available).maximize();
let score = 0;
if (userMaximized.language === availableMaximized.language) score += 1;
if (userMaximized.script === availableMaximized.script) score += 1;
if (userMaximized.region === availableMaximized.region) score += 1;
return { locale: available, score };
});
matches.sort((a, b) => b.score - a.score);
return matches[0].locale;
}
const available = ["en-US", "en-GB", "fr-FR", "de-DE"];
console.log(findBestMatch("en", available));
// "en-US"
Функция расширяет пользовательское предпочтение en до en-Latn-US и находит наиболее подходящее совпадение.
Когда не стоит использовать максимизацию
Нет необходимости максимизировать локали перед передачей их в API форматирования. Intl.DateTimeFormat, Intl.NumberFormat и другие конструкторы форматирования корректно работают с частичными идентификаторами.
const date = new Date("2025-03-15");
const partial = new Intl.DateTimeFormat("fr").format(date);
const maximized = new Intl.DateTimeFormat("fr-Latn-FR").format(date);
console.log(partial);
// "15/03/2025"
console.log(maximized);
// "15/03/2025"
Оба варианта дают одинаковый результат. Дополнительная спецификация не влияет на поведение форматирования в этом случае.
Используйте maximize(), когда вам нужна явная информация для вашей логики, а не при передаче локалей встроенным форматтерам.
Поддержка браузеров
Метод maximize() доступен во всех современных браузерах. Chrome, Firefox, Safari и Edge поддерживают его как часть API Intl.Locale.
Node.js поддерживает maximize(), начиная с версии 12, с полной поддержкой в версии 14 и выше.
Итоги
Вероятные подметки (likely subtags) дополняют частичные идентификаторы локалей, добавляя наиболее распространённые скрипт и регион для данного языка. Метод Intl.Locale.maximize() реализует алгоритм Unicode Add Likely Subtags для такого расширения.
Основные моменты:
- Вероятные подметки основаны на реальных данных о языковом использовании
- Метод
maximize()добавляет недостающие коды скрипта и региона - Теги расширения для календарей и систем счисления не изменяются
- Используйте максимизацию для нормализации пользовательского ввода и сравнения локалей
- API форматирования не требуют максимизированных локалей
Метод maximize() предоставляет стандартизированный способ работы с полными идентификаторами локалей, когда вашей логике приложения требуется явная информация о скрипте и регионе.