Формат BCP 47

BCP 47 (Best Current Practice 47) — стандарт описания языковых тегов, используемый для локализации, интернационализации и настройки региональных параметров. В экосистеме JavaScript этот формат лежит в основе работы Intl API: форматирования дат, чисел, валют, единиц измерения, сортировки строк, правил множественного числа и других возможностей.

Большинство методов Intl принимают строку локали именно в формате BCP 47:

new Intl.DateTimeFormat('en-US')
new Intl.NumberFormat('fr-FR')
new Intl.Collator('de-DE')

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


Структура языкового тега

Тег BCP 47 состоит из последовательности подзаголовков (subtags), разделённых дефисом:

language-script-region-variant-extension

Пример:

zh-Hant-TW

Разбор:

Часть Значение
zh китайский язык
Hant традиционная письменность
TW Тайвань

Языковой подзаголовок

Язык — обязательная часть тега.

'en'
'ru'
'fr'
'ja'
'ar'

Некоторые распространённые коды:

Код Язык
en английский
ru русский
de немецкий
es испанский
it итальянский
zh китайский
ja японский

Intl использует стандарты ISO 639.


Региональный подзаголовок

Регион уточняет локальные особенности языка.

'en-US'
'en-GB'
'pt-BR'
'fr-CA'

Примеры различий:

new Intl.DateTimeFormat('en-US').format(new Date())
12/31/2025
new Intl.DateTimeFormat('en-GB').format(new Date())
31/12/2025

Различия между локалями одного языка

Английский

new Intl.NumberFormat('en-US').format(1234567.89)
1,234,567.89
new Intl.NumberFormat('en-GB').format(1234567.89)
1,234,567.89

Формат чисел одинаковый, но различаются:

  • формат даты;
  • валюта по умолчанию;
  • правила времени;
  • некоторые единицы измерения.

Португальский

new Intl.NumberFormat('pt-PT').format(1234567.89)
1 234 567,89
new Intl.NumberFormat('pt-BR').format(1234567.89)
1.234.567,89

Подзаголовок письменности

Некоторые языки используют несколько систем письма.

Пример:

sr-Cyrl
sr-Latn
Тег Значение
sr-Cyrl сербский, кириллица
sr-Latn сербский, латиница

Пример:

new Intl.DisplayNames(['sr-Cyrl'], { type: 'language' })

Китайский язык и письменность

Китайский особенно часто использует подзаголовок script.

zh-Hans
zh-Hant
Тег Значение
zh-Hans упрощённый китайский
zh-Hant традиционный китайский

Регион может дополнительно уточнять локаль:

zh-Hans-CN
zh-Hant-TW
zh-Hant-HK

Варианты локалей

BCP 47 поддерживает специальные варианты.

Пример:

de-DE-1996

Обозначает немецкий язык с орфографической реформой 1996 года.

Другой пример:

sl-rozaj

Диалект резьянского языка.

В JavaScript варианты используются редко, но стандарт их поддерживает полностью.


Extensions — расширения локали

BCP 47 позволяет добавлять расширения.

Наиболее важное для Intl API — Unicode extension (-u-).

Пример:

en-US-u-ca-buddhist

Разбор:

Часть Значение
en-US английский США
u Unicode extension
ca-buddhist буддийский календарь

Unicode Extensions

Unicode extensions позволяют управлять поведением Intl без передачи дополнительных опций.


Календарь

new Intl.DateTimeFormat(
  'en-US-u-ca-islamic'
).format(new Date())

Используется исламский календарь.

Другие варианты:

Значение Календарь
gregory григорианский
buddhist буддийский
japanese японский
islamic исламский

Система нумерации

new Intl.NumberFormat(
  'ar-EG-u-nu-arab'
).format(123456)

Результат:

١٢٣٤٥٦

Популярные варианты:

Значение Система
latn латинская
arab арабская
thai тайская
hanidec китайская

Часовой формат

en-US-u-hc-h24
Значение Формат
h12 12-часовой
h23 23-часовой
h24 24-часовой

Сортировка

de-DE-u-co-phonebk

Специальная немецкая телефонная сортировка.


Передача массива локалей

Intl API умеет принимать массив языков.

const locales = ['fr-CA', 'fr', 'en']

new Intl.DateTimeFormat(locales)

Механизм:

  1. Проверяется fr-CA;
  2. если локаль недоступна — fr;
  3. затем en.

Это называется locale negotiation.


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

Метод supportedLocalesOf позволяет узнать, какие локали поддерживаются средой выполнения.

Intl.DateTimeFormat.supportedLocalesOf([
  'ru-RU',
  'fr-FR',
  'xx-YY'
])

Результат:

['ru-RU', 'fr-FR']

Canonicalization

Intl автоматически нормализует некоторые локали.

Пример:

Intl.getCanonicalLocales('EN-us')

Результат:

['en-US']

Приведение включает:

  • корректный регистр;
  • замену устаревших кодов;
  • нормализацию структуры.

Регистр символов в BCP 47

Формально регистр не важен:

en-us
EN-US
eN-uS

Все варианты валидны.

Однако существует соглашение:

Часть Регистр
язык нижний
script Capitalized
регион верхний

Правильная запись:

sr-Latn-RS

Невалидные локали

Некоторые строки не соответствуют BCP 47.

Пример:

new Intl.DateTimeFormat('english')

В большинстве движков:

RangeError

Правильный вариант:

new Intl.DateTimeFormat('en')

Частичные локали

Допустимо указывать только язык:

new Intl.DateTimeFormat('ru')

Система самостоятельно подберёт регион по умолчанию.


Undefined locale и fallback

Если локаль отсутствует:

new Intl.DateTimeFormat(undefined)

используется системная локаль среды выполнения.

В браузере — язык пользователя.

В Node.js — настройки ОС или окружения.


Locale Matcher

Intl использует два алгоритма сопоставления локалей:

Значение Описание
lookup строгое сопоставление
best fit интеллектуальный подбор

Пример:

new Intl.DateTimeFormat('en-XX', {
  localeMatcher: 'lookup'
})

Объект Intl.Locale

Современный JavaScript предоставляет специальный класс для работы с BCP 47.

const locale = new Intl.Locale('ru-Cyrl-RU')

Свойства Intl.Locale

locale.language
ru
locale.script
Cyrl
locale.region
RU

Максимизация локали

Метод maximize() добавляет недостающие части.

new Intl.Locale('ru').maximize()

Результат:

ru-Cyrl-RU

Минимизация локали

new Intl.Locale('ru-Cyrl-RU').minimize()

Результат:

ru

Получение календаря

const locale = new Intl.Locale(
  'en-US-u-ca-buddhist'
)

locale.calendar

Результат:

buddhist

Получение numbering system

const locale = new Intl.Locale(
  'ar-EG-u-nu-arab'
)

locale.numberingSystem

Взаимодействие BCP 47 и Intl.NumberFormat

new Intl.NumberFormat(
  'fr-FR'
).format(1234567.89)

Результат:

1 234 567,89

Формат определяется именно локалью BCP 47.


Взаимодействие BCP 47 и Intl.DateTimeFormat

new Intl.DateTimeFormat(
  'ja-JP-u-ca-japanese'
).format(new Date())

Дата будет отображаться с японской эрой.


Взаимодействие BCP 47 и Intl.RelativeTimeFormat

new Intl.RelativeTimeFormat('ru')

Правила склонения зависят от языка локали.


Взаимодействие BCP 47 и Intl.Collator

const collator = new Intl.Collator('sv-SE')

Шведская сортировка отличается от английской.

Например, буква ä сортируется иначе.


Intl.DisplayNames

API умеет отображать названия языков и регионов.

const dn = new Intl.DisplayNames(
  ['ru'],
  { type: 'region' }
)

dn.of('US')

Результат:

США

Часто используемые локали

Локаль Описание
en-US английский, США
en-GB английский, Великобритания
ru-RU русский, Россия
fr-FR французский, Франция
de-DE немецкий, Германия
ja-JP японский, Япония
zh-CN китайский, Китай
zh-TW китайский, Тайвань
ar-SA арабский, Саудовская Аравия

Частые ошибки

Использование подчёркивания вместо дефиса

Неправильно:

en_US

Правильно:

en-US

Использование произвольных названий

Неправильно:

russian
english

Правильно:

ru
en

Неверный регистр script

Неправильно:

sr-latn-rs

Правильно:

sr-Latn-RS

Legacy locale codes

Некоторые старые коды автоматически заменяются.

Старый Новый
iw he
in id
ji yi

Пример:

Intl.getCanonicalLocales('iw')

Результат:

['he']

Private Use Subtags

BCP 47 поддерживает пользовательские расширения.

Пример:

en-US-x-company

Часть после x- интерпретируется приложением самостоятельно.

Intl обычно игнорирует такие расширения.


Grandfathered Tags

Некоторые исторические теги сохранены ради совместимости.

Пример:

i-klingon

Современный аналог:

tlh

Parsing locale вручную

Разбор локалей через регулярные выражения крайне нежелателен.

Причины:

  • сложность стандарта;
  • наличие legacy-форм;
  • extensions;
  • private subtags;
  • grandfathered tags.

Предпочтительно использовать:

Intl.Locale

Практика использования

Форматирование валют

new Intl.NumberFormat('de-DE', {
  style: 'currency',
  currency: 'EUR'
}).format(1000)

Локализованные даты

new Intl.DateTimeFormat('ru-RU', {
  dateStyle: 'full'
}).format(new Date())

Интерфейсы с выбором языка

navigator.languages

Возвращает массив BCP 47 локалей браузера пользователя.

Пример:

[
  'ru-RU',
  'en-US'
]

RFC и стандарты

BCP 47 основан на нескольких RFC:

Документ Назначение
RFC 5646 структура языковых тегов
RFC 4647 сопоставление локалей

Роль ICU и CLDR

Intl API опирается на:

Технология Назначение
ICU библиотека интернационализации
CLDR база локализационных данных Unicode

Именно они определяют:

  • форматы дат;
  • валюты;
  • правила сортировки;
  • системы письма;
  • локализованные названия.

Полезные методы Intl для работы с локалями

Intl.getCanonicalLocales

Intl.getCanonicalLocales([
  'EN-us',
  'fr-fr'
])

resolvedOptions

const formatter =
  new Intl.DateTimeFormat('ru')

formatter.resolvedOptions()

Результат:

{
  locale: 'ru-RU',
  calendar: 'gregory',
  numberingSystem: 'latn',
  timeZone: 'Europe/Moscow'
}

Совместимость

BCP 47 поддерживается:

  • всеми современными браузерами;
  • Node.js;
  • Deno;
  • Bun;
  • мобильными WebView.

Большинство возможностей Intl API требуют полноценной ICU-поддержки среды выполнения.