Fluent 1.0:一个用于自然流畅翻译的本地化系统

Fluent 是 Mozilla 开发的一系列本地化规范、实现和最佳实践。它目前用于 Firefox。使用 Fluent,翻译人员可以创建表达生动的翻译,使其在目标语言中听起来很棒。今天,我们宣布 Fluent 文件格式规范的 1.0 版本。我们邀请翻译工具作者尝试它并提供反馈。

Fluent 解决的问题

Firefox 支持近 100 种语言,面临着许多本地化挑战。使用传统的本地化解决方案,这些挑战难以克服。软件本地化一直受制于一个过时的范式:一對一的源语言映射翻译。源语言的语法(Mozilla 使用的是英语)限制了翻译的表达力。

考虑以下消息,该消息在用户尝试关闭具有多个标签的窗口时出现在 Firefox 中。

tabs-close-warning-multiple =
    You are about to close {$count} tabs.
    Are you sure you want to continue?

该消息仅在标签计数为 2 或更多时显示。在英语中,单词“tab”始终以复数形式“tabs”出现。说英语的开发者可能对这条消息感到满意。它听起来对 $count 的所有可能值都很好。

在英语中,一条消息的单一变体足以用于 $count 的所有值。

然而,许多翻译人员会很快指出,单词“tab”将根据 $count 变量的确切值采用不同的形式。

在传统的本地化解决方案中,修复此消息的责任在于开发人员。他们需要考虑到其他语言区分不止一种复数形式,即使英语不区分。随着应用程序支持的语言数量的增加,这个问题会迅速扩大,而且不会很好地解决。

  • 在某些语言中,名词有性别,需要不同形式的形容词和过去分词。在法语中,“connecté”、“connectée”、“connectés”和“connectées”都表示“connected”。
  • 风格指南可能要求根据软件运行的平台使用不同的术语。在英语版 Firefox 中,我们在 Windows 上使用“Settings”,在其他系统上使用“Preferences”,以匹配用户的操作系统术语。在日语中,差异更为明显:一些与计算机相关的术语根据用户的操作系统使用不同的书写系统拼写。
  • 应用程序的上下文和目标受众可能需要调整文案。在英语中,用于会计的软件可能对数字的格式与社交媒体网站不同。然而,在其他语言中,这种区别可能不必要。

语言之间存在许多语法和风格差异,无法一一映射。使用传统的本地化解决方案来支持所有这些差异并非易事。一些语言特性需要权衡才能支持它们,或者根本无法支持。

非对称本地化

Fluent 将本地化格局颠倒了过来。Fluent 不需要开发人员预测所有支持语言中所有可能的复杂性排列,而是尽可能保持源语言的简单性。

我们使根据其他语言的语法和风格定制成为可能,而与源语言无关。所有这一切都是独立进行的;一种语言从更高级的逻辑中受益,并不需要任何其他本地化来应用它。每个本地化都可以控制翻译变得多么复杂。

考虑上面讨论的“标签关闭”消息的捷克语翻译。单词“panel”(标签)必须采用两种复数形式之一:“panely”用于 2、3 和 4 的计数,“panelů”用于所有其他数字。

tabs-close-warning-multiple = {$count ->
    [few] Chystáte se zavřít {$count} panely. Opravdu chcete pokračovat?
   *[other] Chystáte se zavřít {$count} panelů. Opravdu chcete pokračovat?
}

Fluent 使翻译人员能够创建语法正确的翻译,并利用其语言的表达能力。使用 Fluent,捷克语翻译现在可以为 $count 变量的所有可能值受益于正确的复数形式。

在捷克语中,$count 值为 2、3 和 4 需要名词的特殊复数形式。

同时,源代码或源文案无需进行任何更改。事实上,捷克语翻译人员添加到捷克语翻译中的逻辑不会影响任何其他语言。法语中的同一条消息是一个简单的句子,类似于英语中的句子。

tabs-close-warning-multiple =
    Vous êtes sur le point de fermer {$count} onglets.
    Voulez-vous vraiment continuer ?

“非对称本地化”的概念是 Fluent 的关键创新,它建立在 Mozilla 20 年成功发布本地化软件的历史之上。Fluent 中的许多关键思想也受到 XLIFF 和 ICU 的 MessageFormat 的启发。

乍一看,Fluent 看起来与其他允许翻译使用复数和语法性别的本地化解决方案类似。Fluent 的与众不同之处在于它对本地化的整体方法。Fluent 通过定义存储多个翻译的整个文本文件的语法,以及允许消息引用其他消息,将这些理念推向了更远。

术语和引用

一个 Fluent 文件可能包含许多消息,每条消息都翻译成翻译人员的语言。消息可以引用同一文件中的其他消息,甚至可以引用其他文件中的消息。在运行时,Fluent 将文件组合成“捆绑包”,引用在当前捆绑包的范围内解决。

引用消息是确保一致性的强大工具。一旦定义,翻译可以在其他翻译中重复使用。Fluent 甚至有一种特殊类型的消息,称为“术语”,它最适合重复使用。术语标识符始终以连字符开头。

-sync-brand-name = Firefox Account

一旦定义,-sync-brand-name 术语就可以从其他消息中引用,它将始终解析为相同的值。术语有助于执行风格指南;它们也可以交换进出,以在非官方版本和测试版发行渠道中修改品牌。

sync-dialog-title = {-sync-brand-name}
sync-headline-title =
    {-sync-brand-name}: The best way to bring
    your data always with you
sync-signedout-account-title =
    Connect with your {-sync-brand-name}

在句子的中间直接使用术语可能会给有词形变化的语言或与英语不同的大小写规则的语言带来麻烦。术语可以定义其值的多个方面,适用于不同的上下文。考虑以下在意大利语中定义的 -sync-brand-name 术语。

-sync-brand-name = {$capitalization ->
   *[uppercase] Account Firefox
    [lowercase] account Firefox
}

由于 Fluent 的非对称性,意大利语翻译人员可以自由地定义品牌名称的两个方面。默认的方面(大写)适用于独立出现,以及用在句子的开头。当品牌名称用在较大的句子中时,可以通过传递大小写参数来显式请求小写版本。

sync-dialog-title = {-sync-brand-name}
sync-headline-title =
    {-sync-brand-name}: il modo migliore
    per avere i tuoi dati sempre con te

# Explicitly request the lowercase variant of the brand name.
sync-signedout-account-title =
    Connetti il tuo {-sync-brand-name(capitalization: "lowercase")}

定义多个术语变体是一种通用的技术,它允许本地化满足许多语言的语法需求。在以下示例中,波兰语翻译可以使用词形变化在 sync-signedout-account-title 消息中构建一个语法正确的句子。

-sync-brand-name = {$case ->
   *[nominative] Konto Firefox
    [genitive] Konta Firefox
    [accusative] Kontem Firefox
}

sync-signedout-account-title =
    Zaloguj do {-sync-brand-name(case: "genitive")}

Fluent 使在必要时表达语言复杂性成为可能。同时,简单的翻译仍然简单。Fluent 不会强加复杂性,除非需要创建正确的翻译。

sync-signedout-caption = Take Your Web With You
sync-signedout-caption = Il tuo Web, sempre con te
sync-signedout-caption = Zabierz swoją sieć ze sobą
sync-signedout-caption = So haben Sie das Web überall dabei.

Fluent 语法

今天,我们宣布 Fluent 语法的第一个稳定版本。它是一个存储翻译的文件格式的正式规范,并附带 JavaScript、Python 和 Rust 中解析器实现的测试版。

您已经在上面的示例中看到了 Fluent 语法的示例。它的设计考虑了非技术人员,并旨在使审查和编辑翻译的任务变得容易和防错。错误恢复是重点:单个损坏的翻译不可能破坏整个文件,甚至不能破坏相邻的翻译。可以使用注释来传达有关消息或消息组目的的上下文信息。翻译可以跨越多行,这在处理较长的文本或标记时很有帮助。

Fluent 文件可以在任何文本编辑器中打开和编辑,从而降低了开发人员和本地化人员的入门门槛。该文件格式也 得到 Pontoon(Mozilla 的开源翻译管理系统)的良好支持。

Fluent Playground 是一个在线沙箱,用于在浏览器中实时尝试 Fluent。

您可以通过阅读 Fluent 语法指南 来了解更多关于语法的知识。正式定义可以在 Fluent 语法规范 中找到。如果您只想快速了解其工作原理,请尝试使用 Fluent Playground,它是一个带有可共享 Fluent 代码片段的在线编辑器。

反馈请求

到目前为止,Firefox 一直是 Fluent 开发的主要推动力。如今,Firefox 中有超过 3000 条 Fluent 消息。从传统本地化格式迁移的进程始于去年年初,现在正在如火如荼地进行。事实证明,Fluent 是构建复杂界面的稳定而灵活的解决方案,例如 Firefox 偏好设置的 UI。它也用于许多 Mozilla 网站,例如 Firefox SendCommon Voice

我们认为 Fluent 是重视简洁和精简运行时,同时要求界面元素依赖多个变量的应用程序的绝佳选择。尤其是,Fluent 可以帮助在移动应用程序的大小受限的 UI 中创建自然流畅的翻译;在社交媒体平台的信息丰富的布局中;以及在游戏中,将游戏玩法统计数据和机制传达给玩家。

我们非常希望听到来自 Mozilla 以外的项目和本地化供应商的反馈。因为我们正在开发 Fluent 以期成为未来的标准,我们邀请您尝试一下并告诉我们它是否解决了您的挑战。在您的帮助下,我们可以迭代和改进 Fluent,以满足许多平台、用例和行业的需要。

我们欢迎您提出建设性的反馈。在 项目的网站 上了解更多关于 Fluent 的信息,并在 Fluent 的 Discourse 上与我们联系。

关于 Staś Małolepszy

作为 Mozilla 的本地化工程师,我创建解决方案来帮助 Firefox 在全球范围内可用并符合当地文化。

Staś Małolepszy 的更多文章…


5 条评论

  1. Bruno

    我在一个项目中遇到了一个让我想到这个问题的情况。我想知道 Fluent 是否以及如何处理以下变量问题。

    以句子“The Toronto data center is down.”为例,将城市名称作为包含多个数据中心的系统中的变量,源字符串将类似于“The {-data-center-city} data center is down.”。

    在法语中,限定词会根据后面单词的首字母发生变化。因此,上面的例子将是“Le centre de données de Toronto est hors ligne.”,但如果更改城市,则可能是“Le centre de données d’Atlanta est hors ligne.”(de vs. d’)。

    是否有任何方法可以使翻译考虑到这一点,而无需为每个城市创建单独的字符串?

    2019 年 4 月 26 日 上午 7:22

    1. Staś Małolepszy

      感谢您分享这个用例。我们一直在思考 Fluent 的类似用途。看到人们对解决相同问题的兴趣真是太好了。

      我们一直在讨论两种解决方法。其中一种将在 Fluent 的未来版本中添加。另一种现在就可以使用。

      第一种方法基于名为动态引用的功能。它仍然需要为每个城市创建独立的字符串,但至少主消息(“The {-data-center-city} data center is down”)可以安全地重复使用。有关动态引用的更多信息,请参阅 https://github.com/projectfluent/fluent/issues/80

      另一种解决方案,在 Fluent 1.0 中可用,是基于自定义函数。这是一个强大而高级的功能,我需要改进有关它的文档。您可以在 https://projectfluent.org/fluent/guide/functions.html 上阅读有关函数的信息。Fluent 的部署可以添加本地化人员可用的自定义函数。在您的用例中,您可以添加一个 FIRST_LETTER() 函数,本地化人员可以在需要时使用它。然后,法语翻译可以如下所示

      data-center-offline = Le centre de données {FIRST_LETTER($cityName) ->
      [vowel] d'{$cityName}
      *[consonant] de {$cityName}
      } est hors ligne.

      这种方法需要开发人员进行一些工作:他们需要编写自定义函数才能将其公开给本地化。好消息是,这只需要在代码中一次进行,并且只需要在一个地方进行:在初始化 Fluent 运行时的地方。因为它们是代码,所以可以像代码库中的任何其他代码一样审查和测试自定义函数,以帮助确保它们能够实现它们声称的功能:)

      重要的是,自定义函数的使用是完全可选的。可以根据需要逐步添加它们。所有本地化调用点保持不变,所有现有翻译都保持功能完好。

      2019 年 4 月 26 日 上午 10:12

      1. Bruno

        有趣!但是,我看到第二种方法的一个问题是字母 H 和 Y。举个英语的例子,使用这种函数很难区分“an honor”和“a history”,而当涉及到专有名词时,构建一个详尽的列表是不切实际的。

        我对动态引用与 Fluent 中已显示的功能有何区别感到困惑,但我认为唯一万无一失的解决方案是为每个术语提供各种形式,例如:
        – Toronto
        — de Toronto
        — à Toronto
        — Toronto
        — 等等。

        – Alberta
        — d’Alberta
        — en Alberta
        — l’Alberta
        — 等等。

        – États-Unis
        — des États-Unis
        — aux États-Unis
        — les États-Unis
        — 等等。

        2019 年 4 月 27 日 上午 11:00

        1. Staś Małolepszy

          你说得对:自定义函数方法有限。它不能完全解决这个用例。仅仅检查字符代码是不够的。它可能会在某些语言和某些单词上产生错误的翻译。

          也许将来,我们会看到 Unicode 提供关于何时使用省略或何时“h”是送气的分类数据,类似于它已经在 https://www.unicode.org/cldr/charts/latest/supplemental/index.html 中定义了许多其他功能。另一方面,可能会有太多例外情况,导致这种方法永远无法可靠地运行 100%。

          您关于动态引用方法将如何工作的说法也是正确的。让我用英语和法语的例子来说明。在英语中

          -city-Toronto = Toronto
          -city-Atlanta = Atlanta
          data-center=offline = The {$city} data center is down

          语法和 API 尚未最终确定,但我们目前的思路是:在源代码中,开发人员将 $city 定义为 FluentReference(“-city-Toronto”) 或 FluentReference(“-city-Atlanta”)。引用将在运行时动态解析,无论 $city 变量在何处使用。

          然后,法语翻译可以根据法语语法需要任意地“分解”术语定义。我意识到法语没有语法格,因此实际翻译可能会以其他方式实现,但这里是一个简要说明它可能是什么样子的例子

          -city-Toronto = {$cas ->
          *[nominatif] Toronto
          [génitif] de Toronto
          }
          -city-Atlanta ={$cas ->
          *[nominatif] Atlanta
          [génitif] d’Atlanta
          }
          data-center=offline = Le centre de données {$city(cas: “génitif”)} est hors ligne.

          这种方法要求所有城市名称在预先知道,并且所有必需的变格都必须在术语内定义。这是一种蛮力解决方案。它可能适用于有限的数据中心位置列表,或适用于一个国家/地区中 100 个最大的城市。但它不能很好地扩展到涵盖世界上的所有城市。

          让我们进一步讨论!请随时在我们的 Discourse 上发布 https://discourse.mozilla.org/c/fluent。在那里使用代码示例会更容易:)感谢您的评论!

          2019 年 4 月 30 日 上午 8:08

          1. Bruno

            恐怕我没什么可说的了(我太忙了,都忘了回来查看了),但是感谢您详细的解答。很难在利用模糊匹配和引用之间找到平衡,以及通过永远不重复使用字符串来确保准确性。如果语言不是那么“自然”,它们将更容易编程:)

            2019 年 5 月 6 日 下午 7:48

本文的评论已关闭。