WebExtensions API 有一个相当方便的模块可用于附加组件的国际化 — i18n。在本文中,我们将探讨其功能,并提供一个它如何运作的实例。WebExtensions 的 i18n 系统类似于常见的 i18n 用的 JavaScript 库,例如 i18n.js

本文中的 WebExtension 实例 — notify-link-clicks-i18n 可在 GitHub 上查阅。在您阅读下列章节时,可参照它的代码。

解构一个国际化的 WebExtension

一个国际化的 WebExtension 与其他 WebExtension 一样,可以包含各类功能,如后台脚本内容脚本等,但它也有些额外的部分,从而允许它在不同的语言区域之间切换。目录树大致如下:

  • webextension-root-directory/
    • _locales
      • en
        • messages.json
          • 英语消息(字符串)
      • de
        • messages.json
          • 德语消息(字符串)
      • 等其他语言
    • manifest.json
      • 依赖语言区域的元数据
    • myJavascript.js
      • JavaScript,用于检索浏览器语言区域、特定语言环境的消息等。
    • myStyles.css
      • 依赖语言区域的 CSS

让我们逐项探讨这些新特性,因为下列每个章节都是你在国际化 WebExtension 时所要遵循的步骤。

在 _locales 中提供本地化的字符串

你可以使用Language subtag lookup page上的查找工具查询语言子标签。请注意,你需要搜索语言的英语名称。

所有 i18n 系统都要求提供需翻译为你想支持的不同语言区域的字符串。 在 WebExtensions 里,这些字符串都被包含在一个名为 _locales 的目录下,_locales 位于扩展的根目录。每个语言区域都带有一个名叫 messages.json 的文件,其中包含相应的字符串(在 WebExtension 里叫做消息),这个文件放在 _locales 的子目录下,子目录是用对应语言区域的语言子标签来命名的。

注意如果子标签包含一个基本的语言和一个区域变量,那么语言和变量之间通常会用连字号隔开,例如 "en-US"。但作为 _locales 的子目录,它必须使用下划线来隔开,例如 "en_US"。

所以例如我们这个示例应用,我们有如下几个目录:"en"(英语)、"de"(德语)、"nl"(荷兰语)以及 "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$ 是一个占位符,在 WebExtension 调用 notificationContent 成员时将被一个子字符串替换。你将在接下来的从 JavaScript 检索消息字符串章节中了解如何操作。

注意:你可以在 Locale-Specific Message reference 里找到  messages.json 文件中更多关于内容的信息。

国际化 manifest.json

要国际化你的 manifest.json 有几项任务要完成。

在 manifests 中检索本地化的字符串

你的 manifest.json 包含显示给用户的字符串,例如附加组件的名称和描述。如果您将这些字符串国际化,并将其合适的翻译放到 messages.json 中,则根据当前语言区域用户会看到正确的字符串翻译。

要将字符串国际化,请按如下格式指定:

"name": "__MSG_extensionName__",
"description": "__MSG_extensionDescription__",

在这里,我们检索的消息字符串依赖于浏览器的语言区域,而不仅仅是包含静态字符串。

要调用这样的消息字符串,你需要这样指定:

  1. 两个下划线,接着是
  2. 字符串 "MSG",接着是
  3. 一个下划线,接着是
  4. 你想调用的在 messages.json 中定义的消息名称,接着是
  5. 两个下划线
__MSG_ + messageName + __

指定默认语言区域

您应该在你的 manifest.json 中指定的另一个字段是 default_locale

"default_locale": "en"

如果扩展不包含浏览器当前语言区域的本地化字符串,则可通过该字段指定要使用的默认语言区域。浏览器语言区域中不可用的任何消息字符串都将用默认语言区域替换。有关浏览器如何选择字符串,还有一些细节需要注意 — 详见选择本地化的字符串

依赖语言区域的 CSS

请注意你还可以在扩展的 CSS 文件中检索本地化的字符串。例如,你可能想构建一个依赖于语言区域的 CSS 规则,如下所示:

header {
  background-image: url(../images/__MSG_extensionName__/header.png);
}

这很有用,不过在这种情况下使用预定义消息来处理或许会更好。

从 JavaScript 检索消息字符串

看来你已经建立起您的消息字符串和 manifest。现在你只需要开始从 JavaScript 调用这些消息字符串,以便你的扩展尽可能多地和正确的语言进行交互。实际的 i18n API 相当简单,只需包含以下四个主要的方法(method):

  • 很可能你最常使用的是 i18n.getMessage() — 您可以使用此方法检索一个指定的语言字符串,如上所述。下面我会看到特定的用法示例。
  • i18n.getAcceptLanguages()i18n.getUILanguage() 这两个方法可以在你需要根据语言区域自定义用户界面时使用 — 或许你希望根据用户想要的语言在首选项列表更高层显示首选项,或只显示和特定语言有关的文化信息,又或是按浏览器语言显示格式化过的日期。
  • i18n.detectLanguage() 这个方法可以用来检测用户提交内容的语言,并将其正确格式化。

在我们的 notify-link-clicks-i18n 示例中,后台脚本包含下列代码:

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

第一行是从最适合当前语言区域的可用 messages.json 文件中检索 notificationTitle message 字段。第二行与第一行类似,但它是被传递了一个 URL 作为第二个参数。怎么一回事?它就是你指定内容替代 notificationContent message 字段里的 $URL$ 占位符的方式:

"notificationContent": {
  "message": "您点击了 $URL$。",
  "description": "告诉用户点击了哪个链接。",
  "placeholders": {
    "url" : {
      "content" : "$1",
      "example" : "https://developer.mozilla.org"
    }
  }
}

"placeholders" 这个成员定义了所有的占位符,以及他们所检索的来源。"url" 这个占位符指定了其内容取自 $1,它就是 getMessage() 第二个参数里的第一个值。由于占位符就叫做 "url",我们就在实际的消息字符串中用 $URL$ 调用它("name" $NAME$ 调用也是一样的,以此类推)。对于多个占位符,你可以将其置于数组内,并作为第二个参数传递到 i18n.getMessage()[a, b, c] 可替换为 $1, $2, and $3,以此类推,并置于 messages.json 内。

接下来我们看一个例子:在 en/messages.json 文件中原始的 notificationContent 消息字符串如下: in the en/messages.json file is

您点击了 $URL$。

我们可以看到链接点击后会打开 https://developer.mozilla.org。i18n.getMessage() 调用后,第二个参数的内容就变成了 messages.json 里的 $1,并替换定义在 "url" 占位符里的 $URL$ 这个占位符。所以最后的消息字符串就变成了:

您点击了 https://developer.mozilla.org。

直接占位符的使用

你可以直接将变量($1, $2, $3 等)插入消息字符串,例如我们可以将上述的 "notificationContent" 成员重写为:

"notificationContent": {
  "message": "你点击了 $1。",
  "description": "告诉用户点击了哪个链接。"
}

这么做看起来似乎更快捷,也没那么复杂,但另一种方法(使用 "placeholders")更切实际,因为提供一个占位符名(例如 "url")和 example 可帮你记住占位符是什么 — 在你代码写了一个礼拜后,你很可能忘了 $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 这样的变量值中获取它。有时候你会遇到消息文件非常复杂,或者如果你想将文件里的不同值分离开来以便字符串可读性更好,这种情况下它会很有用,这些值可通过编程来访问。

此外,你可使用这样的替代方式指定不想被翻译的一部分字符串,例如人名或公司名。

选择本地化的字符串

语言区域可以仅使用语言代码指定,例如 fren,也可以进一步限定区域代码,例如 en_USen_GB,其描述了使用相同基础语言的区域变体。当您向 i18n 系统询问一个字符串时,它将使用以下算法选择一个字符串:

  1. 如果有精确匹配当前语言区域的 messages.json 文件,并且它包含该字符串,则返回它。
  2. 否则,如果当前语言区域有合格区域(例如 en_US)并且有一个无区域限定的 messages.json 文件(例如 en)且包含该字符串,则返回它。
  3. 否则,如果 manifest.json 里包含 default_locale 所对应的 messages.json文件且包含该字符串,则返回它。
  4. 否则,返回一个空字符串。

参见下列示例:

  • webextension-root-directory/
    • _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"。
  • 如果 "colorLocalised" 没有在 en_GB 中提供,那么 getMessage("colorLocalised") 将返回 "color" 而不是 "couleur".

预定义消息

i18n 模块为我们提供了一些预定义的消息,我们可以用之前在 Calling message strings from manifests and extension CSS 中看到的相同的方式调用。例如:

__MSG_extensionName__

预定义的消息使用完全相同的语法,例如在消息名称之前使用 @@

__MSG_@@ui_locale__

下表显示了各个可用的预定义消息。

消息名 描述
@@extension_id 扩展或应用的 ID;你可以使用该字符串构建 URL,用于扩展内部的资源。甚至未本地化的扩展也可以使用该消息。
注意:该消息无法在 manifest 文件中使用。
@@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 等 — 这样显得更有意义多了。

让我们看看一个在 CSS 文件中使用 @@bidi_* 消息的例子:

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;

测试你的 WebExtension

从 Firefox 45 开始,你可以临时安装磁盘上的 WebExtensions  — 另见从磁盘加载。按上述步骤操作,然后尝试我们的 notify-link-clicks-i18n WebExtension。访问你喜欢的任何网站,然后点一下链接,查看是否有通知出现来显示所点击的链接网址。

接下来,将 Firefox 的语言区域更改为你想测试的扩展支持的某个语言区域。

  1. 在 Firefox 中打开 "about:config",找到 general.useragent.locale 首选项。
  2. 双击该首选项(或按回车)以选择它,输入你想测试的语言环境的语言代码,然后点击“确定”(或按回车)。我们的示例扩展支持“en”(英语)、“de”德语()、“nl”(荷兰语)和“ja”日语。
  3. 重启你的浏览器以完成更改。

Note: 这个方法可以用来修改浏览器的语言区域,即使你未安装过该语言区域对应的语言包 。这样的话你就可以将浏览器用户界面调整显示为你的默认语言。

再次从磁盘临时加载该扩展,然后测试你的新语言区域:

  • 再次访问 "about:addons" — 你现在应该看到该附加组件已列出其图标,以及相应语言的名称和描述。
  • 再次测试你的 WebExtension。在我们的例子中,你会转到另一个网站并点击一个链接,以查看该通知现在是否以相应语言显示。

文档标签和贡献者

 此页面的贡献者: wlq105556, yfdyh000
 最后编辑者: wlq105556,