遗留格式迁移¶
从遗留格式(.dtd、.properties)迁移不同于 Fluent 到 Fluent 的迁移。在迁移遗留代码路径时,您需要根据 Mozilla 在遗留代码路径中使用的特殊情况调整 Fluent 字符串。您将在此处找到许多专门的功能。
遗留迁移工具¶
为了帮助进行遗留格式迁移,提供了一些脚本工具
创建迁移时,其中一个或两个工具可以通过自动化迁移的至少一部分(包括方案生成和重构调用代码)为手动工作提供良好的起点。
基本迁移¶
让我们考虑一个基本的示例:一个字符串需要从 DTD 文件迁移到 Fluent,无需任何其他更改。
遗留字符串存储在 toolkit/locales/en-US/chrome/global/findbar.dtd
中
<!ENTITY next.tooltip "Find the next occurrence of the phrase">
新的 Fluent 字符串存储在 toolkit/locales/en-US/toolkit/main-window/findbar.ftl
中
findbar-next =
.tooltiptext = Find the next occurrence of the phrase
迁移方案如下所示
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
from __future__ import absolute_import
import fluent.syntax.ast as FTL
from fluent.migrate.helpers import transforms_from
def migrate(ctx):
"""Bug 1411707 - Migrate the findbar XBL binding to a Custom Element, part {index}."""
ctx.add_transforms(
"toolkit/toolkit/main-window/findbar.ftl",
"toolkit/toolkit/main-window/findbar.ftl",
transforms_from(
"""
findbar-next =
.tooltiptext = { COPY(from_path, "next.tooltip") }
""", from_path="toolkit/chrome/global/findbar.dtd"))
首先要注意的是,迁移方案需要相对于本地化存储库的文件路径,丢失 locales/en-US/
toolkit/locales/en-US/chrome/global/findbar.dtd
变成toolkit/chrome/global/findbar.dtd
。
toolkit/locales/en-US/toolkit/main-window/findbar.ftl
变成toolkit/toolkit/main-window/findbar.ftl
。
context.add_transforms
函数接受 3 个参数
目标 l10n 文件的路径。
参考(en-US)文件的路径。
转换数组。转换是 AST 节点,描述了如何迁移遗留翻译。
注意
对于 Firefox 本地化的迁移,目标路径和参考路径相同。这对于所有使用 Fluent 的项目并不适用,因此需要这两个参数。
在本例中,只有一个转换将 ID 为 next.tooltip
的字符串从 toolkit/chrome/global/findbar.dtd
迁移,并将其注入 FTL 片段中。 COPY
转换允许按原样从现有文件复制字符串,而 from_path
用于避免多次重复相同的路径,使方案更具可读性。如果没有 from_path
,则可以写成
ctx.add_transforms(
"toolkit/toolkit/main-window/findbar.ftl",
"toolkit/toolkit/main-window/findbar.ftl",
transforms_from(
"""
findbar-next =
.tooltiptext = { COPY("toolkit/chrome/global/findbar.dtd", "next.tooltip") }
"""))
这种编写迁移方案的方法允许获取原始 FTL 字符串,并简单地用 COPY
转换替换每个消息的值。 transforms_from
负责将 FTL 语法转换为描述如何迁移遗留翻译的转换数组。这种定义迁移的方式仅适用于复制操作足够简单的简单字符串。对于需要在 Python 中使用一些额外逻辑的更复杂用例,有必要使用原始 AST。
上面的示例等效于以下语法,它公开了底层的 AST 结构
ctx.add_transforms(
"toolkit/toolkit/main-window/findbar.ftl",
"toolkit/toolkit/main-window/findbar.ftl",
[
FTL.Message(
id=FTL.Identifier("findbar-next"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("tooltiptext"),
value=COPY(
"toolkit/chrome/global/findbar.dtd",
"next.tooltip"
)
)
]
)
]
)
这会创建一个 Message
,从遗留字符串 findbar-next
中获取值。消息可以有一个属性数组,每个属性都有一个 ID 和一个值:在本例中,只有一个属性,ID 为 tooltiptext
, value
从遗留字符串复制。
请注意,消息的 ID 和属性的 ID 如何都定义为 FTL.Identifier
,而不仅仅是字符串。
提示
可以使用 +
运算符将手动定义的转换数组(如最后一个示例中)与来自 transforms_from
的转换数组连接起来。或者,可以使用多个 add_transforms
。
方案中提供的转换顺序无关紧要,参考文件用于对消息进行排序。
替换遗留字符串中的内容¶
虽然 COPY
允许按原样复制遗留字符串,但 REPLACE
(来自 fluent.migrate)允许在执行迁移时替换内容。例如,当迁移包含需要替换以适应 Fluent 语法的占位符或实体的字符串时,这很有必要。
例如,考虑以下字符串
<!ENTITY aboutSupport.featuresTitle "&brandShortName; Features">
需要迁移到
features-title = { -brand-short-name } Features
实体 &brandShortName;
需要替换为术语引用
FTL.Message(
id=FTL.Identifier("features-title"),
value=REPLACE(
"toolkit/chrome/global/aboutSupport.dtd",
"aboutSupport.featuresTitle",
{
"&brandShortName;": TERM_REFERENCE("brand-short-name"),
},
)
),
这会创建一个 FTL.Message
,从遗留字符串 aboutSupport.featuresTitle
中获取值,但用 Fluent 术语引用替换指定的文本。
注意
REPLACE
替换指定文本的所有出现。
也可以用特定文本替换内容:在这种情况下,需要将其定义为 TextElement
。例如,要将 example.com
替换为 HTML 标记
value=REPLACE(
"browser/chrome/browser/preferences/preferences.properties",
"searchResults.sorryMessageWin",
{
"example.com": FTL.TextElement('<span data-l10n-name="example"></span>')
}
)
当迁移方案需要替换 printf
参数(如 %S
)时,情况会更加复杂。实际上,本地化字符串和源字符串使用的格式不需要匹配,以下两个使用无序和有序参数的字符串完全等效
btn-quit = Quit %S
btn-quit = Quit %1$S
在这种情况下,替换 %S
将在第一个版本上起作用,但在第二个版本上不起作用,并且不能保证本地化字符串使用与源字符串相同的格式。
还要考虑以下使用 %S
表示两个不同变量的字符串,隐式依赖于参数出现的顺序
updateFullName = %S (%S)
以及目标 Fluent 字符串
update-full-name = { $name } ({ $buildID })
如上所述, REPLACE
将替换 %S
的所有出现,因此只能设置一个变量。字符串需要被规范化并像这样处理
updateFullName = %1$S (%2$S)
这可以通过调用 REPLACE
并设置 normalize_printf=True
来实现
FTL.Message(
id=FTL.Identifier("update-full-name"),
value=REPLACE(
"toolkit/chrome/mozapps/update/updates.properties",
"updateFullName",
{
"%1$S": VARIABLE_REFERENCE("name"),
"%2$S": VARIABLE_REFERENCE("buildID"),
},
normalize_printf=True
)
)
注意
为了避免任何问题,在替换 printf
参数时,应始终使用 normalize_printf=True
。这是处理 .properties 文件时的默认行为。
注意
VARIABLE_REFERENCE
、 MESSAGE_REFERENCE
和 TERM_REFERENCE
是可以在常见情况下使用的辅助转换,在这些情况下,使用原始 AST 太冗长。
VARIABLE_REFERENCE
用于创建对变量的引用,例如 { $variable }
。
MESSAGE_REFERENCE
用于创建对另一个消息的引用,例如 { another-string }
。
TERM_REFERENCE
用于创建对 术语 的引用,例如 { -brand-short-name }
。
这两个转换都需要在方案开头导入,例如 from fluent.migrate.helpers import VARIABLE_REFERENCE
修剪翻译中不必要的空格¶
注意
本节已于 2020 年 5 月更新,以反映对默认行为的更改:现在会修剪遗留翻译,除非显式设置 trim
参数。
在遗留翻译中,字符串包含不必要的前导或尾随空格的情况并不少见。这些没有意义,对字符串在产品中显示方式没有实际影响,并且主要出于格式原因而添加。例如,考虑以下 DTD 字符串
<!ENTITY aboutAbout.note "This is a list of “about” pages for your convenience.<br/>
Some of them might be confusing. Some are for diagnostic purposes only.<br/>
And some are omitted because they require query strings.">
默认情况下, COPY
、 REPLACE
和 PLURALS
转换将从翻译的每一行的开头和结尾去除空格,以及开头和结尾的空行。尽管原始字符串的第二行和第三行有大量的缩进,但上述字符串将迁移为以下 Fluent 消息:
about-about-note =
This is a list of “about” pages for your convenience.<br/>
Some of them might be confusing. Some are for diagnostic purposes only.<br/>
And some are omitted because they require query strings.
要禁用默认的修剪行为,请根据上下文设置 trim:"False"
或 trim=False
。
transforms_from(
"""
about-about-note = { COPY("toolkit/chrome/global/aboutAbout.dtd", "aboutAbout.note", trim:"False") }
""")
FTL.Message(
id=FTL.Identifier("discover-description"),
value=REPLACE(
"toolkit/chrome/mozapps/extensions/extensions.dtd",
"discover.description2",
{
"&brandShortName;": TERM_REFERENCE("-brand-short-name")
},
trim=False
)
),
字符串拼接¶
最佳实践是仅将完整的短语暴露给本地化,并避免在代码中将本地化的字符串拼接在一起。使用 DTD 和 properties 时,选项很少。因此,在迁移到 Fluent 时,您会发现拼接来自 DTD 和 properties 的多个字符串的情况非常普遍,例如创建带有 HTML 标记的句子。可以使用 CONCAT
变换在迁移方案中拼接字符串和文本元素。
请注意,在使用 transforms_from
进行简单迁移的情况下,拼接是通过使用与 Fluent 语法交织的 COPY()
变换调用来定义迁移方案隐式执行的。
考虑以下示例
# %S is replaced by a link, using searchResults.needHelpSupportLink as text
searchResults.needHelp = Need help? Visit %S
# %S is replaced by "Firefox"
searchResults.needHelpSupportLink = %S Support
在 Fluent 中
search-results-need-help-support-link = Need help? Visit <a data-l10n-name="url">{ -brand-short-name } Support</a>
这是一个相当复杂的迁移:它需要获取 2 个旧字符串,并将它们的值与 HTML 标记拼接起来。以下是变换的定义方式
FTL.Message(
id=FTL.Identifier("search-results-help-link"),
value=REPLACE(
"browser/chrome/browser/preferences/preferences.properties",
"searchResults.needHelp",
{
"%S": CONCAT(
FTL.TextElement('<a data-l10n-name="url">'),
REPLACE(
"browser/chrome/browser/preferences/preferences.properties",
"searchResults.needHelpSupportLink",
{
"%1$S": TERM_REFERENCE("brand-short-name"),
},
normalize_printf=True
),
FTL.TextElement("</a>")
)
}
)
),
%S
在 searchResults.needHelpSupportLink
中被替换为对术语 -brand-short-name
的引用,从 %S Support
迁移到 { -brand-short-name } Support
。然后将此操作的结果插入两个文本元素之间以创建锚标记。最终使用生成的文本替换 %S
在 searchResults.needHelp
中,并用作 FTL 消息的值。
重要
拼接现有字符串时,避免对原始文本进行更改,例如添加空格或标点符号。每种语言都有自己的规则,这可能会导致迁移的字符串质量低下。如有疑问,请务必征求反馈。
当传入多个元素进行拼接时,CONCAT
会禁用上述部分中描述的所有传入的旧变换(COPY
、REPLACE
和 PLURALS
)的空格修剪,除非在这些变换上显式设置了 trim
参数。这有助于确保在拼接过程中不会丢失段落周围的空格。
但是,当仅将单个元素传入 CONCAT
时,修剪行为不会改变,并遵循上一节中描述的规则。这样做的目的是使 CONCAT(COPY())
等效于裸 COPY()
。
复数字符串¶
从 .properties 文件迁移复数字符串通常涉及来自 fluent.migrate.transforms
的两个变换:REPLACE_IN_TEXT
变换将 TextElements 作为输入,使其可以作为 PLURALS
变换的 foreach 函数传递。
考虑以下旧字符串
# LOCALIZATION NOTE (disableContainersOkButton): Semi-colon list of plural forms.
# See: https://mdn.org.cn/en/docs/Localization_and_Plurals
# #1 is the number of container tabs
disableContainersOkButton = Close #1 Container Tab;Close #1 Container Tabs
在 Fluent 中
containers-disable-alert-ok-button =
{ $tabCount ->
[one] Close { $tabCount } Container Tab
*[other] Close { $tabCount } Container Tabs
}
以下是此字符串变换的定义方式
FTL.Message(
id=FTL.Identifier("containers-disable-alert-ok-button"),
value=PLURALS(
"browser/chrome/browser/preferences/preferences.properties",
"disableContainersOkButton",
VARIABLE_REFERENCE("tabCount"),
lambda text: REPLACE_IN_TEXT(
text,
{
"#1": VARIABLE_REFERENCE("tabCount")
}
)
)
)
PLURALS 变换将负责为每种语言创建正确的复数类别数量。请注意,如何使用 REPLACE_IN_TEXT
和 VARIABLE_REFERENCE("tabCount")
将每个变体的 #1 替换为 { $tabCount }
。
在这种情况下,无法使用 REPLACE
,因为它将文件路径和消息 ID 作为参数,而此处方案需要对常规文本进行操作。替换是在原始字符串的每个复数形式上执行的,其中复数形式以分号分隔。
显式变体¶
显式创建字符串的变体对于平台相关的术语很有用,但也适用于您希望将字符串进行一对多拆分的情况。始终可以通过手动创建底层 AST 结构来迁移字符串。考虑以下复杂的 Fluent 字符串
use-current-pages =
.label =
{ $tabCount ->
[1] Use Current Page
*[other] Use Current Pages
}
.accesskey = C
此字符串的迁移非常复杂:label
属性由 2 个不同的旧字符串创建,它不是正确的复数形式。请注意,第一个字符串与 1
案例关联,而不是复数形式中使用的 one
类别。由于这些原因,无法使用 PLURALS
,需要制作变换以重新创建 AST。
FTL.Message(
id=FTL.Identifier("use-current-pages"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("label"),
value=FTL.Pattern(
elements=[
FTL.Placeable(
expression=FTL.SelectExpression(
selector=VARIABLE_REFERENCE("tabCount"),
variants=[
FTL.Variant(
key=FTL.NumberLiteral("1"),
default=False,
value=COPY(
"browser/chrome/browser/preferences/main.dtd",
"useCurrentPage.label",
)
),
FTL.Variant(
key=FTL.Identifier("other"),
default=True,
value=COPY(
"browser/chrome/browser/preferences/main.dtd",
"useMultiple.label",
)
)
]
)
)
]
)
),
FTL.Attribute(
id=FTL.Identifier("accesskey"),
value=COPY(
"browser/chrome/browser/preferences/main.dtd",
"useCurrentPage.accesskey",
)
),
],
),
此变换使用了本文档中已经描述的一些概念。值得注意的是 SelectExpression
在 Placeable
内,带有 Variant
对象数组。这些变体中只有一个需要具有 default=True
。
此示例仍然可以使用 transforms_from()`()
,因为现有字符串在没有插值的情况下被复制。
transforms_from(
"""
use-current-pages =
.label =
{ $tabCount ->
[1] { COPY(main_dtd, "useCurrentPage.label") }
*[other] { COPY(main_dtd, "useMultiple.label") }
}
.accesskey = { COPY(main_dtd, "useCurrentPage.accesskey") }
""", main_dtd="browser/chrome/browser/preferences/main.dtd"
)