Go 国际化和语言标识符
马塞尔・范・罗威岑 (Marcel van Lohuizen)
2016 年 2 月 9 日
介绍#
考虑一个应用程序,例如网站,在其用户界面中支持多种语言。当一个用户抵达时,有一列表的首选语言,应用程序必须决定在向用户展示时应使用哪种语言。这要求在应用程序支持的语言和用户喜欢的语言之间找到最佳匹配。这篇文章解释了为什么这是一个艰难的决定,以及 Go 如何提供帮助。
语言标签#
语言标签,也称为语言环境标识符,是所使用语言和 / 或方言的机器可读标识符。最常见的参考是 IETF BCP 47 标准,这是 Go 库遵循的标准。以下是 BCP 47 语言标签及其代表的语言或方言的一些示例。
标签 | 描述 |
---|---|
en | English |
en-US | American English |
cmn | Mandarin Chinese |
zh | Chinese, typically Mandarin |
nl | Dutch |
nl-BE | Flemish |
es-419 | Latin American Spanish |
az, az-Latn | both Azerbaijani written in Latin script |
az-Arab | Azerbaijani written in Arabic |
语言标签的一般形式是语言代码(上面的 “en”,“ cmn”,“ zh”,“ nl”,“ az”),然后是脚本的可选子标签(“-Arab”),区域(“ -US”,“-BE”,“-419”),变体(用于牛津英语词典拼写的 “ -oxendict”)和扩展名(用于电话簿分类的 “ -u-co-phonebk”)。如果省略子标签,则采用最常见的形式,例如 “ az” 表示 “ az-Latn-AZ”。
语言标签最常见的用法是根据用户的语言偏好列表从一组系统支持的语言中进行选择,例如,确定偏爱南非荷兰语的用户将得到最好的服务 (假设南非荷兰语不可用),如果系统显示荷兰语。解决这样的匹配需要查阅有关相互语言可理解性的数据。
匹配产生的标签随后用于获取特定于语言的资源,例如翻译,排序顺序和大小写算法。这涉及另一种匹配。例如,由于没有针对葡萄牙语的特定排序顺序,因此整理程序包可能会退回到默认语言或 “根” 语言的排序顺序。
匹配语言的混乱性质#
处理语言标签非常棘手。部分原因是人类语言的边界没有很好地定义,部分原因是语言标签标准不断发展。在本节中,我们将展示处理语言标签的一些混乱方面.
具有不同语言代码的标签可以表示相同的语言
由于历史和政治原因,许多语言代码已随着时间而改变,从而使语言具有较旧的旧代码和新的旧代码。但是,即使当前的两个代码也可能引用相同的语言。例如,普通话的官方语言代码是 "cmn", 但是 "zh" 是迄今为止该语言最常用的代号。正式为所谓的宏语言保留了代码 "zh", 以标识汉语组。宏语言的标记通常与组中最常用的语言互换使用.
仅匹配语言代码是不够的
例如,阿塞拜疆语 ("az") 会根据使用语言的国家 / 地区使用不同的文字书写: "az-Latn" 代表拉丁语 (默认文字), "az-Arab" 代表阿拉伯语,"az-Cyrl" 代表西里尔文。如果仅将 "az-Arab" 替换为 "az", 则结果将以拉丁文字显示,对于仅知道阿拉伯语形式的用户而言可能无法理解.
同样,不同的区域可能意味着不同的脚本。例如: "zh-TW" 和 "zh-SG" 分别表示使用繁体汉字和简体汉字。再举一个例子,"sr" (塞尔维亚语) 默认为西里尔字母,但是 "sr-RU" (俄语中的塞尔维亚语) 暗含拉丁字母!吉尔吉斯语和其他语言也可以说类似的话.
如果忽略子标签,则最好向用户显示希腊语.
最匹配的语言可能是用户未列出的语言
挪威语 ("nb") 最常见的书面形式看起来很像丹麦语。如果没有挪威语,则丹麦语可能是不错的第二选择。类似地,尽管事实并非如此,但要求使用瑞士德语 ("gsw") 的用户很可能会被提示使用德语 ("de"). 请求维吾尔语的用户可能更愿意使用中文而不是英语。其他例子比比皆是。如果不支持用户要求的语言,那么最好不要使用英语.
语言的选择比翻译更重要
假设用户要求丹麦语,而德语为第二选择。如果应用程序选择德语,则它不仅必须使用德语翻译,还必须使用德语 (而非丹麦语) 归类。否则,例如,动物列表可能会在 "Äffin" 之前将 "Bär" 排序.
根据用户的首选语言选择一种受支持的语言,就像握手算法:首先确定要使用哪种协议 (语言) 进行通信,然后在会话期间坚持使用该协议进行所有通信.
将语言的 "父级" 用作后备并非易事
假设您的应用程序支持安哥拉葡萄牙语 ("pt-AO"). golang.org/x/text 中的软件包,例如排序规则和显示,可能对此语言没有特定的支持。在这种情况下,正确的做法是匹配最接近的母方言。语言是按层次结构排列的,每种特定的语言都有一个更通用的父级。例如,"en-GB-oxendict" 的父级是 "en-GB", 其父级是 "en", 其父级是未定义的语言 "und", 也称为根语言。在进行归类的情况下,葡萄牙语没有特定的归类顺序,因此归类包将选择根语言的排序顺序。显示包支持的最接近安哥拉葡萄牙语的父语言是欧洲葡萄牙语 ("pt-PT"), 而不是更明显的 "pt", 这表示巴西语.
通常,父级关系没那么简单的。再举几个例子,"es-CL" 的父级是 "es-419", "zh-TW" 的父级是 "zh-Hant", 而 "zh-Hant" 的父级是 "und". 如果仅通过删除子标签来计算父对象,则可能会选择用户无法理解的 "方言".
Go 中的语言匹配#
在 golang.org/x/text/language 程序包中实现了 BCP 47 语言标签标准,并增加了根据发布的数据确定使用哪种语言的支持在 Unicode 通用语言环境数据存储库 (CLDR) 中.
这是一个示例程序,如下所述,该程序将用户的语言首选项与应用程序支持的语言相匹配:
package main
import (
"fmt"
"golang.org/x/text/language"
"golang.org/x/text/language/display"
)
var userPrefs = []language.Tag{
language.Make("gsw"), // 瑞士德语
language.Make("fr"), // 法语
}
var serverLangs = []language.Tag{
language.AmericanEnglish, // en-US 美国英语
language.German, // de 德语
}
var matcher = language.NewMatcher(serverLangs)
func main() {
tag, index, confidence := matcher.Match(userPrefs...)
fmt.Printf("best match: %s (%s) index=%d confidence=%v.",
display.English.Tags().Name(tag),
display.Self.Name(tag),
index, confidence)
// 最佳匹配: 德国 (Deutsch) 指数=1 可信度=高
// best match: German (Deutsch) index=1 confidence=High
}
创建语言标签#
从用户提供的语言代码字符串创建语言的最简单方法是使用 language.Make. 它甚至可以从格式错误的输入中提取有意义的信息。例如,即使 USD 不是有效的子标签,"en-USD" 也将显示为 "en".
Make 不会返回错误。如果仍然发生错误,通常的做法是使用默认语言,这样会更加方便。使用解析可手动处理任何错误.
HTTP Accept-Language 标头通常用于传递用户所需的语言. ParseAcceptLanguage 函数将其解析为一片语言标签,按优先级排序.
默认情况下,语言包不规范标签。例如,如果它是 "绝大多数" 中的常见选择,则它不遵循 BCP 47 建议删除脚本。它同样忽略了 CLDR 的建议: "cmn" 未替换为 "zh", "zh-Hant-HK" 未简化为 "zh-HK". 规范化标签可能会丢弃有关用户意图的有用信息。规范化将在 Matcher 中处理。如果程序员仍然愿意,可以使用完整的规范化选项.
将用户首选的语言与支持的语言进行匹配#
Matcher 将用户首选的语言与支持的语言进行匹配。强烈建议用户不要使用所有复杂的匹配语言.
Match 方法可以将用户设置 (来自 BCP 47 扩展名) 从首选标签传递到所选的受支持标签。因此,使用 Match 返回的标签来获取特定于语言的资源非常重要。例如,"de-u-co-phonebk" 请求订购德语的电话簿。该扩展名将被忽略以进行匹配,但由归类包用于选择相应的排序顺序变体.
使用应用程序支持的语言初始化 Matcher, 这些语言通常是翻译所使用的语言。该集合通常是固定的,允许在启动时创建匹配器。对 Matcher 进行了优化,以提高 Match 的性能,但以初始化成本为代价.
语言包提供可用于定义支持的语言集的最常用语言标签的预定义集。用户通常不必担心为支持的语言选择确切的标签。例如,AmericanEnglish ("en-US") 可以与更常见的 English ("en") 互换使用,后者默认为 American. 匹配器都是一样的。应用程序甚至可以同时添加这两种语言,从而允许针对 "en-US" 使用更具体的美式英语.
匹配示例#
考虑以下匹配器和支持的语言列表:
var supported = []language.Tag{
language.AmericanEnglish, // en-US: 后备的第一语言
language.German, // de
language.Dutch, // nl
language.Portuguese // pt (默认为巴西)
language.EuropeanPortuguese, // pt-pT
language.Romanian // ro
language.Serbian, // sr (默认为西里尔文字)
language.SerbianLatin, // sr-Latn
language.SimplifiedChinese, // zh-Hans
language.TraditionalChinese, // zh-Hant
}
var matcher = language.NewMatcher(supported)
让我们看一下针对各种用户首选项的支持语言列表与这些匹配项.
对于 "he" (希伯来语) 的用户偏好,最佳匹配是 "en-US" (美国英语). 没有良好的匹配,因此匹配器使用后备语言 (受支持列表中的第一种).
对于 "hr" (克罗地亚语) 的用户偏爱,最佳匹配是 "sr-Latn" (塞尔维亚语和拉丁语脚本), 因为一旦使用相同的脚本编写,塞尔维亚语和克罗地亚语就可以相互理解.
对于 "ru, mo" (俄语,然后是摩尔达维亚语) 的用户偏爱,最佳匹配是 "ro" (罗马尼亚语), 因为摩尔达维亚语现在被规范地分类为 "ro-MD" (摩尔多瓦语中为罗马尼亚语).
对于 "zh-TW" (台湾普通话) 的用户偏爱,最匹配的是 "zh-Hant" (繁体中文), 而不是 "zh-Hans" (简体中文).
对于 "af, ar" (南非语,然后是阿拉伯语) 的用户偏好,最佳匹配是 "nl" (荷兰语). 两种偏好都无法直接得到支持,但荷兰语与南非荷兰语的匹配程度远比后备语言英语更接近.
对于 "pt-AO, id" (安哥拉葡萄牙语,然后是印尼语) 的用户偏好,最佳匹配是 "pt-PT" (欧洲葡萄牙语), 而不是 "pt" (巴西葡萄牙语).
对于 "gsw-u-co-phonebk" (具有电话簿整理顺序的瑞士德语) 的用户偏好,最佳匹配是 "de-u-co-phonebk" (具有电话簿整理顺序的德语). 在服务器的语言列表中,德语是瑞士德语的最佳匹配,并且电话簿整理顺序的选项已被保留.
信心分数#
Go 使用带有基于规则的消除的粗粒度置信度评分。一场比赛分为 "完全", "高" (不完全准确,但没有已知的歧义), "低" (可能是正确的,但可能不是) 或 "否". 在有多个比赛的情况下,有一组抢七决定规则按顺序执行。如果有多个相等的匹配项,则返回第一个匹配项。这些置信度得分可能有用,例如,拒绝相对较弱的匹配。例如,它们还用于对语言标签中最可能的区域或脚本进行评分.
其他语言的实现通常使用更细粒度的可变比例评分。但我们发现,在 Go 实现中使用粗粒度计分最终实现起来更简单,更可维护且更快捷,这意味着我们可以处理更多规则.
显示支持的语言#
golang.org/x/text/language/display 程序包允许使用多种语言命名标签。它还包括一个 "自身" 的命名器,用于其自己的语言标签.
例如:
var supported = []language.Tag{
language.English, // en
language.French, // fr
language.Dutch, // nl
language.Make("nl-BE"), // nl-BE
language.SimplifiedChinese, // zh-Hans
language.TraditionalChinese, // zh-Hant
language.Russian, // ru
}
en := display.English.Tags()
for _, t := range supported {
fmt.Printf("%-20s (%s).", en.Name(t), display.Self.Name(t))
}
打印
English (English)
French (français)
Dutch (Nederlands)
Flemish (Vlaams)
Simplified Chinese (简体中文)
Traditional Chinese (繁體中文)
Russian (русский)
在第二栏中,请注意大小写上的差异,以反映相应语言的规则.
总结#
乍一看,语言标签看起来像结构良好的数据,但是由于它们描述的是人类语言,因此语言标签之间的关系结构实际上非常复杂。特别是对于讲英语的程序员,通常只使用语言标签的字符串操作来编写即席语言匹配,这往往很诱人。但实际上会产生出乎意料的结果.
Go 的 golang.org/x/text/language 软件包解决了这个复杂的问题,同时仍然提供了一个简单易用的 API. 值得一试.
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: