История появления и развития

Первые версии JavaScript создавались как лёгкий язык сценариев для браузера и практически не учитывали задачи интернационализации. Основное внимание уделялось работе с DOM, обработке событий и простым вычислениям. Форматирование дат, времени, чисел и валют зависело либо от возможностей браузера, либо от ручной реализации разработчика.

В эпоху раннего веба большинство сайтов ориентировалось на англоязычную аудиторию. Типичные проблемы локализации тогда решались примитивными способами:

  • хранением строк в отдельных объектах;
  • ручным форматированием дат;
  • самостоятельной расстановкой разделителей тысяч;
  • использованием массивов с названиями месяцев;
  • созданием собственных функций склонения и перевода.

Пример типичного кода начала 2000-х:

function formatDate(date) {
  const months = [
    "January", "February", "March",
    "April", "May", "June",
    "July", "August", "September",
    "October", "November", "December"
  ];

  return `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()}`;
}

Подобный подход имел серьёзные недостатки:

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

С ростом интернета и распространением многоязычных сервисов проблема интернационализации стала критически важной.


Интернационализация и локализация

Развитие Intl API напрямую связано с двумя фундаментальными понятиями:

Интернационализация (i18n)

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

Сокращение i18n образовано так:

  • первая буква — i;
  • последняя буква — n;
  • между ними 18 букв.

Локализация (l10n)

Адаптация приложения под конкретный язык, регион и культурные особенности.

Сюда входят:

  • перевод интерфейса;
  • форматирование валют;
  • отображение дат;
  • правила сортировки;
  • склонение слов;
  • особенности письменности.

До появления Intl API JavaScript практически не предоставлял встроенных средств ни для i18n, ни для l10n.


Ранние ограничения JavaScript

До стандартизации интернационализации разработчики сталкивались с множеством ограничений.

Проблемы форматирования чисел

В разных странах используются разные правила:

Регион Формат
США 1,234.56
Германия 1.234,56
Франция 1 234,56

JavaScript долгое время предоставлял только методы:

number.toFixed()
number.toPrecision()

Они не учитывали локаль пользователя.


Проблемы работы с датами

Метод:

date.toString()

возвращал строку в формате, зависящем от браузера и системы.

Метод:

date.toLocaleString()

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

Например:

new Date().toLocaleString()

в разных браузерах мог выдавать совершенно разные результаты.


Отсутствие нормальной сортировки строк

Обычная сортировка:

["яблоко", "Ёж", "еж"].sort()

не учитывала языковые правила.

Для многих языков это было критично:

  • немецкого;
  • русского;
  • шведского;
  • японского;
  • китайского;
  • арабского.

Рост глобального веба

В середине 2000-х веб начал стремительно глобализироваться.

Появились:

  • международные интернет-магазины;
  • социальные сети;
  • SaaS-платформы;
  • облачные сервисы;
  • мобильные веб-приложения.

Компании начали выходить на международный рынок, и проблема локализации стала экономически значимой.

Например:

  • интернет-магазины должны были отображать цены в локальной валюте;
  • банки — корректно форматировать денежные суммы;
  • системы бронирования — учитывать часовые пояса;
  • поисковые системы — правильно сортировать текст.

Unicode как фундамент

Одним из важнейших этапов стало распространение стандарта Unicode.

До Unicode существовали десятки несовместимых кодировок:

  • ASCII;
  • Windows-1251;
  • KOI8-R;
  • Shift-JIS;
  • ISO-8859;
  • GB2312.

Это создавало огромное количество проблем:

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

JavaScript постепенно начал опираться на Unicode как на основную модель представления строк.

Однако Unicode решал только задачу хранения символов. Он не решал:

  • форматирование;
  • сортировку;
  • локальные правила;
  • особенности письменности.

Для этого требовалась отдельная инфраструктура.


Появление ICU

Ключевую роль в будущем Intl API сыграла библиотека ICU.

ICU — International Components for Unicode

ICU представляет собой набор библиотек для интернационализации, разработанный первоначально компанией IBM.

Она предоставляла:

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

ICU стала фундаментом для множества платформ:

  • Java;
  • Android;
  • C++;
  • браузерных движков.

Именно на ICU позднее начали опираться реализации Intl API.


CLDR и локальные данные

Для корректной интернационализации требовались огромные базы локальных правил.

Так появился проект CLDR.

CLDR — Common Locale Data Repository

Проект поддерживается организацией Unicode Consortium.

CLDR содержит:

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

Пример:

  • в США дата записывается как MM/DD/YYYY;
  • в Казахстане — DD.MM.YYYY;
  • в Японии — YYYY/MM/DD.

Intl API в значительной степени использует данные CLDR через ICU.


Стандартизация ECMAScript Internationalization API

Полноценная стандартизация началась примерно в 2010 году.

Основная проблема состояла в том, что браузеры реализовывали локализацию по-разному.

Это приводило к несовместимости:

date.toLocaleString("fr")

мог работать иначе в Firefox, Safari и Internet Explorer.

Для решения проблемы комитет TC39 начал разработку отдельного стандарта интернационализации.


ECMA-402

Ключевым событием стало появление стандарта ECMA-402.

Что такое ECMA-402

Это официальный стандарт ECMAScript Internationalization API.

Он дополняет основной стандарт ECMAScript и описывает:

  • работу локалей;
  • форматирование;
  • сравнение строк;
  • правила интернационализации.

Первое издание ECMA-402 было опубликовано в 2012 году.


Первые возможности Intl API

В первоначальную версию вошли:

API Назначение
Intl.Collator Сравнение и сортировка строк
Intl.NumberFormat Форматирование чисел
Intl.DateTimeFormat Форматирование дат и времени

Эти API стали первой полноценной встроенной системой интернационализации в JavaScript.


Intl.Collator

Intl.Collator решал проблему корректной сортировки строк.

Пример:

const collator = new Intl.Collator("ru");

["ёж", "яблоко", "еж"].sort(collator.compare);

Теперь сортировка зависела от правил языка.

Поддерживались:

  • чувствительность к регистру;
  • акцентам;
  • диакритическим знакам;
  • языковым особенностям.

Intl.NumberFormat

Intl.NumberFormat упростил форматирование чисел.

Пример:

const formatter = new Intl.NumberFormat("de-DE");

formatter.format(1234567.89);

Результат:

1.234.567,89

Появилась поддержка:

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

Intl.DateTimeFormat

Intl.DateTimeFormat стандартизировал работу с датами.

Пример:

const formatter = new Intl.DateTimeFormat("fr-FR");

formatter.format(new Date());

API учитывал:

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

Влияние браузерных движков

Развитие Intl API тесно связано с развитием движков JavaScript.

V8

Движок компании Google, используемый в:

  • Chrome;
  • Node.js.

V8 начал активно интегрировать ICU для поддержки Intl.


SpiderMonkey

Движок браузера Firefox от Mozilla.

Один из первых движков с хорошей поддержкой ECMA-402.


JavaScriptCore

Движок Safari от Apple.

Долгое время имел частичную поддержку Intl, особенно на мобильных устройствах.


Intl API в Node.js

Появление Node.js значительно ускорило развитие серверной интернационализации.

Ранние версии Node.js имели ограниченную поддержку ICU из-за размера бинарников.

Существовало несколько режимов:

Режим Особенности
none Intl отключён
small-icu Только английская локаль
full-icu Полная поддержка

Позднее полноценная поддержка ICU стала стандартом.


Расширение Intl API

После успеха первых версий стандарт начал активно развиваться.


Intl.PluralRules

Добавлен для корректной работы с множественными формами слов.

Пример:

const rules = new Intl.PluralRules("ru");

rules.select(5);

Результат:

many

Это особенно важно для сложных языков:

  • русского;
  • польского;
  • арабского.

Intl.RelativeTimeFormat

Позволил форматировать относительное время.

Пример:

const rtf = new Intl.RelativeTimeFormat("ru");

rtf.format(-1, "day");

Результат:

1 день назад

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


Intl.ListFormat

API для локализованного объединения списков.

Пример:

const formatter = new Intl.ListFormat("ru");

formatter.format(["JavaScript", "TypeScript", "Node.js"]);

Результат:

JavaScript, TypeScript и Node.js

Intl.DisplayNames

Позволил получать локализованные названия:

  • стран;
  • языков;
  • валют;
  • регионов.

Пример:

const names = new Intl.DisplayNames(["ru"], {
  type: "region"
});

names.of("US");

Результат:

Соединённые Штаты

Intl.Locale

Добавил полноценную работу с локалями как объектами.

Пример:

const locale = new Intl.Locale("ru-KZ");

console.log(locale.language);
console.log(locale.region);

Это стало важным шагом к более гибкой настройке интернационализации.


Intl.Segmenter

Появился для корректного разбиения текста.

Особенно важен для языков без пробелов:

  • китайского;
  • японского;
  • тайского.

Пример:

const segmenter = new Intl.Segmenter("ja", {
  granularity: "word"
});

Проблема временных зон

Одной из самых сложных задач оставалась работа с датами и временем.

Основные трудности:

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

Для решения JavaScript использует базу данных IANA Time Zone Database.

Примеры зон:

  • Europe/Paris
  • Asia/Almaty
  • America/New_York

Intl API начал глубоко интегрироваться с этой системой.


Влияние мобильных платформ

С распространением смартфонов интернационализация стала ещё важнее.

Мобильные приложения требовали:

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

Браузеры на Android и iOS начали активно улучшать поддержку Intl.


Переход от библиотек к встроенным API

До широкого распространения Intl разработчики активно использовали сторонние библиотеки.

Наиболее популярными были:

Библиотека Назначение
Moment.js Работа с датами
Globalize.js Интернационализация
Numeral.js Форматирование чисел

Однако встроенный Intl API имел преимущества:

  • высокая производительность;
  • использование нативных движков;
  • меньший размер приложений;
  • единое поведение;
  • актуальные локальные данные.

Постепенно экосистема начала переходить на встроенные средства.


Влияние на современные фреймворки

Современные фреймворки активно используют Intl API.

React

Библиотеки:

  • react-intl;
  • formatjs.

Строятся поверх Intl API.


Angular

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

  • пайпов дат;
  • валют;
  • чисел.

Vue

Международные плагины Vue также опираются на Intl.


Intl и производительность

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

Причины:

  • реализация на уровне движка;
  • использование ICU;
  • оптимизация браузерами;
  • кэширование локальных данных.

Тем не менее создание экземпляров форматтеров считается относительно дорогой операцией:

new Intl.NumberFormat("ru-RU");

Поэтому в крупных приложениях форматтеры обычно переиспользуются.


Развитие стандарта после ES2020

После 2020 года развитие Intl API ускорилось.

Появились новые предложения:

API Назначение
Intl.DurationFormat Форматирование длительности
Intl.MessageFormat Интернационализированные сообщения
Temporal Современная работа с датами

Некоторые из них всё ещё находятся в стадии стандартизации.


Связь Intl API и Temporal

Исторически объект Date считался одним из самых проблемных API JavaScript.

Проблемы:

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

Для решения этих проблем был разработан Temporal.

Хотя Temporal является отдельным API, он тесно интегрирован с Intl:

Temporal.Now.zonedDateTimeISO()

В сочетании с Intl это создаёт современную систему работы со временем.


Современное состояние Intl API

Сегодня Intl API представляет собой полноценную инфраструктуру интернационализации.

Он поддерживает:

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

Поддерживаются:

  • григорианский календарь;
  • исламский;
  • буддийский;
  • японский;
  • китайский календари.

Значение Intl API для экосистемы JavaScript

Появление Intl API стало одним из важнейших этапов эволюции JavaScript.

API изменил подход к разработке международных приложений:

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

Сегодня Intl API является фундаментом практически любого крупного веб-приложения, ориентированного на международную аудиторию.