Мощь Intl API: подробное руководство по встроенной в браузер интернационализации
- понедельник, 25 августа 2025 г. в 00:00:03
В двух словах: интернационализация — это не только перевод текста. Она включает в себя форматирование дат, правильное образование множественного числа, сортировку имен и многое другое с учетом конкретных локалей. Вместо тяжелых сторонних библиотек современный JavaScript предлагает Intl API — мощный встроенный инструмент для работы с i18n. Еще одно напоминание о том, что веб действительно глобален.
Существует распространенное заблуждение, что интернационализация (i18n) сводится лишь к переводу интерфейса. Перевод, конечно, важен, но это лишь одна из частей. Настоящая сложность — в адаптации информации под культурные особенности.
Например:
Как корректно отображать дату в Японии и Германии?
Каким образом формировать множественное число в арабском и английском языках?
Как правильно сортировать список имен для разных языков?
Многие разработчики привыкли решать такие задачи с помощью громоздких сторонних библиотек или, что еще хуже, самописных функций для форматирования. Эти решения работают, но у них есть минусы: увеличенный размер сборки, падение производительности и постоянная необходимость следить за изменением языковых правил и локализационных данных.
На помощь приходит ECMAScript Internationalization API — чаще всего просто Intl
. Этот "тихий гигант", встроенный прямо в среды выполнения JS, до сих пор недооценен. А ведь он — нативный, быстрый и соответствующий стандартам инструмент для интернационализации данных. Intl
позволяет единым образом форматировать числа, даты, списки и многое другое для конкретных локалей. Это яркое свидетельство того, что веб по-настоящему глобален.
В основе работы Intl
лежит понятие локали (locale). Локаль — это не просто двухбуквенный языковой код (например, en
для английского или es
для испанского). Она описывает полный контекст, необходимый для корректного отображения информации для конкретной культурной группы. В нее входят:
язык — основной язык (например, en
, es
, fr
)
письменность — система письма (например, Latn
для латиницы, Cyrl
для кириллицы). К примеру, zh-Hans
означает упрощенный китайский, а zh-Hant
— традиционный
регион — географическая область (например, US
для США, GB
для Великобритании, DE
для Германии). Это важно для вариаций внутри одного языка: например, en-US
и en-GB
отличаются форматами даты, времени и чисел
предпочтения/варианты — дополнительные культурные или языковые особенности. Подробнее об этом можно прочитать в "Выборе языковых тегов" от W3C
Обычно локаль выбирают в соответствии с языком веб-страницы. В HTML это задается через атрибут lang
:
// Получаем язык страницы из атрибута `lang` в HTML
const pageLocale = document.documentElement.lang || 'en-US'; // Если не задано — используем 'en-US'
Иногда может понадобиться переопределить локаль страницы и указать конкретную — например, если на сайте отображается контент на нескольких языках:
// Принудительно задаем локаль, независимо от языка страницы
const tutorialFormatter = new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' });
console.log(`Chinese example: ${tutorialFormatter.format(199.99)}`); // Chinese example: ¥199.99
В некоторых случаях нужно учитывать предпочтительный язык пользователя:
// Используем предпочтительный язык пользователя
const browserLocale = navigator.language || 'ja-JP';
const formatter = new Intl.NumberFormat(browserLocale, { style: 'currency', currency: 'JPY' });
При создании экземпляра преобразователя Intl
можно передать одну или несколько локалей. API автоматически выберет наиболее подходящую локаль на основе доступности и предпочтений.
Объект Intl
предоставляет несколько конструкторов, каждый из которых предназначен для конкретной задачи форматирования. Рассмотрим наиболее часто используемые, а также несколько мощных, но часто недооцененных возможностей.
Форматирование дат и времени — классическая задача i18n. Должно ли это быть MM/DD/YYYY или DD.MM.YYYY? Месяц числом или полностью словом? Intl.DateTimeFormat
справляется со всем этим без лишних сложностей:
const date = new Date(2025, 6, 27, 14, 30, 0); // June 27, 2025, 2:30 PM
// Задаем конкретную локаль и опции (например, полная дата и короткое время)
const options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
timeZoneName: 'shortOffset' // например, "GMT+8"
};
console.log(new Intl.DateTimeFormat('en-US', options).format(date));
// "Friday, June 27, 2025 at 2:30 PM GMT+8"
console.log(new Intl.DateTimeFormat('de-DE', options).format(date));
// "Freitag, 27. Juni 2025 um 14:30 GMT+8"
// Используем `dateStyle` и `timeStyle` для стандартных шаблонов
console.log(new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'short' }).format(date));
// "Friday 27 June 2025 at 14:30"
console.log(new Intl.DateTimeFormat('ja-JP', { dateStyle: 'long', timeStyle: 'short' }).format(date));
// "2025年6月27日 14:30"
Настройки DateTimeFormat
предоставляют огромные возможности — можно управлять отображением года, месяца, дня, дня недели, часов, минут, секунд, часового пояса и многого другого.
Форматирование чисел выходит за рамки простого указания десятичных знаков — в разных локалях по-разному используют разделители тысяч, десятичные точки, валютные символы и знаки процентов:
const price = 123456.789;
// Форматирование валюты
console.log(new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(price));
// "$123,456.79" (автоматически округляется)
console.log(new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(price));
// "123.456,79 €"
// Единицы измерения
console.log(new Intl.NumberFormat('en-US', { style: 'unit', unit: 'meter', unitDisplay: 'long' }).format(100));
// "100 meters"
console.log(new Intl.NumberFormat('fr-FR', { style: 'unit', unit: 'kilogram', unitDisplay: 'short' }).format(5.5));
// "5,5 kg"
Опции, вроде minimumFractionDigits
, maximumFractionDigits
и notation
(например, scientific
или compact
), позволяют точнее настраивать формат чисел.
Форматирование перечислений нередко скрывает в себе больше нюансов, чем кажется. В английском для соединения элементов используют "and", а для альтернатив — "or". Во многих других языках свои союзы, а иногда требуются и особые знаки препинания.
Этот API упрощает задачу, которая в противном случае потребовала бы сложной условной логики:
const items = ['apples', 'oranges', 'bananas'];
// Список с союзом "и" (conjunction)
console.log(new Intl.ListFormat('en-US', { type: 'conjunction' }).format(items));
// "apples, oranges, and bananas"
console.log(new Intl.ListFormat('de-DE', { type: 'conjunction' }).format(items));
// "Äpfel, Orangen und Bananen"
// Список с союзом "или" (disjunction)
console.log(new Intl.ListFormat('en-US', { type: 'disjunction' }).format(items));
// "apples, oranges, or bananas"
console.log(new Intl.ListFormat('fr-FR', { type: 'disjunction' }).format(items));
// "apples, oranges ou bananas"
Фразы вроде "2 дня назад" или "через 3 месяца" часто встречаются в интерфейсах. Но корректная локализация таких выражений требует обширных языковых данных. Intl.RelativeTimeFormat
берет эту задачу на себя и автоматизирует процесс:
const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });
console.log(rtf.format(-1, 'day')); // "yesterday" — вчера
console.log(rtf.format(1, 'day')); // "tomorrow" — завтра
console.log(rtf.format(-7, 'day')); // "7 days ago" — 7 дней назад
console.log(rtf.format(3, 'month')); // "in 3 months" — через 3 месяца
console.log(rtf.format(-2, 'year')); // "2 years ago" — 2 года назад
// Пример на французском
const frRtf = new Intl.RelativeTimeFormat('fr-FR', { numeric: 'auto', style: 'long' });
console.log(frRtf.format(-1, 'day')); // "hier" — вчера
console.log(frRtf.format(1, 'day')); // "demain" — завтра
console.log(frRtf.format(-7, 'day')); // "il y a 7 jours" — 7 дней назад
console.log(frRtf.format(3, 'month')); // "dans 3 mois" — через 3 месяца
Опция numeric: 'always'
переключает вывод в числовой формат — например, "1 day ago" (1 день назад) вместо "yesterday" (вчера).
Этот аспект можно назвать одним из наиболее значимых в i18n. Правила образования множественного числа в языках сильно различаются: например, в английском есть всего две формы — единственное и множественное, а в арабском — отдельные категории для "ноль", "один", "два", "несколько" и "другое".
Intl.PluralRules
позволяет определить "категорию числа" (plural category) для конкретного числа в заданной локали:
const prEn = new Intl.PluralRules('en-US');
console.log(prEn.select(0)); // "other" (для "0 items" — 0 элементов)
console.log(prEn.select(1)); // "one" (для "1 item" — 1 элемент)
console.log(prEn.select(2)); // "other" (для "2 items" — 2 элемента)
const prAr = new Intl.PluralRules('ar-EG');
console.log(prAr.select(0)); // "zero" — ноль
console.log(prAr.select(1)); // "one" — один
console.log(prAr.select(2)); // "two" — два
console.log(prAr.select(10)); // "few" — несколько
console.log(prAr.select(100)); // "other" — другое
Этот API напрямую не ставит слова в форму множественного числа, но он предоставляет нужную классификацию, чтобы выбрать правильную строку перевода из набора сообщений. Например, если есть ключи сообщений вроде item.one
и item.other
, с помощью pr.select(count)
можно подобрать подходящий вариант.
Нужно отобразить название языка, региона или системы письма на языке, который предпочитает пользователь? Intl.DisplayNames
— это универсальное решение для таких задач:
// Отображение названий языков на английском
const langNamesEn = new Intl.DisplayNames(['en'], { type: 'language' });
console.log(langNamesEn.of('fr')); // "French" — Французский
console.log(langNamesEn.of('es-MX')); // "Mexican Spanish" — Мексиканский испанский
// Отображение названий языков на французском
const langNamesFr = new Intl.DisplayNames(['fr'], { type: 'language' });
console.log(langNamesFr.of('en')); // "anglais" — Английский
console.log(langNamesFr.of('zh-Hans')); // "chinois (simplifié)" — Китайский (упрощенный)
// Отображение названий регионов
const regionNamesEn = new Intl.DisplayNames(['en'], { type: 'region' });
console.log(regionNamesEn.of('US')); // "United States" — Соединенные Штаты
console.log(regionNamesEn.of('DE')); // "Germany" — Германия
// Отображение названий систем письма
const scriptNamesEn = new Intl.DisplayNames(['en'], { type: 'script' });
console.log(scriptNamesEn.of('Latn')); // "Latin" — Латиница
console.log(scriptNamesEn.of('Arab')); // "Arabic" — Арабский
Благодаря Intl.DisplayNames
, мы можем обойтись без ручного прописывания множества соответствий для названий языков, регионов или систем письма, что делает наши приложения более надежными и лаконичными.
Что насчет поддержки браузерами? Хорошая новость: все современные браузеры (Chrome, Firefox, Safari, Edge) полностью поддерживают основные функции, которые мы рассмотрели (DateTimeFormat
, NumberFormat
, ListFormat
, RelativeTimeFormat
, PluralRules
, DisplayNames
). Эти API можно использовать без полифиллов для большинства пользователей.
Intl API
— ключевой инструмент современной веб-разработки для глобальной аудитории. Он дает фронтенд-разработчикам возможность быстро и корректно реализовать глубоко локализованный пользовательский опыт, опираясь на встроенные в браузер оптимизированные механизмы.
Используя Intl
, мы уменьшаем зависимость от сторонних библиотек, сокращаем размер сборки и повышаем производительность — при этом, приложение корректно учитывает разнообразные языковые и культурные особенности пользователей по всему миру. Не надо больше изобретать самописную логику форматирования — лучше опереться на этот проверенный инструмент, соответствующий стандартам.
Важно помнить, что Intl
отвечает за форматирование данных. Хотя он невероятно мощный, он не решает все задачи интернационализации. Перевод контента, двунаправленный текст (RTL/LTR), локализованная типографика и тонкие культурные нюансы за пределами форматирования данных все еще требуют отдельного внимания. Однако для точного и интуитивного отображения динамических данных Intl
— это первое решение, которое должно приходить в голову.
MDN Web Docs:
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud - в нашем Telegram-канале ↩