国际化

WebExtensions API 有一个非常方便的模块可用于扩展国际化——i18n。本文将探讨其功能并提供一个实际的工作示例。

注意:本文中展示的示例扩展——notify-link-clicks-i18n——可在 GitHub 上获取。在阅读以下部分时,请参阅源代码。

国际化扩展的组成

国际化扩展可以包含与任何其他扩展相同的功能——背景脚本内容脚本等——但它也有一些额外的部分,允许它在不同的语言环境之间切换。这些总结在以下目录树中:

  • 扩展根目录/
    • _locales

      • en

        • messages.json
          • 英语消息(字符串)
      • de

        • messages.json
          • 德语消息(字符串)
      • etc.

    • 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() — 这是您用于检索特定语言字符串的方法,如上所述。我们将在下面看到它的具体使用示例。
  • i18n.getAcceptLanguages()i18n.getUILanguage() 方法可以在您需要根据语言环境自定义 UI 时使用——也许您想在偏好设置列表中显示特定于用户首选语言的偏好设置,或者显示仅与某种语言相关的文化信息,或者根据浏览器语言环境适当地格式化显示的日期。
  • 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,这是作为 getMessage() 的第二个参数给出的第一个值。由于占位符名为 "url",我们在实际消息字符串中使用 $URL$ 来调用它(因此对于 "name",您将使用 $NAME$,等等)。如果您有多个占位符,您可以将它们作为第二个参数传递给 i18n.getMessage() 的数组中——[a, b, c] 将在 messages.json 中作为 $1$2$3 等可用。

让我们来看一个例子: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-USzh-Hans-CN)来指定。当您的扩展向 i18n 系统请求字符串时,它使用以下算法选择字符串:

  1. 如果用户的浏览器语言环境设置的 messages.json 文件中包含该字符串,则返回该字符串。例如,如果用户已将其浏览器设置为 en-US 并且扩展提供了 _locales/en_US/messages.json 文件。
  2. 否则,如果浏览器语言环境带有脚本或区域(例如,en-USzh-Hans-CN),并且存在无区域版本以及脚本版本(如果前者不可用)的 messages.json 文件,并且该文件包含该字符串,则返回该字符串。例如,如果用户已将其浏览器设置为 zh-Hans-CN(并且没有 _locales/zh_Hans_CN/messages.json 文件),则 i18n 系统会在 zh-Hans 中查找字符串,如果该字符串不可用,则在 zh 中查找。
  3. 否则,如果 manifest.json 中定义的 default_locale 存在 messages.json 文件,并且它包含该字符串,则返回该字符串。
  4. 否则返回一个空字符串。

以这个例子为例

  • 扩展根目录/
    • _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",因为 _locales/en_GB/messages.json 包含 colorLocalized 消息。
  • 如果浏览器语言环境是 en-US,当扩展调用 getMessage("colorLocalized") 时,它将返回 "color",因为它会回退到 _locales/en/messages.json 中存在的消息。
  • 如果浏览器语言环境是 zh-Hans-CN,当扩展调用 getMessage("colorLocalized") 时,它将返回 "couleur",因为没有与 zh-Hans-CN 语言环境匹配的语言、脚本或区域。

预定义消息

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

__MSG_extensionName__

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

__MSG_@@ui_locale__

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

消息名称 描述
@@extension_id

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

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

另请注意,此 ID **不是** runtime.id 返回的附加组件 ID,并且可以使用 manifest.json 中的 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 的通知的翻译版本。