IETF 语言标签和 IANA 语言子标签注册表





5.00/5 (3投票s)
RFC5646 文档的简化指南;什么是语言标签,如何解析,什么是 IANA 注册表。如何在任何编程语言中实现一个库,以根据支持的语言列表查找用户首选语言。提供了 C# 实现。
引言
本文介绍如何使用语言标签标记数据,以便在不要求用户从支持的语言列表中选择语言的情况下,通过自动查找为用户提供我们能提供的最佳语言信息。
这一切都始于我试图弄清楚如何在产品数据库中存储翻译,我希望我的“生产者”用户插入任意翻译,用任意语言标签标记它们,然后让我的“消费者”用户以他们偏好的语言消费翻译。
我无法使用 .NET 框架完成上述操作,然后我开始研究这个问题,以下是我发现的内容。
一些哲学
首先定义语言:我们可以通过多种方式定义语言,例如,语言可以被视为一组句法规则,但为了我的目的,在这种情况下,我更倾向于将语言定义为“意义”集和“短语”集之间的关系;这意味着对于一个单一的意义,我们可以使用不同的短语来表达相同的意义,使用不同的语言;
∀ c ∈ 概念,∀ l ∈ 语言,∃ P ∈ 短语:P = l(c),P ⊂ 短语
其中 P 是短语的子集,最终可能为空。
所以我们只有一个概念,但 0 到多个短语可以用每种语言表达它(语言是一种关系而不是一个函数)。
想象我们有一个“意义”或“概念”,我们可以用一个短语来编码这个“意义”,语言决定了我们必须使用哪些词将“意义”编码成信息,如果我们的语言表达力足够强,那么接收者应该能够相当精确地重构概念,前提是接收信息的人知道这种语言。
以上很不错,可以帮助我们建模实体关系数据库,但是,人类语言的问题在于我们不能认为“英式英语”等于“美式英语”,但它们非常相似;我们说语言是“概念”编码器:如果一个说 A 语言的人能够通过重构一个足够接近预期概念的概念来解码用 B 语言表达的信息,我们能认为语言 A 与语言 B 相似吗?我想可以。
所以我们可以通过将相似的语言分组来划分语言集,我们可以将这个分区集命名为“语系”分区。
IETF 语言标签应用
回到现实:作为软件开发人员,我们需要我们的软件向用户传递信息,我们需要用户理解“如果按下那个按钮,我将保存你的文件”之类的概念。请记住:概念只有一个,但有许多语言可以表达它。我们大多数人几乎不可能在应用程序中实现地球上所有的语言,但我们可以利用许多语言相似这一事实来覆盖更广泛的受众。
我的意思是,在某些情况下,我们可能希望通过翻译成每一种语言变体来完成高质量的工作,但在许多情况下,使用大型语系就足够了,本地化系统应该处理这两种用例。
现在,我们暂时忘记相似语言的存在以及它们可以被分组在一起的事实,在这种情况下,我们的软件可以为每种支持的语言存储向用户表达信息所需的文本,以“保存文件”概念为例
示例
我们的概念存储可以是概念 x 语言 x 短语的笛卡尔积的一个子集
SaveFile, Italian Language, Salvare il file SaveFile, French Language, Enregistrer le fichier ... etc
使用这样的存储,我们的用户界面可以通过简单地选择语言值等于用户首选语言的三元组来用正确的语言传达 SaveFile 概念,如果找不到匹配项,则默认为给定语言。
以上听起来很简单,但是,我们进行得太快了,我们很接近了,但我们必须把事情复杂化一点:如果我们支持“美式英语”,但用户的偏好是“英式英语”怎么办?
美式英语与英式英语不同。生活在美国的人会更喜欢 en-US,但他们中的大多数人仍然会更喜欢 en-GB,而不是撒哈拉阿拉伯语或粤语。一个好的查找应该返回一种相似的语言,而不是默认语言。
我们的用户界面可以决定支持“美式英语”和“英式英语”,或者“通用英语”,或者只支持其中一种。在后一种情况下,如果我们想扩大受众并仍然提供可接受的用户体验,我们需要处理近似查找。
现在,一分钟过去了,我们必须记住,在我们的语言集中有“语系”子集:如果我们的用户界面支持与某个“语系子集”中的语言相同的语言,也许我们可以提供那种语言而不是默认语言。
知道语系的存在,我们可以在“概念”存储上进行近似查找,但如果存储是我上面描述的那种简单存储,则不行:我们需要在某个地方存储“美式英语”是“英式英语”的一个很好的近似值的信息,或者我们必须以一种更具表现力的方式定义语言标签,IETF 的 BCP47 标准同时做到了这两点。
如果我们使用 IETF 语言标签标记集合中的每种语言,我们可以通过查看语言标签本身来推断语言之间的关系,并且我们还可以从一个外部语言数据库(称为 IANA 语言子标签注册表)获得额外的帮助。
以下是全部要点,也是目前为止所说内容的总结,整篇文章可以归结为
我们如何将用户语言与我们的概念存储匹配?如果我们有完美匹配,那就完美了,但如果没有,我们仍然可以在用户偏好和我们支持的任何语言之间找到关系,这将是一个很好的近似匹配。当然,我们永远不会通过字符串比较来匹配“美式英语”和“英式英语”,我们需要一种方法来权衡同一“语系”中相似语言的权重。
为什么要有一个标准
在过去的好时光里,为了支持多种语言,我们只需要一个像我上面段落中提出的那样简单的存储,我们不需要任何花哨的语言标签语法,因为我们不必对用户语言偏好进行近似匹配:我们只需要提供一个语言列表,用户选择其中一个,然后我们的软件就会选择与用户在列表中选择的语言相对应的翻译,每个人都很满意。
但如今,用户期望软件根据用户的偏好自动选择最佳语言。应用程序必须在某个地方读取用户偏好,然后根据设置决定使用哪种语言与用户交流,复杂之处在于,用户首选的语言可能不受支持,但存在一种相似的语言(更通用的语言优先/不太通用的语言是第二选择)。
现在,我们当然需要我们的应用程序了解如何获取用户偏好。这就是为什么我们需要一种表示语言的标准方式。
我所说的标准是 IETF 的 bcp47,bcp47 被广泛采用,遵守它我们可以与世界进行交流(我指的是软件间通信)。所有我所知的操作系统都支持该标准,所有网络客户端和服务器也支持。这很好,因为大多数时候我们不必询问用户他们喜欢什么语言,我们通过调用一些 API 来获取用户语言,这些 API 读取一些配置文件。
例如,在 .NET 中,我们通过调用以下 API 获取当前线程语言
string preferredUserLanguageName = Thread.CurrentThread.CurrentCulture.Name;
ietf 语言标签语法的想法是:如果我们将我们的语言称为 en-US,我们应该认为它与 en-GB 几乎完美匹配,因为它们共享第一个标签。嗯,这是大局,但事情要复杂得多:ietf 的 bcp47 在语言集上创建了一个分区层次结构。
.NET 中本地化的实现方式
通常,本地化资源存储由 resx 文件组成:有一个或多个资源文件集,每个集都有一个主资源文件 - <ResourceFile>.resx - 然后有一堆包含本地化版本的子文件 - <ResourceFile>.<ietf-language-tag>.resx - 编译器将它们编译到程序集的卫星 DLL 中,运行时将根据用户界面的需要自动查找用户可用的最佳本地化资源,你只需填充本地化 resx 文件即可。
对于 .NET 人员:为了进一步完善您的本地化字符串,您可以使用 Scott Rippey 的这款精美工具 SmartFormat
为什么要研究和实现 BCP47?
.NET Framework 的标准实现非常好,我不想在那里竞争,99% 的情况下它已经足够了,但也有一些限制,或者说我有一些限制,所以我决定无论如何都要实现查找。
我试图找到一种将翻译存储在 SQL 数据库中的方法,例如,产品描述记录需要翻译;但我对资源文件不满意,因为我希望用户能够添加语言标签而无需重新编译和部署卫星 DLL。
另一个原因是,采用新的实现比研究现有工具的精力更少,因为概念相当简单。
最后,我认为也许在未来(这通常不会发生:)),定制查找策略的可能性可能会很有用,例如,实现一种不同类型的查找会很好,用户提供一个首选语言列表而不是单一语言,就像网络浏览器一样。
好的,但是为什么要写一篇文章呢?
因为除了完整的 RFC 5646 本身,我没有找到任何简单的资源可供阅读。
背景
用于标识语言的标签由这份公开文档定义:https://tools.ietf.org/html/bcp47。
该文档的第 2 章定义了语言标签的语法。
语言标签由连字符分隔的块组成,像 en-US 一样简单,或者像 zh-yue-CN-a-anyext-x-private-x-otherprivate 一样复杂;第 2 章还指定了这些子标签如何组合以生成有效的语言标签。
第三章介绍了 IANA 子标签注册表,该注册表定义了所有有效的子标签,除了特定于应用程序的子标签。
你可以在这里下载注册表 http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry,它只是一个 Unicode 文本数据库。
查找语言
好的,我们知道在我们的存储中有一种标准方式来编码语言标签,并且我们知道这些标签由一个子标签层次结构组成,如 Lang-Region 或 Lang-Script-Region-Variant,除了 Lang 标签,所有其他都是可选的;下一个问题是:我们如何将用户语言与支持的语言进行匹配?假设我们的 UI 支持三种语言:en(通用英语)、es(通用西班牙语)和 fr-FR(法国法语)
现在再假设一个用户来到我们的 UI,并且她偏爱 es-CO 语言(哥伦比亚西班牙语)。
UI 需要做的是针对 (es, en, fr-FR) 查找语言标签 es-CO 的最佳匹配;在这种特殊情况下,我希望我们的查找函数返回 es,因为语言子标签匹配;
上述查找可能难以实现,嗯,没那么难,但也没那么容易,因为许多子标签是可选的,以支持复杂或简单的用例:bcp47 支持复杂的用例,需要复杂的语言标签来解释方言、拼写变体、扩展等等。
MSDN 不鼓励开发人员实现语言查找,最佳实践是使用框架集成的全球化功能。
但我们将忽略 MSDN;为了进行查找,我们需要解析语言标签字符串,以便将其分解为各个部分,并能够将区域与区域、脚本与脚本进行比较,而不是将区域与脚本或扩展语言与变体进行比较(参见语法章节)。
解析后,我们需要按照正确的顺序比较字符串的单个标记(语言、脚本、区域等),为每种可用语言分配一个等级,然后返回排名最高的语言;一个简单的实现可以是
Lang user = parse("es-CO");
Lang avail1 = parse("en");
Lang avail2 = parse("es");
Lang defaultL = avail1;
int rank1 =
user.Language == avail.Language ? 32: -32
+
user.Script == avail.Script ? 16 : -16
+
//... and so on ..
//decide your policy for matching language in order to provide better service to the user
;
int rank2 = //..
if(rank1 > rank2 && rank1>0)
return avail1;
else if(rank2 > rank1 && rank2>0)
return avail2;
return defaultL;
由于 es 标签是结果,用户界面可以查看存储并提供带有 es 语言标签的字符串;对于我们的用户来说,es 是对 es-CO 的良好匹配,优于默认的英语语言。
好吧,上面的伪代码,除了它的丑陋和零可重用性问题,可能还不错,事实上 BCP47 规定语言标签的语法设计允许在不读取 IANA 语言子标签注册表的情况下进行有用的查找。但上面的伪代码忽略了我们可能在注册表中找到的很多信息。
为什么使用注册表更好?
事实是,查看注册表更好。一个优点是用户可能偏好一些已被重命名或不规范的语言。
示例
我们的应用程序支持语言 yue-HK,而用户偏好 zh-yue-HK,如果我们不知道它们代表同一种语言,就无法匹配这些字符串,zh-yue 是 yue 的冗余形式,只有注册表能提供帮助。
使用注册表可以进行规范化。
另一个例子
假设我们的 UI 将粤语 yue 作为默认语言,但也支持美索不达米亚阿拉伯语 acm。用户偏好撒哈拉阿拉伯语 aao,我对美索不达米亚阿拉伯语和撒哈拉阿拉伯语一无所知,但我猜用户会更喜欢美索不达米亚阿拉伯语 UI 而不是粤语 UI。
注册表在 acm 和 aao 语言之间建立了一种关系,它们都共享相同的宏语言 ar 阿拉伯语,这就是我们的匹配函数在这种特定情况下的行为方式,同一宏语言关系中的语言将被优先考虑(我的观点)而不是默认语言。
最后但同样重要的是,使用注册表简化了语言标签解析器的编写。
.NET 框架本地化参考
您可以在此处找到有关 .NET 优秀 BCP47 实现的更多信息:https://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo(v=vs.110).aspx (希望此链接能将您带到正确的本地化页面 :-))
BCP47 语言标签语法
在本章中,我将展示一个格式良好的语言标签是如何组成的,您可以将其视为对 RFC 5646 第 2 章内容的简化介绍。
本章和下一章是关于技术细节的,如果您愿意,可以跳过这两章,如果您打算创建自己的实现,阅读这些章节会很有益处。
语言标签是不区分大小写的字符串,有三种类型的标签:普通语言标签、私用标签和祖父标签。
私有标签
私有标签以字符(不区分大小写)“x”开头,后跟破折号“-”,然后是任意数量的私有子标签:例如“x-private-more-private”,子标签的长度必须在 1 到 8 个字符之间。
祖父标签
祖父标签是特殊情况,它们的结构没有语法意义,例如字符串 i-klingon 是一个祖父标签,要验证 i-klingon,解析器只需在注册表中查找与整个字符串 i-klingon 不区分大小写匹配的任何祖父记录,以下是一个祖父语言标签的示例注册表条目
Type: grandfathered Tag: i-klingon Description: Klingon Added: 1999-05-26 Deprecated: 2004-02-24 Preferred-Value: tlh
正如您所注意到的,上述 i-klingon 祖父记录已被弃用,取而代之的是值 tlh,这也意味着在查找过程中 tlh 与 i-klingon 精确匹配。
普通语言标签
一个普通的语言标签总是以一个语言子标签开头,后面跟着(可选的)子标签;普通语言标签被解析为一个具有 language, extlang, script, region, variant, extension, private 字段的结构。
extlang 结构字段可以省略,因为注册表包含一个主语言条目来替换任何有效的语言-子语言组合。
话虽如此,为了能够解析普通的语言标签,我们必须知道它的语法,这里是它的 ABNF 语法(请参阅 RFC 5646 第 2.1 节以获取详细语法)
primarylanguage ["-" extlang] ["-" script] ["-" region] *("-" variant) *("-" extension) ["-" privateuse]
主要语言子标签
语言标签的第一个子标签始终是主要语言子标签,由 2 或 3 个字符组成,代表语言的 ISO 639 代码:复杂之处:ABNF 语法允许更长的子标签(最多 8 个字符),但这些必须被视为保留用途:这是使用注册表的另一个好理由,因为它只包含有效的主要语言子标签。
示例
zozo 不是一个有效的语言标签,它在语法上有效但不是有效的 ISO 639 代码
zoz 在语法上有效但不是有效的 ISO 639 代码
zoo 是一个有效的语言标签,因为它以一个有效的语言子标签开头(Asunción Mixtepec Zapotec)
扩展语言子标签
尽管语言标签的首选形式不包含扩展语言子标签,但出于兼容性原因,语言标签也可以由主要语言后跟一个扩展语言子标签组成。IANA 注册表上的每个扩展语言子标签都有一个对应的主要语言标签;为了阐明这一点,请考虑以下示例:标签 ar-aoo 和标签 aoo 代表相同的语言,这意味着在解析时我们可以将输入字符串 ar-aoo 规范化为 aoo。
扩展语言子标签总是三个字符长,ABNF 语法允许每个语言标签最多有三个扩展语言子标签,但这种用法是保留的,我的实现将认为具有多个扩展语言子标签的语言标签是无效的。
示例
ar-aao-acm 在 ABNF 语法上是正确的,但无效,因为使用多个扩展语言子标签是保留的
文字子标签
表示要使用的文字,例如阿拉伯文、西里尔文、拉丁文等等;注册表包含所有有效文字子标签的列表。
此子标签是可选的,只有在解析语言和扩展语言(如果存在)之后才能解析,并且长度必须恰好为 4 个字符,他们建议使用驼峰式大小写以方便人类阅读,但始终必须视为不区分大小写。
示例
标签 sr-Cyrl 必须解析为
语言:sr (sr 只能是语言,因为第一个子标签总是语言子标签,除非它是“i”或“x”)
脚本:Cyrl (只能是脚本,因为它有四个字符长度,并且位于语言之后,所以不能是区域)
请注意,注册表可以为给定语言指定 Suppress-Script 属性,例如 it-Latn 不正确,因为 Latn 下标已被抑制,可以作为输入值容忍,但不能作为输出。
区域子标签
区域代表语言使用的地理位置。
区域子标签是可选的,必须跟随语言、扩展语言和脚本(如果存在),必须是 2 个字符的国家 ISO 代码或 3 位数字的 UN-m49 代码。
示例
标签 it-756 格式良好(但在注册表中无效),必须解析为
语言:it
区域:756 (只能是区域子标签“756”,因为它正好是 3 位数字,不能是脚本)
IANA 注册表不会保留所有 UN-m49 代码,在这种情况下,不包括 756 子标签,因此即使 IT-756 在语法上正确,即使 756 是有效的 UN-m49 值,我的软件也无法将其识别为有效标签。
变体子标签
变体表示语言上的变体,如方言。
变体子标签在解析时不能与其他子标签混淆,因为如果以“a-z”开头,它必须是 5 个字符长,如果以数字开头,则至少是 4 个字符长。
示例
sl-nedis 标签必须解析为
语言:sl
变体:nedis (它有 5 个字符长,所以它不能是脚本也不能是区域,只能是变体)
例如,“sl-nedis”代表 Nadiza 地区所说的斯洛文尼亚语方言。
示例
标签 de-CH-1996 必须解析为
语言:de
区域:CH (因为在语言子标签之后且长度为 2 个字符)
变体:1996 (它是一个变体,因为它以数字开头且长度为 4 个字符)。
例如,上述标签代表在瑞士使用的德语,并采用公元 1996 年开始的拼写改革。
扩展子标签和私有子标签
不常用,但所提供的解析器或多或少能够处理它们。
两者都以单个字符开头,如果第一个字符是 x,则它是私有子标签,否则它是扩展子标签。所有跟随单字符 x 的子标签都必须被视为私有,即使它们与注册表中的某些条目匹配。
请参阅 RFC 5646 或查看源代码。
IANA 语言子标签注册表
注册表文件可以从 http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry 下载,然后本地缓存。
如果需要全面的知识,我建议阅读 RFC。
注册表有助于解析和验证语言标签,允许规范化过时和冗余的语言标签,还有助于保持属于同一宏语言的语言之间的关系。
注册表本身由一系列记录组成,每个记录包含一个键值对列表。RFC 详细解释了记录的结构。
所有记录都必须有一个“Type”条目,除了文件中第一个只包含 File-Date 条目的记录。
记录类型有:language、extlang、script、region、variant、grandfathered 和 redundant
我构建的解析器读取注册表文件,并将所有记录加载到一堆按标签值(不区分大小写)索引的字典中,以下是注册表上的字典列表
- 有效语言记录
- 有效扩展语言记录
- 有效脚本记录
- 有效区域记录
- 有效变体记录
- 有效祖父记录
- 有效冗余记录
以上所有字典都用于帮助解析语言标签和进行验证。
使用代码
用法非常简单;您需要提供一个语言标签列表。这些语言标签将代表您的 UI 支持的语言。您可以通过这种方式添加支持的语言
bcp47.LangSet s = new bcp47.LangSet(); s.Add("en-US"); //this will be the default language, the first added s.Add("de");//German s.Add("fr");//French s.Add("ja");//Japanese s.Add("es");//Spanish
变量 s 现在将包含支持的语言列表,可以根据用户提供的首选语言字符串进行查询。
以下是最佳匹配语言查找的示例。
string userPreferredLanguage = "es-CO"; Lang bestMatchOrDefault = s.Lookup(test); //will return "es" //good now i can show the user the product description searching my database for //the description which have bestMatchOrDefault.Canonical tag string ProductDescriptionText = myTranlationDatabase.Translate("ProductDescription", bestMatchOrDefault.Canonical);
关注点
关于提供的实现
可以改进的地方
我对这段代码不满意的一点是,如果它在本地找不到缓存文件,它会在后台从 IANA 网站加载注册表,也许注册表文件应该是一个可注入的依赖项:在撰写本文时,LangSet.Add 方法将依次调用 Lang.Parse 方法,Lang 类有一个静态构造函数,它会通过调用 Registry.Load() 创建 Registry 的新实例,Registry.Load() 会检查是否存在名为“.iana-language-registry”的本地文件,如果不存在则从网站下载它,我将来会做得更好。在我安装该库的地方,我还确保存在缓存文件的副本,附件项目将展示如何操作(在项目中包含缓存文件并在编译时将其复制到输出文件夹)
我另一点不喜欢将来可能会改变的是线程安全实现,目前它是安全的但不是最优的,因为我正在序列化所有读写操作。
注册表解析器
注册表解析器相当简单:从注册表文件中读取第一条记录;它必须是一条文件日期记录,然后只要有字节,就将下一条记录读取到一个键值字典中,并根据记录类型,将记录添加到正确的索引,否则忽略该记录
public static Registry Load(StreamReader sr) { Registry u = new Registry(); var d = NextRecord(sr); if (d.Count != 1 || !d.ContainsKey("File-Date") || DateTime.TryParseExact(d["File-Date"], "yyyy-mm-dd", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AllowTrailingWhite, out u.registryUpdated) == false) throw new FormatException(); while (!sr.EndOfStream) { d = NextRecord(sr); CheckRecord(d); if (d["Type"] == "language") { u.languageIndex.Add(d["Subtag"], ParseRecord(d)); } else if (d["Type"] == "extlang") { u.extlangIndex.Add(d["Subtag"], ParseRecord(d)); } else if (d["Type"] == "script") { u.scriptIndex.Add(d["Subtag"], ParseRecord(d)); } else if (d["Type"] == "region") { u.regionIndex.Add(d["Subtag"], ParseRecord(d)); } else if (d["Type"] == "variant") { u.variantIndex.Add(d["Subtag"], ParseRecord(d)); } else if (d["Type"] == "grandfathered") { u.grandfatheredIndex.Add(d["Tag"], ParseRecord(d)); } else if (d["Type"] == "redundant") { u.redundantIndex.Add(d["Tag"], ParseRecord(d)); } } return u; }
解析注册表文件后,所有注册表的字典都已填充,并按子标签索引。每个字典条目的内容都是注册表记录。
注册表记录
注册表记录由少数只读字段组成,我们可以使用此记录来验证语言标签或查找语言子标签的属性。
public class Record { public readonly string Type; public readonly string Subtag; public readonly string Tag; public readonly string PreferredValue; public readonly string Description; public readonly DateTime Created; public readonly string SuppressScript; public readonly string MacroLanguage; readonly HashSet<string> Prefix; //... }
PreferredValue 字段帮助我们规范化冗余形式的标签,例如 i-klingon 可以规范化为 tlh
语言标签解析器
语言标签解析器的目的是从语言标签字符串生成语言标签记录。
语言标签记录结构如下
public class Lang : IEquatable<Lang> { //all fields are readonly thus i'm thread safe public readonly Record Language; public readonly Record ExtLang; public readonly Record Script; public readonly Record Region; public readonly ReadOnlyCollection<Record> Variants; public readonly ReadOnlyDictionary<char, ReadOnlyCollection<string>> Extensions; public readonly string Private; public readonly string Canonical; }
例如,解析字符串 es-CO 的结果将是一个 Lang
记录,其中包含对注册表中每个子标签记录的引用,如下所示
Lang l = Lang.Parse("es-CO"); Console.WriteLine("Lang: {0}", l.Language?.Description.Replace('\n',',')); Console.WriteLine("ExtLang: {0}", l.ExtLang?.Description); Console.WriteLine("Script: {0}", l.Script?.Description); Console.WriteLine("Region: {0}", l.Region?.Description); //Lang: Spanish,Castilian //ExtLang: //Script: Latin //Region: Colombia
解析算法本身非常简单,将输入字符串分成块“es-CO”,然后获取第一个标记并开始
- 确保第一个标记是注册表上的有效主要语言子标签,否则失败。
- 获取下一个标记
- 尝试在注册表中查找扩展语言,如果找到,获取下一个标记。
- 尝试在注册表中查找脚本,如果找到,获取下一个标记。
- 与第 3 和第 4 点相同,适用于其他子标签类型...
查找算法
实际算法尚未优化,其逻辑是从最重要的标签(主要语言标签)开始,并移除不匹配的项,直到只剩下一个候选者:它所做的是:从候选者列表开始,并在每一步移除不匹配的项,第一步是匹配主要语言,然后是脚本,然后是区域,你明白了;你可以个性化算法。
示例
我们需要将 es-CO 与 es, en, fr 进行匹配
移除所有与 es 主要语言不匹配的受支持语言,所以我们从 (es, en, fr) 开始,然后移除 (en, fr),所以我们只剩下 (es),这是唯一一个通过第一步的,所以我们返回它。
代码中有注释,如果您需要个性化查找算法,值得一看。
历史
1.0.0 - 2016-10-31 第一个版本