Валидация локалей

Работа с локалями в JavaScript строится поверх стандарта BCP 47, который определяет структуру языковых тегов: язык, регион, скрипт, варианты, расширения и приватные подстановки. Валидация локалей в контексте Intl заключается не только в проверке формальной корректности строки, но и в приведении её к каноническому виду, сопоставлении с поддерживаемыми локалями окружения и обработке неоднозначных или частично корректных значений.

Структура локали и требования BCP 47

Локаль в BCP 47 представляется последовательностью субтегов, разделённых дефисом:

  • языковой код (en, ru, zh)
  • скрипт (Latn, Cyrl, Hans)
  • регион (US, GB, CN)
  • варианты (posix, fonipa)
  • расширения (u-ca-buddhist, nu-arab)
  • приватные теги (x-whatever)

Примеры корректных локалей:

  • en
  • en-US
  • ru-RU
  • zh-Hans-CN
  • sr-Cyrl-RS

Примеры некорректных или проблемных значений:

  • en__US (некорректный формат)
  • english-US (невалидный языковой код)
  • ru-RU-123 (некорректный вариант)
  • пустая строка

Важно различать формальную валидность и семантическую поддержку: строка может быть корректной по BCP 47, но не поддерживаться окружением выполнения.


Каноникализация локалей

Одной из ключевых операций является приведение локалей к каноническому виду. В JavaScript это выполняется через:

  • Intl.getCanonicalLocales()

Этот метод:

  • принимает строку или массив локалей
  • нормализует регистр (например, en-usen-US)
  • удаляет дубликаты
  • приводит теги к стандартной форме ICU/CLDR
  • выбрасывает ошибку при полностью некорректных значениях

Пример поведения:

Intl.getCanonicalLocales("en-us")
// ["en-US"]
Intl.getCanonicalLocales(["ru-ru", "ru-RU", "en"])
// ["ru-RU", "en"]

При некорректном значении:

Intl.getCanonicalLocales("invalid-locale")
// RangeError

Каноникализация не означает проверку поддержки. Она гарантирует структурную корректность и унификацию представления.


Проверка поддерживаемых локалей

Второй уровень валидации связан с тем, поддерживается ли локаль конкретной реализацией движка. Для этого используется:

  • Intl.NumberFormat.supportedLocalesOf()
  • Intl.DateTimeFormat.supportedLocalesOf()
  • Intl.Collator.supportedLocalesOf()

Общий принцип одинаков: переданный список фильтруется, оставляя только поддерживаемые локали.

Intl.NumberFormat.supportedLocalesOf(["en-US", "xx-YY"])
// ["en-US"]

Здесь важно разделять:

  • синтаксическую валидность (BCP 47)
  • каноническую нормализацию
  • поддержку в рантайме

Локаль может быть канонически корректной, но не поддерживаться форматтером.


Алгоритм выбора локали (locale matching)

При создании объектов Intl происходит сопоставление локалей:

new Intl.DateTimeFormat(["fr-CA", "fr-FR"])

Алгоритм выбирает наиболее подходящую локаль из списка запросов и списка доступных локалей окружения.

Существуют два основных режима:

  • lookup — строгий поиск совпадения или постепенное упрощение тега
  • best fit — эвристический выбор наиболее близкой локали

Валидация в этом контексте включает:

  • удаление некорректных локалей
  • нормализацию
  • выбор первого подходящего значения
  • fallback на локаль по умолчанию среды

Intl.Locale как инструмент структурной валидации

Современный API предоставляет объект:

  • Intl.Locale

Он позволяет выполнять более строгую проверку структуры локали и разбирать её на компоненты.

const loc = new Intl.Locale("en-US-u-ca-buddhist");

Если строка некорректна, выбрасывается RangeError.

Intl.Locale обеспечивает:

  • проверку валидности BCP 47
  • разбор языка, региона, скрипта
  • обработку Unicode extensions (-u-)
  • работу с приватными тегами (-x-)

Пример извлечения компонентов:

const loc = new Intl.Locale("zh-Hans-CN");

loc.language; // "zh"
loc.script;   // "Hans"
loc.region;   // "CN"

Это превращает строковую валидацию в структурную модель данных.


Нормализация регистра и формы записи

BCP 47 требует определённого регистра:

  • язык — lowercase (en, ru)
  • скрипт — Capitalized (Latn, Cyrl)
  • регион — UPPERCASE (US, GB)

Валидация через Intl автоматически приводит значения к корректной форме:

Intl.getCanonicalLocales("eN-uS")
// ["en-US"]

Эта нормализация устраняет ошибки, связанные с пользовательским вводом или внешними источниками данных.


Обработка некорректных локалей

Некорректные локали классифицируются по типам:

Синтаксически неверные

  • неправильные символы
  • неверная структура
  • отсутствие языкового субтега

Результат: RangeError при использовании Intl.Locale или getCanonicalLocales.

Семантически бессмысленные

  • неизвестные коды регионов
  • несуществующие языки

Некоторые движки допускают такие значения как расширенные теги, но они не гарантируют поддержки.

Частично валидные

  • корректный язык + некорректное расширение
  • валидная локаль с неизвестным вариантом

В таких случаях часть тега может быть проигнорирована или нормализована.


Удаление дубликатов и унификация списков

При работе с массивами локалей важной частью валидации является дедупликация:

Intl.getCanonicalLocales(["en-US", "en-us", "en-US"])

Результат:

["en-US"]

Это критично для:

  • конфигураций интернационализации
  • пользовательских предпочтений
  • HTTP-заголовков Accept-Language

Fallback-механизмы

Валидация локали всегда связана с механизмом запасного выбора:

  • основной список локалей
  • частично совпадающие варианты
  • локаль по умолчанию среды

Пример логики:

  1. проверка массива локалей
  2. удаление некорректных значений
  3. каноникализация
  4. поиск поддерживаемых локалей
  5. выбор первой доступной
  6. fallback на системную локаль

Если итоговый список пуст, используется локаль окружения (например, en-US в браузерах на английской локали).


Unicode расширения и их валидация

BCP 47 допускает расширения через -u-, которые влияют на поведение форматирования:

  • календарь (ca)
  • система чисел (nu)
  • часовой пояс (tz)

Пример:

"ja-JP-u-ca-japanese"

Валидация расширений включает:

  • проверку структуры ключей
  • проверку значений на допустимость
  • нормализацию порядка

Некорректные расширения могут быть отброшены или проигнорированы без ошибки, в зависимости от реализации движка.


Валидация пользовательского ввода

Данные, поступающие извне (формы, API, конфигурации), требуют многоуровневой обработки:

  1. проверка на пустые строки
  2. приведение к массиву
  3. удаление null и undefined
  4. каноникализация через Intl.getCanonicalLocales
  5. фильтрация поддерживаемых локалей

Пример устойчивой обработки:

function normalizeLocales(input) {
  return Intl.getCanonicalLocales(
    (Array.isArray(input) ? input : [input])
      .filter(Boolean)
  );
}

Сравнение стратегий: строгая и мягкая валидация

Существует два подхода:

Строгая валидация

  • использование Intl.Locale
  • выброс ошибок при любом отклонении
  • подходит для конфигураций и критичных систем

Мягкая валидация

  • использование getCanonicalLocales
  • игнорирование некорректных элементов
  • fallback вместо ошибок

Выбор зависит от контекста:

  • конфигурации приложения → строгая
  • пользовательские предпочтения → мягкая

Частые ошибки при работе с локалями

  • использование устаревших или неканонических форм
  • отсутствие обработки массива локалей
  • игнорирование fallback-логики
  • предположение, что любая BCP 47 локаль поддерживается движком
  • попытка ручной валидации через регулярные выражения вместо Intl API

Регулярные выражения не покрывают весь BCP 47 стандарт, особенно расширения и вариации, поэтому использование встроенных механизмов остаётся единственным надёжным способом.


Итоговая модель валидации в Intl

Валидация локалей в экосистеме Intl представляет собой многослойный процесс:

  • синтаксическая проверка BCP 47
  • каноникализация представления
  • дедупликация списков
  • проверка поддержки в рантайме
  • применение алгоритма сопоставления
  • fallback на локаль окружения

Эти механизмы образуют единый конвейер обработки локалей, в котором строковое значение превращается в строго определённый и предсказуемый идентификатор локализации.