本地化工作流程

由于要求程序员牢记用于引用特定消息的魔法键并不切实可行,因此很明显,本地化系统应提供一种符合人体工学的方式,以符号化方式引用这些键(请记住上面的 MSG_Hello_World 抽象示例)。

遵循 18n 和 l10n 的最佳实践,源字符串位于 XML 文件(此处名为 strings.xml)中,如下所述。strings.xml 文件示例如下所示。此文件的目标是声明程序使用的所有外部化字符串,并为其提供本地唯一的 name。字符串将用作翻译的基础,name 将用作符号化 xml <!-- comment --> <?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- comment --> <string name="STRING_NAME" >text_string</string> <string name="STRING_NAME_2" >text_string_2</string> <string name="STRING_NAME_3" >string with an intervening newline</string> </resources> 的基础

文件 strings.xml 会经历一系列转换,其中译者会生成同一文件的特定语言版本。翻译流程的输入-输出行为如下:strings.xml 文件作为输入,其中包含用某种源(人类)语言编写的字符串,然后输出多种 strings.xml 变体,每种变体都采用特定单一语言进行翻译。整个翻译过程可能非常复杂,在大型组织中,可能需要将任务分派给世界各地的译者,并使用数十种专用翻译工具。但对于我们这些消费者来说,只要该过程的输入-输出行为得到保留,翻译工具的确切机制并不重要,而且我们通常都知道翻译可能需要一段时间。生成的文件会转换为机器可读的形式,并与 Fuchsia 程序一起在同一Fuchsia 软件包中提供 Fuchsia 软件包的一个重要特性是,它们本质上不是归档文件,而是通过内容哈希指向文件的清单。因此,多个程序可以共享相同的文件,并且密切相关的语言(例如“en-US”“en-GB”)可能会共享消息磁盘空间。下图简要概述了字符串的生命周期。

上图显示了本地化流程。由于 XML 文件带有注释,因此不直接适合机器翻译,因此我们将其转换为 JSON 文件,以便我们重复使用可用的库来加载这些文件,并构建一个从键到消息字符串的映射。然后,这些字符串可用作 `MessageFormat` 中的格式字符串。

strings.xml

我们将重复使用 Android 字符串资源 XML 格式来表示可本地化的字符串。由于我们不会向 strings.xml 格式添加任何内容,因此有关这些功能的完整讨论将交由“字符串资源”页面进行。

虽然上图中的所有 XML 代码看起来像是刚刚从某个直接连接到 1990 年代的虫洞中冒出来,但 XML 实际上非常适合用于描述带注释的文本。strings.xml 是一种经过 Android 长期考验的格式,因此我们知道它足以胜任,而且开发者也熟悉它。

例如,可以通过将注解交错插入源文本来声明字符串资源。

<!-- … -->
<string name="title"
   >Best practices for <annotation font="title_emphasis">text</annotation> look like so</string>
<!-- … -->

上图:翻译文本和注释的交错示例。_

您可以交错应免于翻译的文本,如图所示

<string name="countdown">
  <xliff:g id="time" example="5 days"
    >{1}</xliff:g> until holiday</string>

上图:带有示例值的注解且由字符串资源数据架构中不存在的标记保护的已围定参数的交错示例。_

我们还可以根据需要定义数据架构中的自定义内容,并透明地将该数据架构插入现有架构中。

上述文件的内容有一些必要的约束条件:

  • 文件中的每个 name 属性都必须是唯一的。
  • 名称标识符可以包含大写和小写 ASCII 字母、数字和下划线,但不能以数字开头。例如,允许使用 _H_e_L_L_o_wo_1_rld,但不允许使用 0cool
  • 文件中不得有任何两个 name-message 组合重复。

目前,系统不支持在一个项目中包含多个字符串文件。

消息标识符

系统会根据 strings.xml 文件的内容生成消息标识符(每个消息的“神奇”数字常量)。每条字符串消息都会获得一个唯一标识符,该标识符是根据 name 上的单向哈希和消息本身的内容计算得出的。这种标识符分配可确保两个不同的消息不太可能意外地具有相同的生成标识符。

这些消息的生成由 Fuchsia 中的 GN 构建规则自动完成,但最终由名为 strings_to_fidl 的程序执行。此程序会为消息 ID 生成 FIDL 中间表示法,常规 FIDL 工具链则用于生成该信息的特定于语言的版本。例如,C++ 变种将是一个包含以下内容的头文件:

namespace fuchsia {
namespace intl {

namespace l10n {
enum class MessageIds : uint64_t {
  STRING_NAME = 42u,
  STRING_NAME_2 = 43u,
  STRING_NAME_3 = 44u,
};

}  // namespace l10n
}  // namespace intl
}  // namespace fuchsia

上面示例中为每个特定枚举值分配的确切值不相关。生成方法目前也不相关,因为所有标识符都是在编译时生成的,没有版本偏差的机会。目前,我们可以放心地假设,完全相同的名称-内容组合将始终分配相同的消息 ID。

将生成的文件包含到 C++ 程序中非常简单。下面给出了一个最小示例,但如需了解详细的连接信息,请参阅完整示例。库参数 fuchsia.intl.l10n 由作者直接作为标志提供给 strings_to_fidl;如果使用适当的 GN 模板,则作为 GN 模板的参数提供给 strings_to_fidl

#include <iostream>

// This header file has been generated from the strings library fuchsia.intl.l10n.
#include "fuchsia/intl/l10n/cpp/fidl.h"

// Each library name segment between dots gets its own nested namespace in
// the generated C++ code.
using fuchsia::intl::l10n::MessageIds;

int main() {
  std::cout << "Constant: " << static_cast<uint64_t>(MessageIds::STRING_NAME) << std::endl;
  return 0;
}

*.json

通过 FIDL 和 C++ 代码生成,程序作者可以使用消息 ID。在打包方面,我们还必须为我们支持的每种语言提供本地化资源。目前,此类信息的编码为 JSON。之所以这样做,是为了加快速度,但我们可以对此做出一些改进,以提升性能和安全性。

生成此类信息的任务会委托给名为 strings_to_json 的程序,该程序会将原始 strings.xml 与特定于语言的文件(例如,法语译文位于 strings_fr.xml 中)合并。再次强调一下,对于由 GN 驱动的 build,strings_to_json 的调用会封装在 build 规则中。

下面列出了生成的 JSON 文件的内容示例。

{
  "locale_id": "fr",
  "source_locale_id": "en-US",
  "num_messages": 3,
  "messages": {
    "42": "le string",
    "43": "le string 2",
    "44": "le string\nwith intervening newline"
  }
}

JSON 格式目前定义了以下字段。如果下表已过时,JSON 结构的可信来源是字符串模型

字段 类型 说明
locale_id 语言区域 ID(字符串) 消息要翻译到的语言区域。
source_locale_id 语言区域 ID(字符串) 源消息文件的语言区域。
num_messages 正整数 原始 strings.xml 中的消息数量。这样,我们就可以通过比较该消息数量与 JSON 文件中的消息数量,快速估算翻译质量。
messages 地图:[u64->string] 消息 ID 与相应消息之间的映射。