Английский – один из самых распространенных языков в мире, каждый седьмой житель планеты говорит на нем. Это первый (родной) язык для 379 миллионов человек, но 917 миллионов говорят на китайском, 460 миллионов – на испанском и 341 миллион – на хинди.
Много людей, не говорящих по-английски, проживает в странах с экспоненциальным ростом Интернета. Если ваше веб-приложение может быть полностью переведено на другой язык, ваш потенциальный целевой рынок может увеличиться во много раз.
JavaScript Internationalization API (API интернационализации), также известный как i18n, позволяет разрабатывать веб-страницы и приложения таким образом, чтобы их можно было легко адаптировать для пользователей, говорящих на разных языках. В этой статье будут рассмотрены различные методы, предлагаемые API и то, как эти возможности можно использовать в коде, чтобы шире охватить международную аудиторию.
Интернационализация (I18n) может быть сложной задачей
Интернационализация выглядит легко… но до тех пор, пока не попытаетесь её использовать. Языки, основанные на латыни, могут быть внешне похожими. Например, форма, запрашивающая имя, адрес электронной почты и дату, переводится следующим образом:
- Испанский: nombre, email, fecha
- Французский: nom, e-mail, date
- Немецкий: name, email, datum
Система интернационализации и локализации Gettext существует уже несколько десятилетий, а библиотеки доступны для большинства языков программирования.
В простейших случаях можно использовать некоторую форму токенизации. Например, вот HTML-шаблон, содержащий следующий код:
<label for="name">{{ NAME }}</label>
Здесь NAME
динамически заменяется на «Name», когда пользователь в качестве основного языка выбрал английский. К сожалению, именно здесь начинаются проблемы для пользовательского интерфейса:
- Могут быть разные варианты для одного и того же языка. Например, испанский, на котором говорят в Испании, не идентичен тому, на котором говорят в Южной Америке.
- В некоторых языках слова могут быть значительно длиннее на других. Например, «email» на русском языке будет выглядеть, как «электронное письмо».
- Текст не всегда ориентирован слева направо. Для некоторых языков слова пишутся справа налево, например: арабский, иврит, курдский и идиш. Ещё написание может быть сверху вниз, например в китайском, корейском, японском и тайваньском языках.
Многие проблемы можно решить, если свести текст к минимуму и использовать CSS-свойства, такие как direction
(направление), writing-mode
(режим письма) и логические размеры для макета.
Терминологическая путаница
Другая путаница возникнет, когда приложению понадобится отображать дату, время, числа, валюты или единицы измерения.
Рассмотрим дату, которая выглядит, как «12/03/24». Она может быть прочитана так:
- «3 December 2024» для жителей США, использующих формат MDY
- «12 March 2024» для жителей Европы, Южной Америки и Азии, использующих формат DMY, и
- «24 March 2012» для жителей Канады, Китая, Японии и Венгрии, которые считают более практичным формат YMD.
Также, имейте в виду, что косая черта в разделителях даты используется не во всех языках.
Число «1,000» может читаться как:
- «Одна тысяча» в США, Великобритании, Канаде, Китае и Японии, а также
- «Один (целый, ноль тысячных)» в Испании, Франции, Германии и России, где десятичная дробь числа отделена запятой.
Ситуация может осложняться даже в одном только английском языке. Термин «1,000 meters» означает:
- 1 километр (или 0,62 мили) для жителей США
- набор из тысячи измерительных приборов в Великобритании, Канаде и Австралии!
JavaScript Intl
API
Малоизвестный JavaScript-объект Intl
в большинстве современных браузеров и сред выполнения реализует API интернационализации ECMAScript. Поддержка в целом неплохая, и даже в IE11 есть много полезных методов. Для старых браузеров есть полифилл, а поддержку API можно определить так:
if (window.Intl) {
// Intl поддерживается
}
Сам API немного необычный. Он предоставляет несколько конструкторов объектов для дат, времени, чисел и списков, которым передаются языковой стандарт и необязательный объект, содержащий параметры конфигурации. Например, вот объект DateTime
, для английского (США):
const dateFormatter = new Intl.DateTimeFormat('en-US');
Этот объект можно использовать любое количество раз для вызова различных методов, которым передается значение Date()
(или ES6 Temporal, если доступно). Метод format()
используется чаще всего, например:
const valentinesDay = dateFormatter.format( new Date('2022-02-14') );
// возвращает US формат "2/14/2022"
const starwarsDay = dateFormatter.format( new Date('2022-05-04') );
// возвращает US формат "5/4/2022"
То же самое можно сделать, создав объект Intl
:
const starwarsDay = new Intl.DateTimeFormat('en-US')
.format(new Date('2022-05-04'));
Помимо метода format()
, некоторые объекты поддерживают ещё:
formatToParts()
, который возвращает массив объектов, содержащих отформатированные строки, например{type: 'weekday', value: 'Monday'}
resolvedOptions()
, который возвращает новый объект со свойствами, отражающими используемый языковой стандарт и параметры форматирования, напримерdateFormatter.resolvedOptions().locale
Определение локалей
Все объекты Intl
требуют передавать в качестве аргумента locale (языковой стандарт). Это строка, которая идентифицирует:
- языковой подтэг
- подтег скрипта (необязательно)
- подтег региона или страны (необязательно)
- один или несколько вариантов подтегов (необязательно)
- одна или несколько последовательностей расширения BCP 47 (необязательно)
- приватная последовательность расширения (необязательно)
Часто, достаточно языка и региона. Например, ru-RU
, fr-FR
и т. д.
Помимо использования строки, объект Intl.locale
может использоваться для создания таких локалей, как английский (США) с 12-часовым форматом времени:
const us = new Intl.Locale('en', {
region: 'US', hourCycle: 'h12', calendar: 'gregory'
});
Это можно использовать в другом конструкторе Intl
, например:
new Intl.DateTimeFormat(us, { timeStyle: 'medium' })
.format( new Date('2022-05-04T13:00:00') );
// "1:00:00 PM"
Если языковой стандарт (locale) не определен, используются настройки языка и региона устройства, например:
new Intl.DateTimeFormat().format(new Date('2022-05-04'));
Pltcm будет возвращаться 5/4/2022
на устройстве с настройками для США и 04/05/2022
на устройстве с настройками для Великобритании.
Даты и время
Следующий инструмент показывает примеры дат и времени, отформатированных с помощью Intl.DateTimeFormat()
:
Конструктору передаются языковой стандарт (locale) и объект с параметрами. У этого объекта есть много различных свойств, хотя обычно редко требуется больше, чем dateStyle
и/или timeStyle
:
свойство | описание |
---|---|
dateStyle |
стиль даты: "full" "long" "medium" "short" |
timeStyle |
стиль времени: "full" "long" "medium" "short" |
calendar |
опции включают: "chinese" "gregory" "hebrew" "indian" "islamic" и т.д. |
dayPeriod |
выражения периода: "narrow" "short" "long" |
numberingSystem |
система нумерации: "arab" "beng" "fullwide" "latn" и т.д. |
localeMatcher |
алгоритм распознавания локали: "lookup" "best fit" |
timeZone |
часовой пояс: "America/New_York" "Europe/Paris" etc. |
hour12 |
true для 12-часового отображения времени |
hourCycle |
часовой цикл: "h11" "h12" "h23" "h24" |
formatMatcher |
алгоритм распознавания формата: "basic" "best fit" |
weekday |
формат дней недели: "long" "short" "narrow" |
era |
формат эры: "long" "short" "narrow" |
year |
формат года: "numeric" "2-digit" |
month |
формат времени: "numeric" "2-digit" "long" "short" "narrow" |
day |
формат дня месяца: "numeric" "2-digit" |
hour |
формат часов: "numeric" "2-digit" |
minute |
формат минут: "numeric" "2-digit" |
second |
формат секунд: "numeric" "2-digit" |
timeZoneName |
любое из: "long" "short" |
Примеры:
// Japanese короткая дата без времени: "2022/05/04"
new Intl.DateTimeFormat("ja-JP", { dateStyle: "short" })
.format( new Date("2022-05-04T13:00") );
// US короткая дата и время: "5/4/22, 1:00 PM"
new Intl.DateTimeFormat("en-US", { dateStyle: "short", timeStyle: "short" })
.format( new Date("2022-05-04T13:00") );
// UK длинная дата, короткое время: "4 May 2022 at 13:00"
new Intl.DateTimeFormat("en-GB", { dateStyle: "long", timeStyle: "short" })
.format( new Date("2022-05-04T13:00") );
// Spanish дата и время полные
// (в зависимости от локали вашей системы)
// "miércoles, 4 de mayo de 2022, 13:00:00 (hora de verano británica)"
new Intl.DateTimeFormat("es-ES", { dateStyle: "full", timeStyle: "full" })
.format( new Date("2022-05-04T13:00") );
Диапазоны дат
Метод formatRange()
принимает две даты и форматирует период наиболее кратким образом в зависимости от языкового стандарта и параметров, например:
// результат: "4 May 2022, 13:00–14:00"
new Intl.DateTimeFormat("en-US", { dateStyle: "long", timeStyle: "short" })
.formatRange(new Date("2022-05-04T13:00"), new Date("2022-05-04T14:00"))
Этот метод имеет ограниченную поддержку в браузерах, но был реализован в Chrome 76.
Относительные периоды
Объект Intl.RelativeTimeFormat()
может отображать периоды, относящиеся к этому моменту времени. У объекта c параметрами меньше опций:
свойство | описание |
---|---|
localeMatcher |
алгоритм распознавания локали: "lookup" "best fit" |
numeric |
одно из "always" , т.е. "1 день назад" или "auto" , т.е. "yesterday" |
style |
формат: "long" "short" "narrow" |
В метод format()
передается числовое значение и единица измерения: "year"
(год), "quarter"
(квартал), "month"
(месяц), "week"
(неделя), "day"
(день), "hour"
(час), "minute"
(минута), или "second"
(секунда). Примеры:
// US 1 день назад, numeric: "1 day ago"
new Intl.RelativeTimeFormat("en-US")
.format( -1, "day" );
// US на 1 день, auto: "tomorrow" (завтра)
new Intl.RelativeTimeFormat("en-US", { numeric: "auto" })
.format( -1, "day" );
// German, следующий месяц auto: "nächsten Monat" (в следующем месяце)
new Intl.RelativeTimeFormat("de-DE", { numeric: "auto" })
.format( 1, "month" );
Числа, валюты, проценты и единицы измерения
Следующий пример показывает варианты использования Intl.NumberFormat()
для форматирования чисел, валют, процентов и единиц измерения:
Конструктору передаются языковой стандарт и объект с параметрами:
свойство | описание |
---|---|
numberingSystem |
система нумерации: "arab" "beng" "deva" "fullwide" "latn" и т.д. |
notation |
тип: "standard" "scientific" "engineering" "compact" |
style |
форматирование: "decimal" "currency" "percent" "unit" — this determines which other options can be set |
currency |
код валюты: "USD" "EUR" "GBP" etc. |
currencyDisplay |
формат валюты: "symbol" "narrowSymbol" "code" "name" |
currencySign |
для отрицательных значений валюты, "standard" знак “минус” или "accounting" для скобок |
unit |
unit type: "centimeter" "inch" "hour" и т.д. |
unitDisplay |
формат единиц изм.: "long" "short" "narrow" |
useGrouping |
false убирает разделитель тысячных |
minimumIntegerDigits |
минимальное количество целых цифр |
minimumFractionDigits |
минимальное количество разрядов дробной части |
maximumFractionDigits |
максимальное количество разрядов дробной части |
minimumSignificantDigits |
минимальное количество значащих цифр |
maximumSignificantDigits |
максимальное количество значащих цифр |
Примеры:
// US округление до 2 десятичных: "12,345.68"
new Intl.NumberFormat("en-US", { maximumSignificantDigits: 2 })
.format( 12345.6789 );
// French округление до 3 десятичных знаков: "12 345,689"
new Intl.NumberFormat("fr-FR", { maximumSignificantDigits: 3 })
.format( 12345.6789 );
// US компактное число, 0 десятичных знаков: "12K"
new Intl.NumberFormat("en-US", { notation: "compact", maximumSignificantDigits: 0 })
.format( 12345.6789 );
// Spanish запись валюты амер. долл.: "12.345,68 US$"
new Intl.NumberFormat("es-ES", {
style: "currency",
currency: "USD",
currencyDisplay: "symbol"
})
.format( 12345.6789 );
// UK метры в длинном формате без десятичн.: "12,346 metres"
new Intl.NumberFormat("en-GB", {
maximumSignificantDigits: 0,
style: "unit",
unit: "meter",
unitDisplay: "long"
})
.format( 12345.6789 );
Списки
Объект Intl.ListFormat()
может форматировать массив элементов в чувствительный к языку список. В английском языке это обычно требует наличия «and» (и) или «or» (или) перед последним элементом.
Объект c параметрами может устанавливать следующие свойства:
свойство | описание |
---|---|
type |
формат вывода: "conjunction" для “и“-списков, "disjunction" для “или“-списков |
style |
форматирование: "long" "short" "narrow" |
Примеры:
const browsers = ['Chrome', 'Firefox', 'Edge', 'Safari'];
// US English: "Chrome, Firefox, Edge, and Safari"
new Intl.ListFormat("en-US", { type: "conjunction" }).format(browsers);
// US English: "Chrome, Firefox, Edge, or Safari"
new Intl.ListFormat("en-US", { type: "disjunction" }).format(browsers);
// French: "Chrome, Firefox, Edge, et Safari"
new Intl.ListFormat("fr-FR", { type: "conjunction" }).format(browsers);
// French: "Chrome, Firefox, Edge, ou Safari"
new Intl.ListFormat("fr-FR", { type: "disjunction" }).format(browsers);
Множественное число
Немного причудливый объект Intl.PluralRules()
включает правила языка с учетом множественного значения числа, lkz форматирования количества элементов. Объект с параметрами может установить для свойства type
одно из следующих значений:
cardinal
: количество вещей (по умолчанию), илиordinal
: рейтинг вещей, как1st
,2nd
, или3rd
в английском
Метод select()
возвращает строку на английском языке, представляющую категорию множественного значения числа: zero
, one
, two
, few
, many
, или other
.
Примеры:
// US English zero cardinal: "other"
new Intl.PluralRules("en-US", { type: "cardinal" }).select(0);
// US English zero ordinal: "other"
new Intl.PluralRules("en-US", { type: "ordinal" }).select(0);
// US English 1 cardinal: "one"
new Intl.PluralRules("en-US", { type: "cardinal" }).select(1);
// US English 1 ordinal: "one"
new Intl.PluralRules("en-US", { type: "ordinal" }).select(1);
// US English 2 cardinal: "other"
new Intl.PluralRules("en-US", { type: "cardinal" }).select(2);
// US English 2 ordinal: "two"
new Intl.PluralRules("en-US", { type: "ordinal" }).select(2);
// US English 3 cardinal: "other"
new Intl.PluralRules("en-US", { type: "cardinal" }).select(3);
// US English 3 ordinal: "few"
new Intl.PluralRules("en-US", { type: "ordinal" }).select(3);
Сравнение строк
Наконец, объект Intl.Collator()
позволяет сравнивать строки с учетом языка. Его объект c параметрами может устанавливать следующие свойства:
свойство | описание |
---|---|
collation |
сопоставление вариантов для определенных языков |
numeric |
true для числового сопоставления, где ‘1 < 2 < 10’ |
caseFirst |
регистр первого символа "upper" или "lower" |
usage |
любая строка "sort" (по умолчанию) или "search" |
sensitivity |
"base" "accent" "case" "variant" |
ignorePunctuation |
true игнорировать пунктуацию |
Метод compare()
сравнивает две строки, например:
// German: возвращает 1
new Intl.Collator('de').compare('z', 'ä');
Заключение
Отображение информации с учетом формата языковых настроек устройства пользователя должно быть простым, если для отображения данных используется JavaScript. Например, следующий код определяет функцию dateFormat()
, которая использует краткий формат даты Intl
или возвращается к YYYY-MM-DD
, если он не поддерживается:
// функция форматирования даты
const dateFormat = (Intl && Intl.DateTimeFormat ?
date => new Intl.DateTimeFormat({ dateStyle: 'short' }).format(date) :
date => date.toISOString().slice(0, 10)
);
// вставляет сегодняшнюю дату в DOM-элемент #today
document.getElementById('today').textContent = dateFormat( new Date() );
Одно это не сделает ваше приложение абсолютно удобным для международной аудитории, но это приблизит его к глобальному распространению.