Как добавить вероятные подметки к неполным локалям
Используйте JavaScript для дополнения частичных идентификаторов локалей наиболее вероятными скриптом и регионом
Введение
При работе с идентификаторами локалей иногда приходится иметь дело с неполной информацией. Пользователь может указать только код языка, например, ja, без указания скрипта или региона. Хотя такой частичный идентификатор является допустимым, он не обладает достаточной спецификой для выполнения определённых операций, таких как сравнение локалей или определение форматов.
JavaScript предоставляет способ дополнить эти частичные идентификаторы, добавляя наиболее вероятные недостающие компоненты. Этот процесс использует языковые данные для определения скрипта и региона, которые обычно используются носителями данного языка.
В этом руководстве объясняется, что такое вероятные подметки, как JavaScript их определяет и когда использовать эту функцию в ваших приложениях.
Что такое вероятные подметки
Вероятные подметки — это коды скрипта и региона, которые чаще всего ассоциируются с данным языком. Эти ассоциации основаны на данных о реальном использовании языков, поддерживаемых Консорциумом Unicode.
Например, английский язык обычно пишется латинским алфавитом и чаще всего используется в Соединённых Штатах. Если у вас есть только код языка en, вероятные подметки будут Latn для скрипта и US для региона, что даст вам полный идентификатор en-Latn-US.
Вероятность основана на численности носителей языка и исторических моделях использования. Алгоритм всегда возвращает наиболее статистически распространённую комбинацию.
Зачем добавлять вероятные подметки
Частичные идентификаторы локалей подходят для большинства операций форматирования. API Intl.DateTimeFormat и Intl.NumberFormat принимают en и применяют разумные значения по умолчанию. Однако существуют ситуации, когда необходимы полные идентификаторы.
Сравнение идентификаторов локалей
При сравнении двух идентификаторов локалей, чтобы определить, относятся ли они к одному и тому же языку и региону, частичные идентификаторы создают двусмысленность. Означает ли en то же самое, что и en-US, или это разные вещи, потому что один указывает регион, а другой — нет?
Добавление вероятных подметок устраняет эту двусмысленность. И 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 - они выглядят по-разному
const maximized1 = locale1.maximize();
const maximized2 = locale2.maximize();
console.log(maximized1.baseName === maximized2.baseName);
// true - оба равны "en-Latn-US"
Хранение канонических форм
При хранении идентификаторов локалей в базах данных или файлах конфигурации использование полных форм обеспечивает согласованность. Каждая французская локаль становится fr-Latn-FR, каждая японская локаль становится ja-Jpan-JP и так далее.
Такая согласованность делает поиск, фильтрацию и группировку по локалям более надежными.
Определение поведения, зависящего от скрипта
Некоторые языки используют несколько скриптов, и скрипт влияет на отображение текста, выбор шрифта и сортировку. Китайский язык может быть написан упрощенными или традиционными иероглифами, а сербский — кириллицей или латиницей.
Добавление вероятных подметок делает скрипт явным. Если пользователь указывает zh без указания скрипта, максимизация преобразует его в zh-Hans-CN, что указывает на использование упрощенных китайских иероглифов.
Как работает алгоритм
Алгоритм Add Likely Subtags использует базу данных информации об использовании языков для определения недостающих компонентов. Эта база данных поддерживается Консорциумом Unicode в рамках 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 и находит наиболее близкое соответствие.
Когда не следует использовать maximize
Вам не нужно максимизировать локали перед передачей их в 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 и выше.
Резюме
Вероятные подметки дополняют частичные идентификаторы локалей, добавляя наиболее распространённые скрипт и регион для данного языка. Метод Intl.Locale.maximize() реализует алгоритм Unicode Add Likely Subtags для выполнения этого расширения.
Ключевые моменты:
- Вероятные подметки основаны на данных о реальном использовании языков
- Метод
maximize()добавляет недостающие коды скрипта и региона - Расширенные теги для календарей и систем нумерации остаются неизменными
- Используйте максимизацию для нормализации пользовательского ввода и сравнения локалей
- API форматирования не требуют максимизированных локалей
Метод maximize() предоставляет стандартизированный способ работы с полными идентификаторами локалей, когда вашей логике приложения требуется явная информация о скрипте и регионе.