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

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" — теперь вы должны увидеть ваше расширение, его иконку, имя и описание на выбранном языке.
  • Ещё раз протестируйте расширение. Для нашего примера, вам следовало бы посетить другую страницу и, нажав на ссылку, проверить, появляется ли сообщение на нужном языке.