国际化

The WebExtensions API has a rather handy module available for internationalizing extensions — i18n. In this article we'll explore its features and provide a practical example of how it works. The i18n system for extensions built using WebExtension APIs is similar to common JavaScript libraries for i18n such as i18n.js.

注意:本文中介绍的示例扩展——notify-link-clicks-i18n——在 GitHub 上提供。在您浏览以下部分时,请遵循源代码。

国际化扩展的剖析

一个国际化扩展可以包含与任何其他扩展相同的特性——后台脚本内容脚本等——但它还有一些额外的部分,以允许它在不同的区域设置之间切换。这些部分在以下目录树中概述

  • extension-root-directory/
    • _locales
      • en
        • messages.json
          • 英文消息(字符串)
      • de
        • messages.json
          • 德语消息(字符串)
      • 等等
    • manifest.json
      • 与区域设置相关的元数据
    • myJavascript.js
      • 用于检索浏览器区域设置、与区域设置相关的消息等的 JavaScript 代码
    • myStyles.css
      • 与区域设置相关的 CSS 代码

让我们依次探索每个新特性——以下每个部分都代表着国际化扩展时要遵循的一个步骤。

在 _locales 中提供本地化字符串

注意:您可以使用 语言子标签查找页面 上的“查找”工具查找语言子标签。请注意,您需要搜索语言的英文名称。

每个 i18n 系统都需要提供翻译成您要支持的所有不同区域设置的字符串。在扩展中,这些字符串包含在名为 _locales 的目录中,该目录位于扩展根目录内。每个单独的区域设置都将其字符串(称为消息)包含在一个名为 messages.json 的文件中,该文件位于 _locales 的子目录中,并使用该区域设置语言的语言子标签命名。

请注意,如果子标签包括基本语言加上区域变体,则语言和变体通常使用连字符分隔:例如,“en-US”。但是,在 _locales 下的目录中,分隔符必须是下划线:“en_US”。

所以 例如,在我们的示例应用程序中,我们有“en”(英语)、“de”(德语)、“nl”(荷兰语)和“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://mdn.org.cn"
      }
    }
  }
}

此文件是标准 JSON——其每个成员都是一个带有名称的对象,该对象包含一个 message 和一个 description。所有这些项都是字符串;$URL$ 是一个占位符,在扩展调用 notificationContent 成员时,它将被子字符串替换。您将在从 JavaScript 中检索消息字符串部分了解如何执行此操作。

注意:您可以在我们的与区域设置相关的消息参考中找到有关 messages.json 文件内容的更多信息。

国际化 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

json
"default_locale": "en"

这指定了一个默认区域设置,如果扩展不包含与浏览器当前区域设置相对应的本地化字符串,则使用该默认区域设置。浏览器当前区域设置中不可用的任何消息字符串将从默认区域设置中获取。在浏览器选择字符串的方式方面,还有一些需要注意的细节——请参阅本地化字符串选择

与区域设置相关的 CSS

请注意,您也可以从扩展中的 CSS 文件中检索本地化字符串。例如,您可能希望构建一个依赖于区域设置的 CSS 规则,如下所示

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

这很有用,但您最好使用预定义消息来处理这种情况。

从 JavaScript 中检索消息字符串

因此,您已经设置了消息字符串和清单。现在您只需要从 JavaScript 中开始调用消息字符串,以便您的扩展尽可能地使用正确的语言。实际的i18n API非常简单,只包含四个主要方法

  • 您可能最常使用i18n.getMessage()——这是您用于检索特定语言字符串的方法,如上所述。我们将在下面看到此方法的具体使用示例。
  • 如果您需要根据区域设置自定义 UI,则可以使用i18n.getAcceptLanguages()i18n.getUILanguage()方法——例如,您可能希望在首选项列表中将与用户首选语言相关的首选项显示得更高,或者显示仅与特定语言相关的文化信息,或者根据浏览器区域设置适当地格式化显示的日期。
  • 可以使用i18n.detectLanguage()方法检测用户提交内容的语言,并适当地格式化。

在我们notify-link-clicks-i18n示例中,后台脚本包含以下几行代码

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

第一行代码只是从可用的、最适合浏览器当前语言环境的messages.json文件中检索notificationTitle message字段。第二行代码类似,但它将 URL 作为第二个参数传递。这是怎么回事呢?这正是你指定内容来替换我们在notificationContent message字段中看到的$URL$占位符的方法。

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

"placeholders"成员定义了所有占位符及其来源。"url"占位符指定其内容取自$1$1是在getMessage()的第二个参数中给出的第一个值。由于占位符名为"url",我们在实际消息字符串中使用$URL$来调用它(因此对于"name",你将使用$NAME$,等等)。如果你有多个占位符,你可以将它们放在一个数组中,并将其作为第二个参数传递给i18n.getMessage()[a, b, c]将分别作为$1$2$3messages.json中使用。

让我们看一个例子:en/messages.json文件中原始的notificationContent消息字符串为

You clicked $URL$.

假设单击的链接指向https://mdn.org.cn。在调用i18n.getMessage()后,第二个参数的内容将在 messages.json 中作为$1提供,并替换"url"占位符中定义的$URL$占位符。因此最终的消息字符串为

You clicked https://mdn.org.cn.

直接使用占位符

可以将你的变量($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://mdn.org.cn/"
    }
  }
}

在这种情况下,我们只是将占位符内容硬编码,而不是从像$1这样的变量值获取它。当你的消息文件非常复杂时,这有时会很有用,并且你希望将不同的值拆分开来,以使文件中的字符串更易读,此外,这些值还可以通过编程方式访问。

此外,你可以使用这种替换来指定你不希望翻译的字符串部分,例如人名或公司名称。

本地化字符串选择

语言环境可以使用语言代码来指定,例如fren,或者可以使用区域代码进一步限定,例如en_USen_GB,这描述了相同基本语言的区域变体。当你请求 i18n 系统提供一个字符串时,它将使用以下算法选择一个字符串。

  1. 如果存在当前语言环境的messages.json文件,并且它包含该字符串,则返回它。
  2. 否则,如果当前语言环境使用区域代码限定(例如en_US),并且存在该语言环境的无区域版本的messages.json文件(例如en),并且该文件包含该字符串,则返回它。
  3. 否则,如果存在manifest.json中定义的default_localemessages.json文件,并且它包含该字符串,则返回它。
  4. 否则返回一个空字符串。

让我们看一个例子

  • extension-root-directory/
    • _locales
      • en_GB
        • messages.json
          • { "colorLocalized": { "message": "colour", "description": "Color." }, /* … */ }
        en
        • messages.json
          • { "colorLocalized": { "message": "color", "description": "Color." }, /* … */ }
      • fr
        • messages.json
          • { "colorLocalized": { "message": "couleur", "description": "Color." }, /* … */}

假设default_locale设置为fr,浏览器的当前语言环境为en_GB

  • 如果扩展调用getMessage("colorLocalized"),它将返回 "colour"。
  • 如果en_GB中不存在 "colorLocalized",则getMessage("colorLocalized")将返回 "color",而不是 "couleur"。

预定义消息

i18n 模块为我们提供了一些预定义的消息,我们可以像之前在在清单中检索本地化字符串与语言环境相关的 CSS中看到的那样调用它们。例如

__MSG_extensionName__

预定义的消息使用完全相同的语法,只是在消息名前面加了@@,例如

__MSG_@@ui_locale__

下表显示了不同的可用预定义消息。

消息名称 描述
@@extension_id

扩展的内部生成的 UUID。你可以使用此字符串来构造扩展内部资源的 URL。即使未本地化的扩展也可以使用此消息。

你不能在清单文件中使用此消息。

还要注意,此 ID 不是runtime.id返回的附加组件 ID,并且可以使用清单文件中的browser_specific_settings键设置。它是附加组件 URL 中出现的生成 UUID。这意味着你不能将此值用作runtime.sendMessage()extensionId参数,也不能使用它来检查runtime.MessageSender对象的id属性。

@@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 等)相匹配的目录中,这更有意义。

让我们看一个在 CSS 文件中使用@@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 声明将转换为以下最终代码行

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

对于阿拉伯语等从右到左的语言,你会得到

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

测试扩展

要测试你的扩展的本地化,可以使用FirefoxFirefox Beta,这些 Firefox 版本允许你安装语言包。

然后,对于要测试的扩展支持的每个语言环境,请按照使用 Firefox 的另一种语言中的说明切换 Firefox UI 语言。(如果你熟悉设置,可以在“语言”下使用“设置备选”。)

一旦 Firefox 以你的测试语言运行,临时安装扩展。安装扩展后,在about:debugging中,如果你正确设置了扩展,你会看到扩展以其图标、名称和描述列出,并以你选择的语言显示。你也可以在about:addons中看到本地化的扩展详细信息。现在,使用扩展的功能来确保你需要的翻译到位。

如果你想尝试这个过程,可以使用notify-link-clicks-i18n扩展。将 Firefox 设置为显示此示例中支持的语言之一(德语、荷兰语或日语)。加载扩展并访问一个网站。单击一个链接以查看报告链接 URL 的通知的翻译版本。