本地化工作流程

由于要求程序员从心里知道一个指代特定消息的魔法键不切实际,因此,本地化系统应该提供一种工效学方法,以符号方式引用这些键(请记住上述 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 软件包的一项重要功能是它们本质上不是归档,而是按内容哈希指向文件的清单。因此,多个程序可以共享相同的文件,而密切相关的语言(“en-US”“en-GB”)可能会共享消息磁盘空间。下图显示了字符串生命周期的简明概览。

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

strings.xml

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

虽然上图中的所有 XML 让这种讨论看起来像是刚从 20 世纪 90 年代的一个问题中脱颖而出,但实际上 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>

上图:一个 fenced-off 参数的交错示例,它带有一个示例值进行注解,并使用一个不属于字符串资源数据架构的标记进行保护。

如果需要,我们还可以定义我们自己要添加的数据架构,并在现有架构中透明地交错该数据架构。

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

  • 文件中的每个 name 属性都必须是唯一的。
  • 名称标识符可以包含大小写字母、数字和下划线,但不能以数字开头。例如,允许使用 _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 模板的参数。

#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 到相应消息的映射。