Первые версии 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 образовано так:
i;n;Адаптация приложения под конкретный язык, регион и культурные особенности.
Сюда входят:
До появления Intl API JavaScript практически не предоставлял встроенных средств ни для i18n, ни для l10n.
До стандартизации интернационализации разработчики сталкивались с множеством ограничений.
В разных странах используются разные правила:
| Регион | Формат |
|---|---|
| США | 1,234.56 |
| Германия | 1.234,56 |
| Франция | 1 234,56 |
JavaScript долгое время предоставлял только методы:
number.toFixed()
number.toPrecision()
Они не учитывали локаль пользователя.
Метод:
date.toString()
возвращал строку в формате, зависящем от браузера и системы.
Метод:
date.toLocaleString()
существовал давно, но реализовывался браузерами непоследовательно.
Например:
new Date().toLocaleString()
в разных браузерах мог выдавать совершенно разные результаты.
Обычная сортировка:
["яблоко", "Ёж", "еж"].sort()
не учитывала языковые правила.
Для многих языков это было критично:
В середине 2000-х веб начал стремительно глобализироваться.
Появились:
Компании начали выходить на международный рынок, и проблема локализации стала экономически значимой.
Например:
Одним из важнейших этапов стало распространение стандарта Unicode.
До Unicode существовали десятки несовместимых кодировок:
Это создавало огромное количество проблем:
JavaScript постепенно начал опираться на Unicode как на основную модель представления строк.
Однако Unicode решал только задачу хранения символов. Он не решал:
Для этого требовалась отдельная инфраструктура.
Ключевую роль в будущем Intl API сыграла библиотека ICU.
ICU представляет собой набор библиотек для интернационализации, разработанный первоначально компанией IBM.
Она предоставляла:
ICU стала фундаментом для множества платформ:
Именно на ICU позднее начали опираться реализации Intl API.
Для корректной интернационализации требовались огромные базы локальных правил.
Так появился проект CLDR.
Проект поддерживается организацией Unicode Consortium.
CLDR содержит:
Пример:
MM/DD/YYYY;DD.MM.YYYY;YYYY/MM/DD.Intl API в значительной степени использует данные CLDR через ICU.
Полноценная стандартизация началась примерно в 2010 году.
Основная проблема состояла в том, что браузеры реализовывали локализацию по-разному.
Это приводило к несовместимости:
date.toLocaleString("fr")
мог работать иначе в Firefox, Safari и Internet Explorer.
Для решения проблемы комитет TC39 начал разработку отдельного стандарта интернационализации.
Ключевым событием стало появление стандарта ECMA-402.
Это официальный стандарт ECMAScript Internationalization API.
Он дополняет основной стандарт ECMAScript и описывает:
Первое издание ECMA-402 было опубликовано в 2012 году.
В первоначальную версию вошли:
| API | Назначение |
|---|---|
| Intl.Collator | Сравнение и сортировка строк |
| Intl.NumberFormat | Форматирование чисел |
| Intl.DateTimeFormat | Форматирование дат и времени |
Эти API стали первой полноценной встроенной системой интернационализации в JavaScript.
Intl.Collator решал проблему корректной сортировки
строк.
Пример:
const collator = new Intl.Collator("ru");
["ёж", "яблоко", "еж"].sort(collator.compare);
Теперь сортировка зависела от правил языка.
Поддерживались:
Intl.NumberFormat упростил форматирование чисел.
Пример:
const formatter = new Intl.NumberFormat("de-DE");
formatter.format(1234567.89);
Результат:
1.234.567,89
Появилась поддержка:
Intl.DateTimeFormat стандартизировал работу с
датами.
Пример:
const formatter = new Intl.DateTimeFormat("fr-FR");
formatter.format(new Date());
API учитывал:
Развитие Intl API тесно связано с развитием движков JavaScript.
Движок компании Google, используемый в:
V8 начал активно интегрировать ICU для поддержки Intl.
Движок браузера Firefox от Mozilla.
Один из первых движков с хорошей поддержкой ECMA-402.
Движок Safari от Apple.
Долгое время имел частичную поддержку Intl, особенно на мобильных устройствах.
Появление Node.js значительно ускорило развитие серверной интернационализации.
Ранние версии Node.js имели ограниченную поддержку ICU из-за размера бинарников.
Существовало несколько режимов:
| Режим | Особенности |
|---|---|
| none | Intl отключён |
| small-icu | Только английская локаль |
| full-icu | Полная поддержка |
Позднее полноценная поддержка ICU стала стандартом.
После успеха первых версий стандарт начал активно развиваться.
Добавлен для корректной работы с множественными формами слов.
Пример:
const rules = new Intl.PluralRules("ru");
rules.select(5);
Результат:
many
Это особенно важно для сложных языков:
Позволил форматировать относительное время.
Пример:
const rtf = new Intl.RelativeTimeFormat("ru");
rtf.format(-1, "day");
Результат:
1 день назад
Ранее подобная функциональность обычно реализовывалась сторонними библиотеками.
API для локализованного объединения списков.
Пример:
const formatter = new Intl.ListFormat("ru");
formatter.format(["JavaScript", "TypeScript", "Node.js"]);
Результат:
JavaScript, TypeScript и Node.js
Позволил получать локализованные названия:
Пример:
const names = new Intl.DisplayNames(["ru"], {
type: "region"
});
names.of("US");
Результат:
Соединённые Штаты
Добавил полноценную работу с локалями как объектами.
Пример:
const locale = new Intl.Locale("ru-KZ");
console.log(locale.language);
console.log(locale.region);
Это стало важным шагом к более гибкой настройке интернационализации.
Появился для корректного разбиения текста.
Особенно важен для языков без пробелов:
Пример:
const segmenter = new Intl.Segmenter("ja", {
granularity: "word"
});
Одной из самых сложных задач оставалась работа с датами и временем.
Основные трудности:
Для решения JavaScript использует базу данных IANA Time Zone Database.
Примеры зон:
Europe/ParisAsia/AlmatyAmerica/New_YorkIntl API начал глубоко интегрироваться с этой системой.
С распространением смартфонов интернационализация стала ещё важнее.
Мобильные приложения требовали:
Браузеры на Android и iOS начали активно улучшать поддержку Intl.
До широкого распространения Intl разработчики активно использовали сторонние библиотеки.
Наиболее популярными были:
| Библиотека | Назначение |
|---|---|
| Moment.js | Работа с датами |
| Globalize.js | Интернационализация |
| Numeral.js | Форматирование чисел |
Однако встроенный Intl API имел преимущества:
Постепенно экосистема начала переходить на встроенные средства.
Современные фреймворки активно используют Intl API.
Библиотеки:
Строятся поверх Intl API.
Фреймворк использует Intl для:
Международные плагины Vue также опираются на Intl.
Одним из преимуществ API стала высокая эффективность.
Причины:
Тем не менее создание экземпляров форматтеров считается относительно дорогой операцией:
new Intl.NumberFormat("ru-RU");
Поэтому в крупных приложениях форматтеры обычно переиспользуются.
После 2020 года развитие Intl API ускорилось.
Появились новые предложения:
| API | Назначение |
|---|---|
| Intl.DurationFormat | Форматирование длительности |
| Intl.MessageFormat | Интернационализированные сообщения |
| Temporal | Современная работа с датами |
Некоторые из них всё ещё находятся в стадии стандартизации.
Исторически объект Date считался одним из самых
проблемных API JavaScript.
Проблемы:
Для решения этих проблем был разработан Temporal.
Хотя Temporal является отдельным API, он тесно интегрирован с Intl:
Temporal.Now.zonedDateTimeISO()
В сочетании с Intl это создаёт современную систему работы со временем.
Сегодня Intl API представляет собой полноценную инфраструктуру интернационализации.
Он поддерживает:
Поддерживаются:
Появление Intl API стало одним из важнейших этапов эволюции JavaScript.
API изменил подход к разработке международных приложений:
Сегодня Intl API является фундаментом практически любого крупного веб-приложения, ориентированного на международную аудиторию.