Пожалуйста, опишите ошибку

Нашли баг? Помогите нам его исправить, заполнив эту форму

Интернационализация приложения на React

Сергей Петтай
Front-end разработчик

В нынешних реалиях разработки приложений важную роль играет доступность контента. Под доступностью приложения подразумевается следование множеству стандартам организации W3C, одним из которых является интернационализация.

Интернационализация — это создание и развитие содержания продукта, программы или документации, целью которой является обеспечение локализации контента для целевых рынков, различающихся по культуре, региону или языку. Как правило, среди разработчиков слово интернационализация заменяют более коротким словосочетанием — «i18n», где 18 — это число между «i» и «n» в слове «Internationalization».

Зачем и почему нужна интернационализация?

Интернационализация играет огромную роль при разработке приложения, так как оно будет доступно огромному числу пользователей, которые при этом не обязательно будут находиться в одной стране, а вероятно по всему миру. Интернационализированное приложение адаптируется к концертному языку и культуре, переводя контент на язык пользователя и форматируя данные в соответствии с ожиданиями пользователя. А это означает, что каждый пользователь ожидает получить хороший пользовательский опыт и чувствовать себя комфортно при работе с сервисом. Например, пользователи какой-либо страны, даже если они хорошо знают английский язык, предпочитают совершать покупки, а также пользоваться услугами службы поддержки на их родном языке. Если приложение ориентировано на европейский рынок, который насчитывает около 50 государств, то стоит учитывать, что практически в каждой стране существуют свои региональные особенности по отображению валют, дат или чисел. В том случае, если приложение ориентированно на весь мир, то стоит принять во внимание страны Восточной Азии (Китай, Япония и др.), Ближнего Востока (Саудовская Аравия, Афганистан, Иран и др.), в которых написание текста может быть справа налево или сверху вниз, а числа записываются с помощью индо-арабских или персидских цифр.

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

Для того, чтобы интернационализировать приложение, необходимо создать определенный словарь, содержащий слова и строки на разных языках, а также адаптировать даты и числа для соответствующих стран. Особое внимание стоит уделить отображению даты и времени в привычном формате для каждой из стран. В приведенной ниже таблице можно ознакомиться с формами записи дат для разных стран:

Страна Формат даты Дата
Россия DD.MM.YYYY 22.07.2018
США MM-DD-YYYY 07-22-2018
Венгрия YYYY-MM-DD 2018-07-22
Испания DD/MM/YYYY 22/07/2018
Португалия DD-MM-YYYY 22-07-2018

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

Страна Локаль Формат валюты
Россия ru-RU 212 474,15 $
США en-US $ 212,474.15
Германия de-DE 212.474,15 $
Австрия de-AT $ 212 474,15

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

ECMAScript Internationalization API

Это специальный стандарт, который предоставляет возможности для интернационализации дат, чисел и языко-зависимого сравнения строк. Отличительной особенностью данного стандарта является то, что он разрабатывается такими компаниями, как Google, Mozilla, Microsoft, Amazon. Весь процесс интернационализации проходит через объект Intl, который дает возможность работать с такими функциями, как:

  1. Intl.DateTimeFormat — конструктор объектов, включающих языко-зависимое форматирование даты и времени;
  2. Intl.NumberFormat — конструктор объектов, включающих языко-зависимое форматирование чисел;
  3. Intl.Collator — конструктор сортировщиков-объектов, включающих языко-зависимое сравнение строк.

Для того, чтобы правильно интернационализировать контент, конструктор принимает в качестве аргумента локаль, представляющий из себя строку из 3-х (может быть и меньше) компонентов, разделенных дефисом:

  1. Код языка (‘ru’: русский);
  2. В какой стране используется язык (‘de-AT’: немецкий, используется в Австрии);
  3. Стиль (‘zh-Hans-CN’: упрощённый китайский, используемый в Китае).

Интернационализировать контент с помощью данного стандарта очень просто, достаточно взглянуть на пример:

// Работа с датами
let date = new Date();
let formatDate = new Intl.DateTimeFormat('ru');
console.log(formatDate.format(date)); // дата в формате 09.08.2018

let formatDate = new Intl.DateTimeFormat('en-US');
console.log(formatDate.format(date)); // дата в формате 8/9/2018

// Работа с числами
let formatNumber = new Intl.NumberFormat('ru');
console.log(formatNumber.format(123456789.123)); // 1 234 567 89,123

let formatNumber = new Intl.NumberFormat('en-US');
console.log(formatNumber.format(123456789.123)); // 123,456,789.123

Дополнительно вторым аргументом можно передать опции:

let number = 123456789.123;

console.log(
  new Intl.NumberFormat('ru', {
    style: 'currency', // валюта
    currency: 'RUB' // рубли
  }).format(number)
); // 123 456 789,12 ₽

console.log(
  new Intl.NumberFormat('ja-JP', {
    style: 'currency', // валюта
    currency: 'JPY' // Японские йены
  }).format(number)
); // ¥123,457

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

FormatJS

FormatJS — это модульная коллекция библиотек JavaScript для интернационализации. FormatJS умеет работать с датами, номерами, а также строками, форматируя их в зависимости от предпочитаемого языка. Данная библиотека может быть интегрирована в такие современные фреймворки и шаблонизаторы, как React, Ember, Handlebars и Dust. К неоспоримым плюсам библиотеки FormatJS относятся: возможность работы как на клиенте, так и на сервере, использование возможностей стандарта ECMAScript Internationalization API, модульность, поддержка многих фреймворков и шаблонизаторов.

REACT-INTL

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

Для начала работы с данной библиотекой необходимо поставить пакет react-intl:

npm i -S react-intl

Далее импортируем компоненты IntlProvider и FormattedMessage из библиотеки react-intl. Компонент IntlProvider будет играть своего рода обертку, в которой будет происходить интернационализация контента. Компонент FormattedMessage также является оберткой для контента, который будет динамически брать из соответствующего словаря по ключу текст и подставлять перевод.

import { IntlProvider, FormattedMessage } from 'react-intl';

Следующим шагом следует создание словарей для языков, которые будут присутствовать в приложении. Каждый из словарей будет храниться в файлах .json. В данном примере приложения содержит 3 языка: русский, английский и немецкий. Важно отметить то, что название ключей для текста в каждом из словарей должны быть одинаковыми.

Создадим папку i18n, в которую поместим словари. В этой же папке создадим файл index.js, в который необходимо импортировать Локали из библиотеки react-intl для каждого из языков, представленных в приложении:

import ruLocaleData from 'react-intl/locale-data/ru';
import enLocaleData from 'react-intl/locale-data/en';
import deLocaleData from 'react-intl/locale-data/de';

В этот же файле импортируем словари и функцию addLocaleData, которая будет принимать в качестве аргумента конкретную локаль и автоматически форматировать вывод даты и времени, согласно выбранной локализации.

Создадим объект translations, ключами которого будут словари.

export const translations = {
  ru,
  en,
  de
};

Объект translations импортируем в компонент App и передаем в качестве props компоненту IntlProvider. При изменении state.locale один из пропсов компонента IntlProvider props.message будет принимать конкретный словарь и переводить и форматировать весь контент на нужный язык.

В родительском компоненте App создадим локальный state, в котором укажем язык, который будет указан по умолчанию, в данном случае это русский язык. В дальнейшем можно создать функцию, которая будет возвращать предпочтительный язык, используемый в браузере.

state = {
  locale: 'ru'
};

После этого оборачиваем рутовый компонент App в компонент intlProvider.

<IntlProvider locale={locale} messages={translations[locale]}>
  <div className="App">
    <header className="App-header">
      <img src={logo} className="App-logo" alt="logo" />
      <h1 className="App-title">
        <FormattedMessage id="title" />
      </h1>
    </header>
    <p className="App-intro">
      <FormattedMessage id="caption" />
    </p>
  </div>
</IntlProvider>

Создадим компонент LangSelect, который представляет из себя выпадающий список с доступными языками. Для каждого из элементов списка установим дата-атрибут data-lang, который будет указывать на конкретный язык и импортируем данный компонент в App.

Для того, чтобы менять язык, создадим метод changeLanguage и прокинем его в виде props в компонент LangSelect.

<IntlProvider locale={locale} messages={translations[locale]}>
  <div className="App">
    <LangSelect changeLanguage={this.changeLanguage} />
    ...
  </div>
</IntlProvider>

Данный метод при клике на соответствующий элемент списка будет принимать data-lang, прокидывать его в родительский компонент и динамически менять state.locale компонента App, вследствии чего контент будет изменен на язык выбранного элемента списка.

export default class LangSelect extends Component {
  state = {
    dropDown: false,
    lang: 'ru'
  };
  openDropDown = () => {
    ...
  };
  changeLang = e => {
    this.setState({ lang: e.target.dataset.lang }, () =>
      this.props.changeLanguage(this.state.lang)
    );
  };
  render() {
    const { dropDown, lang } = this.state;
    return (
      <div className="content">
        <div className="wrapper">
          <div
            className="selected"
            onClick={this.openDropDown}
          >
            <span className="text">{lang}</span>
            {dropDown && (
              <ul className="dropdown">
                <li className="item" data-lang="ru" onClick={this.changeLang}>
                  ru
                </li>
                <li className="item" data-lang="en" onClick={this.changeLang}>
                  en
                </li>
                <li className="item" data-lang="de" onClick={this.changeLang}>
                  de
                </li>
              </ul>
            )}
          </div>
        </div>
      </div>
    );
  }
}

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

В заключении

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

Читать и комментировать
Поиск пути в Unity 3D

Максим Некрасов

23 мая 2016

Поиск пути в Unity 3D

Test Driven Development: Заметки на полях.

Виталий Мороз

29 сентября 2017

Test Driven Development: Заметки на полях.

Создаем расширение для Chrome

Александр Хисматулин

24 октября 2017

Создаем расширение для Chrome

Матрицы в Android, третья (заключительная) часть

Краснодар

Коммунаров, 268,
3 эт, офисы 705, 707

+7 (861) 200 27 34

Хьюстон

3523 Brinton trails Ln Katy

+1 832 993 0204

Москва

+7 (495) 145-01-05