Pull to refresh
1902.97
Timeweb Cloud
То самое облако

JavaScript: немного об интернационализации и локализации веб-приложений

Reading time18 min
Views13K


Привет, друзья!


В данной статье я хочу рассказать вам об Internationalization API — интерфейсе, предоставляемом браузером, позволяющем выполнять интернационализацию и локализацию веб-приложений.


Статья состоит из 2 частей: теоретической и практической. В теоретической части мы кратко рассмотрим возможности, предоставляемые Internationalization API. В практической — создадим пример локализованного приложения с помощью разработанной мной утилиты.


Теория


Internationalization API предоставляет следующие возможности:


  • локализованное (далее предполагается) сравнение строк
  • форматирование чисел, включая валюту, различные единицы измерения и проценты
  • форматирование даты и времени, включая относительные периоды, такие как завтра, вчера, через неделю и т.д.
  • форматирование названий языков, регионов, скриптов и валют
  • форматирование списков с соединительным союзом И или разделительным союзом ИЛИ
  • "плюрализация" — перевод во множественное число
  • преобразование регистра

В настоящее время Internationalization API поддерживается всеми современными браузерами.


Несмотря на то, что в JavaScript существуют такие методы для локализации как:



их зачастую оказывается недостаточно.


Кроме того, поведение данных методов определяется конкретной реализацией ECMAScript, т.е. браузером.


Функционал, определенный в Internationalization API, инкапсулирован в объекте Intl. Данный объект не имеет внутреннего метода [[Construct]], поэтому не может вызываться как конструктор с помощью ключевого слова new. Он также не имеет внутреннего метода [[Call]], поэтому не может вызываться как функция.


Intl включает в себя следующие интерфейсы-конструкторы:


  • Collator — сравнение строк
  • DateTimeFormat — форматирование даты и времени
  • DisplayNames — форматирование названий языков, регионов и т.д. на других языках
  • ListFormat — форматирование списков
  • Locale — определение локали
  • NumberFormat — форматирование чисел
  • PluralRules — плюрализация
  • RelativeTimeFormat — форматирование относительных периодов времени

Возможные значения:



Пример получения текущей даты и времени в дефолтной локали


// [] означает текущую локаль (локаль по умолчанию)
const currentDateAndTime = new Intl.DateTimeFormat([], {
  dateStyle: 'short',
  timeStyle: 'short'
}).format()

console.log(currentDateAndTime) // 03.08.2021, 15:57

Locale


Конструктор Locale используется для создания экземпляров идентификаторов локали. Первым обязательным аргументом, передаваемым Locale, является локаль, которая может состоять из следующего:


  • код языка
  • код диалекта
  • код региона или страны
  • один или несколько уникальных вариантных подтегов (subtags)
  • одна или несколько последовательностей из расширения BCP 47
  • последовательность из расширения для частного использования

В большинстве случаев достаточно указать код языка или код языка и код страны через дефис:


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

Вторым опциональным аргументом Locale является объект с настройками:


const ru = new Intl.Locale(
  'ru',
  { region: 'RU', hourCycle: 'h24', calendar: 'gregory' }
)

Локаль может быть строкой или объектом. Пустой массив означает использование текущей локали пользователя:


const now = new Intl.DateTimeFormat([], { timeStyle: 'short' }).format()

DateTimeFormat


Конструктор DateTimeFormat используется для форматирования даты и времени. Он принимает локаль и объект с настройками.


Сигнатура


new Intl.DateTimeFormat(locale: object | string | [], options: object).format(date)
// [] - локаль по умолчанию
// date - дата, время или дата и время

Настройки


Свойство Описание
timeZone часовой пояс: UTC, America/New_York, Europe/Paris и т.д.
calendar календарь: chinese, gregory, hebrew, indian, islamic и т.д.
numberingSystem система счисления: arab, beng, fullwide, latin и т.д.
localeMatcher алгоритм для поиска совпадений: lookup, best fit
formatMatcher алгоритм для форматирования: basic, best fit
hour12 если имеет значение true, используется 12-часовой формат
hourCycle часовой формат: h11, h12, h23, h24
dateStyle стиль форматирования даты: full, long, medium, short
weekday день недели: long, short, narrow
day день месяца: numeric, 2-digit
month месяц: numeric, 2-digit, long, short, narrow
year год: numeric, 2-digit
era эпоха: long, short, narrow
timeStyle стиль форматирования времени: full, long, medium, short
hour часы: numeric, 2-digit
minute минуты: numeric, 2-digit
second секунды: numeric, 2-digit
dayPeriod часть дня (утро, вечер и т.п.): narrow, short, long
timeZoneName название часового пояса (UTC, PTC): long, short

Настройки localeMatcher и formatMatcher могут передаваться любому конструктору, предоставляемому Intl, но используются редко.


По умолчанию new Intl.DateTimeFormat().format() возвращает текущую дату в кратком виде (dateStyle: short).


Примеры


const formatDateTime = ({ locale = [], date = Date.now(), ...options } = {}) =>
  new Intl.DateTimeFormat(locale, options).format(date)

console.log(
  '\n',
  // русский
  formatDateTime(), // 17.08.2021
  '\n',
  // американский английский
  formatDateTime({ locale: 'en-US', dateStyle: 'short', timeStyle: 'short' }), // 8/17/21, 3:56 PM,
  '\n',
  // британский английский
  formatDateTime({ locale: 'en-GB', dateStyle: 'long', timeStyle: 'short' }), // 17 August 2021 at 15:57
  '\n',
  // японский
  formatDateTime({ locale: 'ja-JP', dateStyle: 'short' }), // 2021/08/17
  '\n',
  // испанский
  formatDateTime({ locale: 'es-ES', dateStyle: 'full', timeStyle: 'full' }), // martes, 17 de agosto de 2021, 15:57:49 (hora estándar de Ekaterimburgo)
  '\n',
  // французский
  formatDateTime({
    locale: 'fr-FR',
    weekday: 'long',
    day: '2-digit',
    month: 'long',
    year: 'numeric',
    hour: '2-digit'
  }) // mardi 17 août 2021, 15 h
)

Другие методы


  • formatToParts() — возвращает массив объектов, содержащих форматированную дату в виде пар ключ/значение ({ type: 'weekday', value: 'Monday' })
  • formatRange(startDate, endDate) — возвращает диапазон, например, 01/10/2022, 10:00 AM - 12:00 PM
  • formatRangeToParts()
  • resolveOptions() — возвращает объект со свойствами, значениями которых являются вычисленные настройки форматирования для локали, даты и времени

RelativeTimeFormat


Конструктор RelativeTimeFormat используется для локализации относительного времени, например, вчера, завтра, на следующей неделе, в прошлом месяце и т.д. Данный метод принимает локаль и объект с настройками.


Сигнатура


new Intl.RelativeTimeFormat(locale, options).format(amount, unit)
// amount - количество единиц времени
// unit - единица времени: `day`, `month`, `year` и т.д.

Положительное число в amount означает будущее, отрицательное — прошлое.


Настройки


Свойство Описание
numeric always — "1 день назад" (дефолтное значение), auto — "вчера"
style long (дефолтное значение), short, narrow

В данном случае метод format в качестве аргументов принимает число и единицу времени.


Примеры


const formatRelativeTime = ({
  locale = [],
  value = '1 day',
  ...options
} = {}) => {
  const [amount, unit] = value.split(/[\s_]/)
  return new Intl.RelativeTimeFormat(locale, options).format(amount, unit)
}

console.log(
  '\n',
  formatRelativeTime(), // через 1 день
  '\n',
  formatRelativeTime({ locale: 'en-US', value: '-1 day', numeric: 'auto' }), // yesterday
  '\n',
  formatRelativeTime({
    locale: 'fr-FR',
    value: '1 week',
    style: 'long'
  }), // dans 1 semaine
  '\n',
  formatRelativeTime({
    locale: 'ja-JP',
    value: '-1 month',
    numeric: 'auto',
    style: 'long'
  }) // 先月
)

NumberFormat


Конструктор NumberFormat используется для форматирования чисел, валюты, процентов и единиц измерения, таких как длина, температура и др. Данный метод принимает локаль и объект с настройками.


Сигнатура


new Intl.NumberFormat(locale, options).format(number)

Настройки


Свойство Описание
style вид единиц: decimal — число с плавающей точкой, currency — валюта, percent — проценты, unit — единицы измерения. От этой настройки зависят другие
notation стиль форматирования: standard, scientific, engineering, compact
numberingSystem система счисления: arab, beng, deva, fullwide, latn и др.
minimumIntegerDigits минимальное количество цифр целой части числа (от 1 до 21; по умолчанию 1)
minimumFractionDigits минимальное количество цифр после запятой (от 0 до 20; по умолчанию 0)
maximumFractionDigits максимальное количество цифр после запятой (от 0 до 20; по умолчанию наибольшее значение из minimumFractionDigits и 3)
minimumSignificantDigits минимальное количество значащих цифр (от 1 до 21; по умолчанию 1)
maximumSignificantDigits минимальное количество значащих цифр (от 1 до 21; по умолчанию minimumSignificantDigits)
signDisplay отображение символов +/-: auto, never, always, exceptZero
useGrouping если имеет значение false, разделители тысяч будут игнорироваться
compactDisplay форматирование при использовании нотации compact
currency код валюты при использовании стиля currency: USD, EUR, RUB и т.д.
currencyDisplay отображение символа/названия валюты при использовании стиля currency: symbol, narrowSymbol, code, name
currencySign форматирование отрицательных значений при использовании стиля currency: standard, accounting
unit вид единицы измерения: centimeter, meter, minute, hour, byte и т.д.
unitDisplay формат отображения единицы измерения: long, short, narrow

Примеры


const formatNumber = ({ locale = [], number = 1234.56, ...options } = {}) =>
  new Intl.NumberFormat(locale, options).format(number)

console.log(
  '\n',
  formatNumber(),
  '\n', // 1 234,56
  formatNumber({ locale: 'en-US' }),
  '\n', // 1,234.56
  formatNumber({ locale: 'de-DE', style: 'currency', currency: 'EUR' }),
  '\n', // 1.234,56 €
  formatNumber({ locale: 'fr-FR', style: 'percent' }),
  '\n', // 123 456 %
  formatNumber({
    locale: 'it-IT',
    style: 'unit',
    unit: 'celsius',
    minimumFractionDigits: 3
  }),
  '\n' // 1.234,560 °C
)

DisplayNames


Конструктор DisplayNames используется для форматирования названий языков, диалектов, регионов и валют на другом языке. Данный метод принимает локаль и объект с настройками.


Сигнатура


new Intl.DisplayNames(locale, options).of(localeOf)

Настройки


Свойство Описание
type тип названия: language, region, script, currency
style стиль форматирования: long, short, narrow
fallback резерв: code, none

Обратите внимание: настройка type является обязательной. При этом хорошо поддерживается только type со значением language.


Примеры


const formatName = ({
  locale = [],
  localeOf = 'en-US',
  type = 'language',
  ...options
} = {}) => new Intl.DisplayNames(locale, { type, ...options }).of(localeOf)

console.log(
  '\n',
  formatName(),
  '\n', // американский английский
  formatName({
    localeOf: 'Egyp',
    type: 'script'
  }),
  '\n', // египетская иероглифическая
  formatName({
    locale: 'fr-FR',
    localeOf: 'AU',
    type: 'region'
  }),
  '\n', // Australie - Авcтралия по-французски
  formatName({
    locale: 'pl-PL',
    localeOf: 'GBP',
    type: 'currency',
    style: 'long'
  }),
  '\n' // funt szterling - английские фунты стерлингов на польском
)

ListFormat


Конструктор ListFormat используется для форматирования списков путем подстановки соединительного союза И или разделительного союза ИЛИ. Данный метод принимает локаль и объект с настройками.


Сигнатура


new Intl.ListFormat(locale, options).format(list)

Настройки


Свойство Описание
type формат вывода: conjunction (и; дефолтное значение), disjunction (или), unit (нет)
style стиль форматирования: long, short, narrow

Примеры


const browsers = ['Chrome', 'Firefox', 'Safari']

const formatList = ({
  locale = [],
  list = browsers,
  ...options
} = {}) => new Intl.ListFormat(locale, options).format(list)

console.log(
  '\n',
  formatList(),
  '\n', // Chrome, Firefox и Safari
  formatList({ locale: 'en-US', style: 'short' }),
  '\n', // Chrome, Firefox, & Safari
  formatList({ locale: 'ja-JP', type: 'disjunction' }),
  '\n' // Chrome、Firefox、またはSafari
)

Collator


Конструктор Collator используется для сравнения строк с учетом локали. Данный метод принимает локаль и объект с настройками.


Сигнатура


new Intl.Collator(locale, options).compare(str1, str2)

Настройки


Свойство Описание
usage sort — сортировка (дефолтное значение) или search — поиск
sensitivity чувствительность: base, accent, case, variant
collation сопоставление вариантов для нескольких языков
numeric true означает сравнение чисел
ignorePunctuation true означает игнорирование пунктуации
caseFirst upper — сначала идут строки, начинающиеся с большой буквы, lower — сначала идут строки, начинающиеся с маленькой буквы

Результат


  • 0 — строки равны
  • -1 — первая строка "меньше" второй
  • 1 — первая строка "больше" второй

Примеры


const compareValues = ({ locale = [], values = [], ...options } = {}) =>
  new Intl.Collator(locale, options).compare(...values)

console.log(
  '\n',
  compareValues({ values: ['a', 'á'], sensitivity: 'base' }),
  '\n', // 0 -> одинаковые
  compareValues({ values: ['2', '10'] }),
  '\n', // 1 -> '2' > '10'
  compareValues({ values: ['2', '10'], numeric: true }),
  '\n' // -1 -> 2 < 10
)

PluralRules


Конструктор PluralRules используется для плюрализации (перевода во множественное число). Данный метод принимает локаль и объект с настройками.


Сигнатура


new Intl.PluralRules(locale, options).select(number)

Настройки


Свойство Описание
type cardinal — количество элементов (дефолтное значение), ordinal — порядок элемента (первый, второй, третий и т.д.)

Примеры


const pluralize = ({ locale = [], number = 1, ...options } = {}) =>
  new Intl.PluralRules(locale, options).select(number)

console.log(
  '\n',
  pluralize(),
  '\n', // one
  pluralize({ locale: 'ru-RU', type: 'ordinal' }),
  '\n' // other
)

В настоящее время поддерживается только локаль en-US.


Практика


В данном разделе мы создадим небольшое локализованное приложение с помощью разработанной мной утилиты easy-intl.


Принцип работы утилиты подробно описан в документации. Многие вещи были рассмотрены в теоретической части. Поэтому, с вашего позволения, здесь я опишу только ключевые моменты.


Демо приложения:



Создаем директорию для проекта и устанавливаем easy-intl:


mkdir intl-app
cd !$

yarn add easy-intl
# or
npm i easy-intl

Структура проекта будет следующей:


- intl
  - options.js - настройки для утилиты
  - dictionary.js - словарь для кастомной локализации
- index.html
- style.css
- script.js

Как сказал бы Ватсон, все ЭЛЕМЕНТАРНО.


В стилях у нас не будет ничего интересного, поэтому


вот:
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Montserrat', sans-serif;
  letter-spacing: 1px;
}

h1 {
  margin: 1rem 0;
  text-align: center;
  font-size: 2rem;
}

.container {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}

.card {
  margin: 1rem;
  width: 320px;
  padding: 0.75rem;
  border-radius: 4px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}

main > .card {
  position: fixed;
  top: 10%;
  left: 50%;
  transform: translateX(-50%);
  margin: 1rem auto;
  box-shadow: none;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  border-radius: 50%;
}

main > .card img {
  position: absolute;
  z-index: -1;
  width: 100px;
  opacity: 0.9;
  filter: brightness(0.9);
}

select {
  width: 50px;
  padding: 0.25rem;
  font-weight: bold;
  border-radius: 4px;
  opacity: 0.95;
  cursor: pointer;
}

select:focus-visible {
  outline: none;
}

h2 {
  font-size: 1.4rem;
  margin: 0.75rem 0;
}

h3,
h4 {
  margin: 0.5rem 0;
}

h3 {
  font-size: 1.2rem;
}

h4 {
  font-size: 1.1rem;
}

Что касается разметки, то в процессе локализации easy-intl ориентируется на следующие атрибуты:


  • data-intl_type — тип содержимого для локализации. Возможные значения:
    • date — дата или время, или дата и время
    • relative — относительный период времени
    • number — число, включая валюту, проценты и единицы измерения
    • names — название языка, региона, валюты и т.д.
    • list — список
    • compare — строки для сравнения
    • plural — строка для перевода во множественное число
    • custom — строка, которая используется в качестве ключа словаря для кастомной локализации
  • data-intl_value — собственно, содержимое для локализации; зависит от data-intl_type
  • data-intl_options — строковые (inline) настройки для локализации; зависят от data-intl_type. Данные настройки имеют высший приоритет
  • data-intl_root — индикатор (атрибут без значения) корневого HTML-элемента для локализации. easy-intl позволяет выполнять разную локализацию для разных частей приложения — дочерние элементы нижележащего (в иерархии DOM) data-intl_root не будут локализоваться вышестоящим data-intl_root
  • data-intl_map — по умолчанию easy-intl выполняет локализацию значений свойства textContext, а также атрибутов title и placeholder элементов, имеющих атрибут data-intl_type. Значение атрибута data-intl_map становится ключом объекта map вида { [data-intl_map]: локализованное data-intl_value } — геттера экземпляра EasyIntl. Данный объект позволяет выполнять ручную локализацию других свойств и атрибутов соответствующих элементов.

Вот как будет выглядеть наша разметка:


<main data-intl_root id="global_root">
  <h1 data-intl_type="custom" data-intl_value="main_title" data-intl_map="main_title">
    Easy Intl
  </h1>
  <section class="card">
    <img src="https://cdn-icons-png.flaticon.com/512/814/814513.png" alt="" role="presentation" />
    <select id="locale">
      <option value="ru-RU">Ru</option>
      <option value="en-US" selected>En</option>
      <option value="de-DE">De</option>
      <option value="ja-JP">Ja</option>
    </select>
  </section>

  <div class="container">
    <section class="card">
      <h2 data-intl_type="custom" data-intl_value="date_title" data-intl_map="date_title">
        Дата и время
      </h2>
      <div class="box">
        <h3 data-intl_type="custom" data-intl_value="without_options">
          Без встроенных настроек
        </h3>
        <time data-intl_type="date" data-intl_value="2021-08-16 12:00"></time>
      </div>
      <div class="box">
        <h3 data-intl_type="custom" data-intl_value="with_options">
          Со встроенными настройками
        </h3>
        <time data-intl_type="date" data-intl_value="2021-08-16 12:00" data-intl_options="dateStyle: long, timeStyle: 'short'"></time>
      </div>
    </section>

    <section data-intl_root id="local_root" class="card">
      <h2 data-intl_type="custom" data-intl_value="date_title">
        Дата и время
      </h2>
      <div class="box">
        <h3 data-intl_type="custom" data-intl_value="without_options">
          Без встроенных настроек
        </h3>
        <time data-intl_type="date" data-intl_value="2021-08-16 12:00"></time>
      </div>
      <div class="box">
        <h3 data-intl_type="custom" data-intl_value="with_options">
          Со встроенными настройками
        </h3>
        <time data-intl_type="date" data-intl_value="2021-08-16 12:00" data-intl_options="day: 'numeric', month: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit'"></time>
      </div>
    </section>

    <section class="card">
      <h2 data-intl_type="custom" data-intl_value="relative_title">
        Относительное время
      </h2>
      <div class="box">
        <h3 data-intl_type="custom" data-intl_value="without_options">
          Без встроенных настроек
        </h3>
        <p data-intl_type="relative" data-intl_value="1 day" data-intl_map="1_day"></p>
      </div>
      <div class="box">
        <h3 data-intl_type="custom" data-intl_value="with_options">
          Со встроенными настройками
        </h3>
        <p data-intl_type="relative" data-intl_value="1_day" data-intl_options='numeric: "always"'></p>
      </div>
    </section>

    <section class="card">
      <h2 data-intl_type="custom" data-intl_value="number_title">Число</h2>
      <div class="box">
        <h3 data-intl_type="custom" data-intl_value="without_options">
          Без встроенных настроек
        </h3>
        <p data-intl_type="number" data-intl_value="123.45"></p>
      </div>
      <div class="box">
        <h3 data-intl_type="custom" data-intl_value="with_options">
          Со встроенными настройками
        </h3>
        <h4 data-intl_type="custom" data-intl_value="currency_title">
          Валюта
        </h4>
        <p data-intl_type="number" data-intl_value="123.45" data-intl_options="style: currency; currency: USD"></p>
        <h4 data-intl_type="custom" data-intl_value="unit_title">
          Единица измерения
        </h4>
        <p data-intl_type="number" data-intl_value="123.45" data-intl_options="{ style: unit; unit: celsius }"></p>
      </div>
    </section>

    <section class="card">
      <h2 data-intl_type="custom" data-intl_value="names_title">
        Названия языков и т.д.
      </h2>
      <div class="box">
        <h3 data-intl_type="custom" data-intl_value="without_options">
          Без встроенных настроек
        </h3>
        <p data-intl_type="names" data-intl_value="ru"></p>
      </div>
      <div class="box">
        <h3 data-intl_type="custom" data-intl_value="with_options">
          Со встроенными настройками
        </h3>
        <p data-intl_type="names" data-intl_value="EUR" data-intl_options="type: currency"></p>
      </div>
    </section>

    <section class="card">
      <h2 data-intl_type="custom" data-intl_value="list_title">Список</h2>
      <div class="box">
        <h3 data-intl_type="custom" data-intl_value="without_options">
          Без встроенных настроек
        </h3>
        <p data-intl_type="list" data-intl_value="Chrome, Firefox, Safari"></p>
      </div>
      <div class="box">
        <h3 data-intl_type="custom" data-intl_value="with_options">
          Со встроенными настройками
        </h3>
        <p data-intl_type="list" data-intl_value="[Chrome, 'Firefox', `Safari`]" data-intl_options="type: disjunction"></p>
      </div>
    </section>
  </div>
</main>

Здесь у нас имеется следующее:


  • два элемента с атрибутом data-intl_root (с идентификаторами global_root и local_root)
  • переключатель локали (с идентификатором locale) — по умолчанию easy-intl выполняет автоматическую локализацию при создании экземпляра и изменении локали. Автоматическую локализацию можно отключить, передав в конструктор EasyIntl настройку autorun со значением false
  • разделы с датой и временем (один в global_root и еще один в local_root), относительным временем, числами (число, валюта и единица измерения), названием языка (язык и валюта) и списком
  • все заголовки имеют атрибут data-intl_custom — перевод заголовков содержится в словаре
  • некоторые элементы имеют атрибут data-intl_map
  • настройки могут содержать символы '"{}` и разделяться запятой или точкой запятой. Ключ и значение должны разделяться двоеточием
  • список может содержать символы '"[]` и разделяться запятой или точкой запятой

Определим некоторые настройки для глобальной локализации в intl/options.js (некоторые настройки являются дефолтными, но в данном случае большого значения это не имеет):


export default {
  date: { dateStyle: 'short' },
  relative: { numeric: 'auto' },
  number: { style: 'decimal' },
  names: { type: 'language' },
  list: { type: 'conjunction' }
}

Определим словарь для заголовков в intl/dictionary.js для русского и американского английского языков:


export default {
  'ru-RU': {
    main_title: 'Пример использования Easy Intl',
    locale_title: 'Локаль',
    date_title: 'Дата и время',
    relative_title: 'Относительное время',
    number_title: 'Число',
    currency_title: 'Валюта',
    unit_title: 'Единицы измерения',
    names_title: 'Названия языков и т.д.',
    list_title: 'Список',
    without_options: 'Без встроенных настроек',
    with_options: 'Со встроенными настройками'
  },
  'en-US': {
    main_title: 'Example of use Easy Intl',
    locale_title: 'Locale',
    date_title: 'Date and time',
    relative_title: 'Relative time',
    number_title: 'Number',
    currency_title: 'Currency',
    unit_title: 'Units',
    names_title: 'Language names etc.',
    list_title: 'List',
    without_options: 'Without inline options',
    with_options: 'With inline options'
  }
}

Переходим к основному скрипту (script.js). Импортируем EasyIntl, настройки и словарь:


import { EasyIntl } from './node_modules/easy-intl/index.js'
import options from './intl/options.js'
import dictionary from './intl/dictionary.js'

Создаем экземпляр EasyIntl, передавая в конструктор локаль en-US, селектор корневого элемента для глобальной локализации, словарь и настройки (обратите внимание, что объект настроек должен быть распакован):


const globalIntl = new EasyIntl({
  locale: 'en-US',
  root: '#global_root',
  dictionary,
  ...options
})

Поскольку мы не определяем настройку autorun со значением false, создание экземпляра EasyIntl приводит к автоматической локализации приложения (за исключением содержимого дочерних элементов local_root).


Прелесть easy-intl (и, конечно, самого Intl) заключается в том, что дефолтная локаль пользователя определяется автоматически. Это означает, что для локализации приложения на языке пользователя достаточно создать экземпляр EasyIntl (разумеется, при условии добавления к необходимым элементам атрибутов data-intl_type и data-intl_value). Поскольку для всех вспомогательных функций, используемых easy-intl, включая, корневой элемент, определены дефолтные настройки (многие дефолтные настройки определяются Intl), для локализации приложения достаточно выполнить:


new EasyIntl()

Посмотрим на локализуемые элементы, карту локализации, в целом, и локализацию основного заголовка, в частности:


console.log(globalIntl.elements) // [h1, h2, h3, ...]
console.log(globalIntl.map)
/*
{
  1_day: "tomorrow"
  date_title: "Date and time"
  main_title: "Example of use Easy Intl"
}
*/
console.log(globalIntl.map['main_title']) // Example of use Easy Intl

Выполним ручную локализацию содержимого дочерних элементов local_root:


const localIntl = new EasyIntl({
  // отключаем автоматическую локализацию
  autorun: false,
  dictionary,
  // передаем другие настройки для даты
  date: { day: '2-digit', month: 'long', year: 'numeric' }
})
// определяем локаль
localIntl.locale = 'ja-JP'
// определяем корневой элемент
localIntl.root = '#local_root'
// выполняем локализацию
localIntl.localize()

Получаем ссылку на переключатель локали и обрабатываем ее изменение:


const localeSelector = document.querySelector('#locale_selector')
localeSelector.onchange = (e) => {
  // изменение свойства `locale` приводит к автоматической локализации
  globalIntl.locale = e.target.value
}

Приоритет настроек следующий (от минимального к максимальному):


  • new EasyIntl(options)
  • intl.localize(options)
  • data-intl_options="options"

Получаем вполне работоспособное локализованное приложение:








Пожалуй, это все, чем я хотел поделиться с вами в данной статье.


Если вас заинтересовала утилита easy-intl, не стесняйтесь использовать ее в своих проектах. Как всегда, приветствуется любой фидбэк как в форме личных сообщений, так и на GitHub.


Что касается планов по дальнейшей разработке утилиты, то, кроме небольших доработок, связанных с преобразованием строковых значений атрибутов data-intl_value в пригодный для Intl формат, в них входит добавление типов для TypeScript и, возможно, адаптация утилиты под React в форме хука или связки провайдера и хука.


Как видите, Internationalization API предоставляет довольно интересные возможности, которые при правильном использовании могут существенно облегчить процесс интернационализации и локализации веб-приложений.


Вместе с тем надо понимать, что Internationalization API не предназначен для перевода (его задачи четко обозначены в спецификации), поэтому многие вещи придется делать вручную или прибегать к помощи таких интерфейсов, как Google Cloud Translation API или API Переводчика Яндекса.


Также важно учитывать, что Internationalization API является относительно новым интерфейсом, некоторые его возможности еще находятся в процессе реализации браузерами, и, вероятно, в будущем появятся новые "фичи".


Благодарю за внимание и хорошего дня!




Tags:
Hubs:
Total votes 9: ↑7 and ↓2+8
Comments2

Articles

Information

Website
timeweb.cloud
Registered
Founded
Employees
201–500 employees
Location
Россия
Representative
Timeweb Cloud