Интернационализация
API WebExtensions предоставляет полезный модуль для интернационализации расширений — i18n. В этой статье мы рассмотрим его особенности и пример его работы. Система для расширений, построенных с помощью API WebExtension, i18n похожа на библиотеки JavaScript для i18n, такие как i18n.js.
Расширение, используемое в этой статье в качестве примера, — notify-link-clicks-i18n — доступно на GitHub. Читая последующие секции этой статьи, вы можете исследовать его исходный код.
Структура интернализированного расширения
Интернационализированное расширение может содержать такие же элементы, как и любое другое расширение — фоновые скрипты, встраиваемые скрипты, и т. д. — а также дополнительные инструменты, позволяющие переключаться между разными локализациями. Их можно представить следующим деревом директорий:
- корневая-директория-расширения/
- _locales
- en
- messages.json
- Сообщения на английском (строки)
- messages.json
- de
- messages.json
- Сообщения на немецком (строки)
- messages.json
- и т. д.
- en
- manifest.json
- Метаданные, зависящие от локализации
- myJavascript.js
- Файл JavaScript, получающий локализацию браузера, сообщения, зависящие от локализации, и т. д.
- myStyles.css
- CSS, зависящий от локализации
- _locales
Давайте отдельно рассмотрим каждый элемент — последующие секции представляют собой шаги, которым стоит следовать во время интернационализации вашего расширения.
Добавление локализованных строк в _locales
Каждая система i18n требует предоставить строки во всех локализациях, которые вы хотите поддерживать. В расширениях они хранятся в директории _locales
, размещённой внутри корневой директории. Строки каждой локализации (также называемые сообщениями) хранятся в файле messages.json
, находящемся в поддиректории _locales
, название которой - тег языка локализации.
Стоит заметить, что если тег включает в себя и базовый язык, и его региональный вариант, то по конвенции эти язык и вариант разделяются дефисом: например, "en-US". Однако в поддиректориях _locales
, вместо дефиса используется нижнее подчёркивание: "en_US".
Таким образом, в нашем примере существую директории "en" (английский), "de" (немецкий), "nl" (голландский), and "ja" (японский). Внутри каждой из них находится файл messages.json
.
Давайте рассмотрим структуру одного из этих файлов (_locales/en/messages.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
здесь.
Интернационализация manifest.json
Для интернационализации файла manifest.json нужно предпринять несколько шагов.
Получение локализованных строк в манифестах
Ваш файл manifest.json содержит строки, отображаемые пользователю, такие как имя и описание расширения. Если вы интернационализируете эти строки и поместите их переводы в messages.json, то эти переводы будут отображаться пользователю в зависимости от локализации его браузера.
Чтобы интернационализировать строки, их нужно указывать следующим образом:
"name": "__MSG_extensionName__",
"description": "__MSG_extensionDescription__",
Здесь мы получаем сообщения, зависящие от локализации браузера, а не просто статические строки.
Чтобы получить строку сообщения, её нужно указать следующим образом:
- Два подчёркивания
- Строка "MSG"
- Одно подчёркивание
- Имя сообщения так как оно указано в
messages.json
- Два подчёркивания
__MSG_ + messageName + __
Локализация по умолчанию
Ещё одно поле. которое нужно указать в manifest.json — это default_locale:
"default_locale": "en"
Этот параметр устанавливает локализацию по умолчанию, используемую, если расширение не поддерживает локализацию браузера пользователя. Любые сообщения, недоступные в текущей локализации, будут браться из той локализации, которая установлена по умолчанию. There are some more details to be aware of in terms of how the browser selects strings — see Выбор локализованной строки.
CSS, зависящий от локализации
Локализованные строки также можно получить из CSS-файлов расширения. Например, вы можете создать поля CSS, зависящие от локализации, так:
header {
background-image: url(../images/__MSG_extensionName__/header.png);
}
Эта функциональность может быть полезна, однако, возможно, для этих целей стоит использовать Заранее определённые сообщения.
Получение сообщений из JavaScript
Допустим, вы добавили сообщения в ваш manifest.json. Чтобы ваше расширение начало использовать правильные языки, соответствующие сообщения следует вызывать при помощи JavaScript. API i18n достаточно прост и содержит всего 4 основных метода:
- Скорее всего, наиболее часто вы будете использовать i18n.getMessage() (en-US) — этот метод используется для получения конкретного сообщения. Примеры его использования можно увидеть ниже.
- Методы i18n.getAcceptLanguages() (en-US) и i18n.getUILanguage() (en-US) используются, если UI надо менять в зависимости от локализации — например, если вы хотите, чтобы предпочтения, свойственные носителям какого-либо языка, находились выше в списке, или чтобы формат дат соответствовал локализации браузера.
- Метод i18n.detectLanguage() (en-US) используется для получения языка информации, введённой пользователем, и её форматирования.
В нашем примере notify-link-clicks-i18n , фоновый скрипт содержит следующие строки:
var title = browser.i18n.getMessage("notificationTitle");
var content = browser.i18n.getMessage("notificationContent", message.url);
Первая из них получает поле notificationTitle message
из доступного файла messages.json
, соответствующее наиболее подходящей локализации . Вторая строка похожа на первую, но в ней метод принимает URL в качестве второго параметра. Зачем? С помощью этого параметра мы указываем, на что нужно заменить заполнитель $URL$
в поле notificationContent message
:
"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"
следующим образом:
"notificationContent": {
"message": "You clicked $1.",
"description": "Tells the user which link they clicked."
}
Этот метод может показаться более быстрым и простым, но другой способ (использование "placeholders"
) считается лучшей практикой. Это вызвано тем, что имена заполнителей (например "url"
) и примеры помогают понять, что означают заполнители — через неделю после написания кода Вы, наверное, забудете, что обозначают заполнители $1
–$8
, что менее вероятно, если заполнители именованы.
Заданные замены
Значения заполнителей можно задавать вручную, если вы хотите, чтобы каждый раз это значение было одним и тем же, а не определялось переменной в коде. Например:
"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, системы возвращает её используя следующий алгоритм:
- Если для текущей локализации существует файл
messages.json
, содержащий требуемую строку, возвращается она. - Иначе,если текущая локализация — региональный вариант (например
en_US
) и существует файлmessages.json
для этого языка, но без указания региона (напримерen
), содержащий строку, возвращается она. - Иначе, если существует файл
messages.json
дляdefault_locale
, указанной вmanifest.json
, и этот файл содержит нужную строку, возвращается она. - В противном случае возвращается пустая строка.
Рассмотрим следующий пример:
- корневая-директория-расширения/
- _locales
- en_GB
- messages.json
{ "colorLocalised": { "message": "colour", "description": "Color." }, ... }
- messages.json
{ "colorLocalised": { "message": "color", "description": "Color." }, ... }
- messages.json
- fr
- messages.json
{ "colorLocalised": { "message": "couleur", "description": "Color." }, ...}
- messages.json
- en_GB
- _locales
Пусть 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 в manifest.json. Это сгенерированный UUID, содержащийся в URL аддона. Это означает, что данную величину нельзя использовать в качестве параметра |
@@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". |
Возвращаясь к нашему примеру, лучше было бы написать:
header {
background-image: url(../images/__MSG_@@ui_locale__/header.png);
}
Теперь мы можем хранить изображения в директориях поддерживаемых локализаций — en, de, и т. д. — что выглядит логичней.
Давайте рассмотрим пример использования сообщений @@bidi_*
в файле 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, использующие заранее определённые сообщения, сверху задают такие значения:
direction: ltr;
padding-left: 0;
padding-right: 1.5em;
Для языков, читающихся справа налево, значения будут следующими:
direction: rtl;
padding-right: 0;
padding-left: 1.5em;
Тестирование расширения
Начиная с Firefox 45, расширения могут быть временно установлены с диска — подробнее об этом написано в статье Loading from disk. Сделайте это и попробуйте протестировать наше расширение notify-link-clicks-i18n. Перейдите на одну из ваших любимых страниц и нажмите на ссылку, чтобы проверить, появляется ли сообщения, содержащее URL нажатой ссылки.
Затем измените локализацию Firefox на какую-либо поддерживающуюся расширением, которое вы хотите протестировать.
- Откройте "about:config" в Firefox, и найдите параметр
intl.locale.requested
(обратите внимание на версию Firefox: в версиях до Firefox 59 этот параметр называетсяgeneral.useragent.locale
). - Если параметр существует, нажмите на него дважды (или нажмите Return/Enter), чтобы выбрать его, введите языковой код локализации, которую вы хотите протестировать и нажмите "OK" (или Return/Enter). Например, в нашем примере расширение поддерживает "en" (английский), "de" (немецкий), "nl" (голландский), and "ja" (японский). Вы также можете указать пустую строку (
""
) в качестве значения. В этом случае браузер выберет язык вашей ОС по умолчанию. - Если параметр
intl.locale.requested
не существует, нажмите правой кнопкой мыши на список параметров (или откройте контекстное меню при помощи клавиатуры), и выберите "New", а затем "String". Введитеintl.locale.requested
как имя настройки и, "de", "nl", и т. д. как значение, как это описано в шаге 2. - Найдите
intl.locale.matchOS
и, если параметр существует и равенtrue
, дважды нажмите на него и установите наfalse
. - Перезапустите браузер, чтобы изменения вступили в силу.
Примечание: Этот метод работает, даже если у вас не установлен языковой пакет для выбранного языка. В этом случае UI браузера просто будет использовать ваш язык по умолчанию.
Примечание: Чтобы изменить результат getUILanguage
требуется языковой пакет, поскольку он отражает язык UI браузера, а не язык сообщений расширения.
Ещё раз загрузите расширение с диска и протестируйте локализацию:
- Ещё раз откройте "about:addons" — теперь вы должны увидеть ваше расширение, его иконку, имя и описание на выбранном языке.
- Ещё раз протестируйте расширение. Для нашего примера, вам следовало бы посетить другую страницу и, нажав на ссылку, проверить, появляется ли сообщение на нужном языке.