Интернационализация
===================

WebExtensions API предоставляет полезный модуль для интернационализации расширений — i18n (en-US). В этой статье мы рассмотрим его особенности и пример его работы. Система для расширений, построенных с помощью WebExtension API, i18n похожа на библиотеки JavaScript для i18n, такие как i18n.js.

**Примечание:** Расширение, используемое в этой статье в качестве примера, — notify-link-clicks-i18n — доступно на GitHub. Читая последующие секции этой статьи, вы можете исследовать его исходный код.

Структура интернализированного расширения
-----------------------------------------

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

* корневая-директория-расширения/
    + \_locales
        - en
            * messages.json
                + Сообщения на английском (строки)
        - de
            * messages.json
                + Сообщения на немецком (строки)
        - и т. д.
    + manifest.json
        - Метаданные, зависящие от локализации
    + myJavascript.js
        - Файл JavaScript, получающий локализацию браузера, сообщения, зависящие от локализации, и т. д.
    + myStyles.css
        - CSS, зависящий от локализации

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

Добавление локализованных строк в \_locales
-------------------------------------------

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

Каждая система i18n требует предоставить строки во всех локализациях, которые вы хотите поддерживать. В расширениях они хранятся в директории `_locales`, размещённой внутри корневой директории. Строки каждой локализации (также называемые сообщениями) хранятся в файле `messages.json`, находящемся в поддиректории `_locales`, название которой - тег языка локализации.

Стоит заметить, что если тег включает в себя и базовый язык, и его региональный вариант, то по конвенции эти язык и вариант разделяются дефисом: например, "en-US". Однако в поддиректориях `_locales`, **вместо дефиса используется нижнее подчёркивание**: "en\_US".

Таким образом, в нашем примере существую директории "en" (английский), "de" (немецкий), "nl" (голландский), and "ja" (японский). Внутри каждой из них находится файл `messages.json` .

Давайте рассмотрим структуру одного из этих файлов (\_locales/en/messages.json):


```json
{
  "extensionName": {
    "message": "Notify link clicks i18n",
    "description": "Name of the extension."
  },

  "extensionDescription": {
    "message": "Shows a notification when the user clicks on links.",
    "description": "Description of the extension."
  },

  "notificationTitle": {
    "message": "Click notification",
    "description": "Title of the click notification."
  },

  "notificationContent": {
    "message": "You clicked $URL$.",
    "description": "Tells the user which link they clicked.",
    "placeholders": {
      "url": {
        "content": "$1",
        "example": "https://developer.mozilla.org"
      }
    }
  }
}
```


Это стандартный файл JSON — каждый из его элементов является объектом с именем, содержащим сообщение (`message)` и описание (`description)`. Оба предмета - строки; `$URL$` — это заполнитель, который заменяется подстрокой, когда элемент `notificationContent` вызывается расширением. Вы научитесь это делать в секции Получение сообщений из JavaScript.

**Примечание:** вы можете найти больше информации о структуре `messages.json` здесь (en-US).

Интернационализация manifest.json
---------------------------------

Для интернационализации файла manifest.json нужно предпринять несколько шагов.

### Получение локализованных строк в манифестах

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

Чтобы интернационализировать строки, их нужно указывать следующим образом:


```json
"name": "\_\_MSG\_extensionName\_\_",
"description": "\_\_MSG\_extensionDescription\_\_",
```


Здесь мы получаем сообщения, зависящие от локализации браузера, а не просто статические строки.

Чтобы получить строку сообщения, её нужно указать следующим образом:

1. Два подчёркивания
2. Строка "MSG"
3. Одно подчёркивание
4. Имя сообщения так как оно указано в `messages.json`
5. Два подчёркивания
```

__MSG_ + messageName + __
```

### Локализация по умолчанию

Ещё одно поле. которое нужно указать в manifest.json — это default\_locale (en-US):


```json
"default\_locale": "en"
```


Этот параметр устанавливает локализацию по умолчанию, используемую, если расширение не поддерживает локализацию браузера пользователя. Любые сообщения, недоступные в текущей локализации, будут браться из той локализации, которая установлена по умолчанию. There are some more details to be aware of in terms of how the browser selects strings — see Выбор локализованной строки.

CSS, зависящий от локализации
-----------------------------

Локализованные строки также можно получить из CSS-файлов расширения. Например, вы можете создать поля CSS, зависящие от локализации, так:


```css
header {
  background-image: url(../images/\_\_MSG\_extensionName\_\_/header.png);
}
```


Эта функциональность может быть полезна, однако, возможно, для этих целей стоит использовать Заранее определённые сообщения.

Получение сообщений из JavaScript
---------------------------------

Допустим, вы добавили сообщения в ваш manifest.json. Чтобы ваше расширение начало использовать правильные языки, соответствующие сообщения следует вызывать при помощи JavaScript. API i18n (en-US) достаточно прост и содержит всего 4 основных метода:

* Скорее всего, наиболее часто вы будете использовать `i18n.getMessage()` (en-US) — этот метод используется для получения конкретного сообщения. Примеры его использования можно увидеть ниже.
* Методы `i18n.getAcceptLanguages()` (en-US) и `i18n.getUILanguage()` (en-US) используются, если UI надо менять в зависимости от локализации — например, если вы хотите, чтобы предпочтения, свойственные носителям какого-либо языка, находились выше в списке, или чтобы формат дат соответствовал локализации браузера.
* Метод `i18n.detectLanguage()` (en-US) используется для получения языка информации, введённой пользователем, и её форматирования.

В нашем примере notify-link-clicks-i18n , фоновый скрипт содержит следующие строки:


```js
var title = browser.i18n.getMessage("notificationTitle");
var content = browser.i18n.getMessage("notificationContent", message.url);
```


Первая из них получает поле `notificationTitle message` из доступного файла `messages.json`, соответствующее наиболее подходящей локализации . Вторая строка похожа на первую, но в ней метод принимает URL в качестве второго параметра. Зачем? С помощью этого параметра мы указываем, на что нужно заменить заполнитель `$URL$` в поле `notificationContent message`:


```json
"notificationContent": {
  "message": "You clicked $URL$.",
  "description": "Tells the user which link they clicked.",
  "placeholders": {
    "url" : {
      "content" : "$1",
      "example" : "https://developer.mozilla.org"
    }
  }
}
```


Объект `"placeholders"` определяет все заполнители и то, откуда их нужно получать. Заполнитель `"url"` указывает, что информация о нем должна содержаться в $1 — первое значение, заданное внутри второго параметра `getMessage()`. Поскольку заполнитель называется `"url"`, `$URL$` используется для его вызова внутри сообщения (то есть для заполнителя `"name"` нужно использовать `$NAME$`, и т. д.). Если вы хотите задать значения нескольких заполнителей, их можно передавать во второй аргумент `i18n.getMessage()` (en-US) в виде массива — массив `[a, b, c]`передаёт значения`$1`, `$2`и`$3`, и т. д. внутрь `messages.json`.

Давайте посмотрим на пример: изначально сообщение `notificationContent` в файле `en/messages.json` такое:
```

You clicked $URL$.
```


Пусть эта ссылка указывает на `https://developer.mozilla.org`. После вызова `i18n.getMessage()` (en-US), содержание второго параметра становится доступно в messages.json в качестве значения `$1`, замещающего `$URL$`, так как это указано в заполнителе `"url"`. Таким образом, итоговое значение строки:
```

You clicked https://developer.mozilla.org.
```

### Прямое использование заполнителей

Переменные (`$1`, `$2`, `$3`, и т. д.) можно вставлять напрямую в сообщения. Например, можно переписать объект `"notificationContent"` следующим образом:


```json
"notificationContent": {
  "message": "You clicked $1.",
  "description": "Tells the user which link they clicked."
}
```


Этот метод может показаться более быстрым и простым, но другой способ (использование `"placeholders"`) считается лучшей практикой. Это вызвано тем, что имена заполнителей (например `"url"`) и примеры помогают понять, что означают заполнители — через неделю после написания кода Вы, наверное, забудете, что обозначают заполнители `$1`–`$8`, что менее вероятно, если заполнители именованы.

### Заданные замены

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


```json
"mdn\_banner": {
  "message": "For more information on web technologies, go to $MDN$.",
  "description": "Tell the user about MDN",
  "placeholders": {
    "mdn": {
      "content": "https://developer.mozilla.org/"
    }
  }
}
```


В этом примере мы сами задаём значение заполнителя, а не получаем его из переменной, такой как `$1`. Это может быть полезно, если сообщение очень сложное, и вы хотите разделить значения, чтобы сделать строки более читаемыми. К тому же, доступ к этим значениям можно получить внутри программы.

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

Выбор локализованной строки
---------------------------

Локализации могут быть указаны с помощью кода языка, например `fr` или `en`. Они также могут содержать региональный код, например `en_US` или `en_GB`, описывающий региональный вариант языка. Когда вы запрашиваете строку у системы i18n, системы возвращает её используя следующий алгоритм:

1. Если для текущей локализации существует файл `messages.json`, содержащий требуемую строку, возвращается она.
2. Иначе,если текущая локализация — региональный вариант (например `en_US`) и существует файл `messages.json` для этого языка, но без указания региона (например `en`), содержащий строку, возвращается она.
3. Иначе, если существует файл `messages.json` для `default_locale`, указанной в `manifest.json`, и этот файл содержит нужную строку, возвращается она.
4. В противном случае возвращается пустая строка.

Рассмотрим следующий пример:

* корневая-директория-расширения/
    + \_locales
        - en\_GB
            * messages.json
                + `{ "colorLocalised": { "message": "colour", "description": "Color." }, ... }`en
            * messages.json
                + `{ "colorLocalised": { "message": "color", "description": "Color." }, ... }`
        - fr
            * messages.json
                + `{ "colorLocalised": { "message": "couleur", "description": "Color." }, ...}`

Пусть `default_locale` установлен как `fr`, а текущая локализация браузера — `en_GB`:

* Вызов `getMessage("colorLocalised")` вернёт "colour".
* Если бы в `en_GB` не было "colorLocalized", то вызов `getMessage("colorLocalised")`, вернул бы "color", а не "couleur".
Заранее определённые сообщения
------------------------------

Модуль i18n module предоставляет заранее определённые сообщения, которые можно вызвать таким же образом, как мы это делали в разделе Интернационализация manifest.json. Например:
```

__MSG_extensionName__
```


Заранее определённые сообщения используют такой же синтаксис, за исключением `@@` перед именем сообщения, например:
```

__MSG_@@ui_locale__
```


Следующая таблица содержит различные заранее определённые сообщения:

| Message name | Description |
| --- | --- |
| `@@extension_id` | Внутренний UUID расширения. Эту строку можно использовать для создания URL ресурсов внутри расширения.Даже нелокализованные расширения могут использовать это сообщения.Это сообщения нельзя использовать в manifest.json.Также стоит заметить, что этот ID — *не* ID аддона, которое возвращает `runtime.id` (en-US), и которое может быть установлено с помощью ключа applications (en-US) в manifest.json. Это сгенерированный UUID, содержащийся в URL аддона. Это означает, что данную величину нельзя использовать в качестве параметра `extensionId` метода `runtime.sendMessage()` (en-US), или для проверки поля `id` объекта `runtime.MessageSender` (en-US). |
| `@@ui_locale` | Текущая локализация; эту строку можно использовать для создания URL, зависящих от локализации. |
| `@@bidi_dir` | Направления чтения, либо "ltr" для языков, таких как английский, где текст читается слева направо, либо "rtl" для языков, считающихся справа налево, таких как арабский. |
| `@@bidi_reversed_dir` | Если `@@bidi_dir` имеет значение "ltr", то возвращает "rtl"; иначе "ltr". |
| `@@bidi_start_edge` | Если `@@bidi_dir` имеет значение "ltr", то возвращает "left"; иначе "right". |
| `@@bidi_end_edge` | Если `@@bidi_dir` имеет значение "ltr", то возвращает "right"; иначе "left". |

Возвращаясь к нашему примеру, лучше было бы написать:


```css
header {
  background-image: url(../images/\_\_MSG\_@@ui\_locale\_\_/header.png);
}
```


Теперь мы можем хранить изображения в директориях поддерживаемых локализаций — en, de, и т. д. — что выглядит логичней.

Давайте рассмотрим пример использования сообщений `@@bidi_*` в файле CSS:


```css
body {
  direction: __MSG_@@bidi\_dir\_\_;
}

div#header {
  margin-bottom: 1.05em;
  overflow: hidden;
  padding-bottom: 1.5em;
  padding-__MSG_@@bidi\_start\_edge\_\_: 0;
  padding-__MSG_@@bidi\_end\_edge\_\_: 1.5em;
  position: relative;
}
```


Для языков, в которых текст читается слева направо, таких как английский, правила CSS, использующие заранее определённые сообщения, сверху задают такие значения:


```css
direction: ltr;
padding-left: 0;
padding-right: 1.5em;
```


Для языков, читающихся справа налево, значения будут следующими:


```css
direction: rtl;
padding-right: 0;
padding-left: 1.5em;
```

Тестирование расширения
-----------------------

Начиная с Firefox 45, расширения могут быть временно установлены с диска — подробнее об этом написано в статье Loading from disk. Сделайте это и попробуйте протестировать наше расширение notify-link-clicks-i18n. Перейдите на одну из ваших любимых страниц и нажмите на ссылку, чтобы проверить, появляется ли сообщения, содержащее URL нажатой ссылки.

Затем измените локализацию Firefox на какую-либо поддерживающуюся расширением, которое вы хотите протестировать.

1. Откройте "about:config" в Firefox, и найдите параметр `intl.locale.requested` (обратите внимание на версию Firefox: в версиях до Firefox 59 этот параметр называется `general.useragent.locale`).
2. Если параметр существует, нажмите на него дважды (или нажмите Return/Enter), чтобы выбрать его, введите языковой код локализации, которую вы хотите протестировать и нажмите "OK" (или Return/Enter). Например, в нашем примере расширение поддерживает "en" (английский), "de" (немецкий), "nl" (голландский), and "ja" (японский). Вы также можете указать пустую строку (`""`) в качестве значения. В этом случае браузер выберет язык вашей ОС по умолчанию.
3. Если параметр `intl.locale.requested` не существует, нажмите правой кнопкой мыши на список параметров (или откройте контекстное меню при помощи клавиатуры), и выберите "New", а затем "String". Введите `intl.locale.requested` как имя настройки и, "de", "nl", и т. д. как значение, как это описано в шаге 2.
4. Найдите `intl.locale.matchOS` и, если параметр существует и равен `true`, дважды нажмите на него и установите на `false`.
5. Перезапустите браузер, чтобы изменения вступили в силу.

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

**Примечание:** Чтобы изменить результат `getUILanguage` требуется языковой пакет, поскольку он отражает язык UI браузера, а не язык сообщений расширения.

Ещё раз загрузите расширение с диска и протестируйте локализацию:

* Ещё раз откройте "about:addons" — теперь вы должны увидеть ваше расширение, его иконку, имя и описание на выбранном языке.
* Ещё раз протестируйте расширение. Для нашего примера, вам следовало бы посетить другую страницу и, нажав на ссылку, проверить, появляется ли сообщение на нужном языке.