跳转到主要内容

用于使用IETF语言标签标记人类语言的工具

项目描述

Langcodes:语言代码库

langcodes 了解语言。它知道指代它们的标准化代码,例如 en 代表英语,es 代表西班牙语,hi 代表印地语。

这些是 IETF 语言标签。你可能知道它们以前的名称,ISO 639 语言代码。IETF 为向后兼容性和支持在 ISO 标准中找不到的语言变体做了一些重要工作。

你可能觉得 langcodes 解决了一个相当无聊的问题。在某种程度上,这是正确的。有时候你有一个无聊的问题,当有一个库为你解决它时,那真是太好了。

但这里隐藏着一个有趣的问题。你如何处理语言代码?你怎么知道两个不同的代码代表相同的事物?你的代码应该如何表示代码之间的关系,如下所示?

  • eng 等同于 en
  • frafre 都等同于 fr
  • en-GB 可能会被写成 en-gben_GB。或者 'en-UK',这是错误的,但应被视为相同。
  • en-CAen-US 不完全等同,但它真的很接近。
  • en-Latn-US 等同于 en-US,因为书面英语必须使用拉丁字母才能被理解。
  • ararb 的区别是 "阿拉伯语" 和 "现代标准阿拉伯语" 之间的区别,这可能对你来说并不重要。
  • 你会在维基词典中看到标注为 cmn 的普通话,但许多其他资源会将同样的语言称为 zh
  • 中文在不同地区使用不同的书写系统。一些软件区分书写系统,其他软件区分地区。结果是,zh-CNzh-Hans 可以互换使用,zh-TWzh-Hant 也是如此,尽管偶尔你需要其他不同的,如 zh-HKzh-Latn-pinyin
  • 印度尼西亚语(id)和马来西亚语(mszsm)是相互理解的。
  • jp 不是一个语言代码。(日语的语言代码是 ja,但人们常常将其与日本的国代码 jp 混淆。)

了解这些的一个方法是通过阅读 IETF 标准,以及 Unicode 技术报告。另一个方法是使用一个库为你实现这些标准和指南,这就是 langcodes 所做的。

当你处理这些简短的语言代码时,你可能想看到在语言中该语言被称为什么:在英语中,fr 被称为 "French"。这种语言不一定是英语:fr 在法语中被称为 "français"。langcodes 的补充 language_data 提供了这些信息。

langcodes 由 Elia Robyn Lake(别名 Robyn Speer)维护,并以 MIT 许可证发布为免费软件。

实现的标准

尽管这并不是使用它的唯一原因,但 langcodes 会让你更加符合首字母缩略词规范。

langcodes 实现 BCP 47,这是 IETF 关于语言标签的最佳当前实践。BCP 47 也称为 RFC 5646。它包含 ISO 639,并与其向后兼容,它还实现了来自 Unicode CLDR 的建议。

如果你安装了 language_data,langcodes 还可以引用一个语言属性和名称数据库,该数据库由 Unicode CLDR 和 IANA 子标签注册表构建。

总结来说,langcodes接收语言代码并对它们做正确的事,如果你想确切地知道什么是正确的事,有一些文档你可以去阅读。

文档

标准化语言标签

此函数以多种方式对标签进行标准化。

它将过长的标签替换为其最简版本,并按照BCP 47的约定格式化它们

>>> from langcodes import *
>>> standardize_tag('eng_US')
'en-US'

它移除与语言冗余的脚本子标签

>>> standardize_tag('en-Latn')
'en'

如果可能,它将已弃用的值替换为其正确版本

>>> standardize_tag('en-uk')
'en-GB'

有时这涉及到复杂的替换,例如将塞尔维亚-克罗地亚语(sh)替换为拉丁语塞尔维亚语(sr-Latn),或者整个标签sgn-US替换为ase(美国手语)。

>>> standardize_tag('sh-QU')
'sr-Latn-EU'

>>> standardize_tag('sgn-US')
'ase'

如果macro为True,则使用宏观语言代码作为该宏观语言中最常见的标准化语言的替换

>>> standardize_tag('arb-Arab', macro=True)
'ar'

即使macro为False,它也会缩短包含宏观语言和语言的标签

>>> standardize_tag('zh-cmn-hans-cn')
'zh-Hans-CN'

如果标签无法根据BCP 47进行解析,这将引发LanguageTagError(ValueError的子类)

>>> standardize_tag('spa-latn-mx')
'es-MX'

>>> standardize_tag('spa-mx-latn')
Traceback (most recent call last):
    ...
langcodes.tag_parser.LanguageTagError: This script subtag, 'latn', is out of place. Expected variant, extension, or end of string.

语言对象

此包定义了一个名为Language的类,它包含解析语言标签的结果。语言对象有以下字段,其中任何一个都可能未指定

  • language:语言本身的代码。
  • script:正在使用的书写系统的4位代码。
  • territory:使用该语言的国家或类似地区的2位或3位代码。
  • extlangs:一个语言代码列表,该代码跟在语言代码之后。(这是允许的语言代码语法,但已弃用。)
  • variants:用于特定语言使用变体的代码,这些变体未涵盖scriptterritory代码。
  • extensions:附加到语言代码上的信息,用于某些特定系统,例如Unicode排序顺序。
  • private:以x-开头的代码,没有定义的含义。

Language.get方法将字符串转换为Language实例,而Language.make方法则从其字段创建Language实例。这些值被缓存,以便再次调用Language.getLanguage.make时,使用相同的值返回相同的对象,以提高效率。

默认情况下,它将替换非标准和过长的标签,就像它正在解释它们一样。要禁用此功能并获取语言标签中实际出现的代码,请使用normalize=False选项。

>>> Language.get('en-Latn-US')
Language.make(language='en', script='Latn', territory='US')

>>> Language.get('sgn-US', normalize=False)
Language.make(language='sgn', territory='US')

>>> Language.get('und')
Language.make()

以下是替换非标准标签的一些示例

>>> Language.get('sh-QU')
Language.make(language='sr', script='Latn', territory='EU')

>>> Language.get('sgn-US')
Language.make(language='ase')

>>> Language.get('zh-cmn-Hant')
Language.make(language='zh', script='Hant')

使用str()函数在Language对象上将其转换回其标准字符串形式

>>> str(Language.get('sh-QU'))
'sr-Latn-EU'

>>> str(Language.make(territory='IN'))
'und-IN'

检查有效性

语言代码是有效的,当IANA为其每个部分分配一个含义时。这个含义可能是“私人用途”。

在langcodes中,我们检查语言子标签、脚本、地区和变体是否有效。我们不检查其他部分,如extlangs或Unicode扩展。

例如,ja是有效的语言代码,而jp不是

>>> Language.get('ja').is_valid()
True

>>> Language.get('jp').is_valid()
False

顶级函数tag_is_valid(tag)可能更方便使用,因为它甚至可以返回False,即使对于无法解析的标签

>>> tag_is_valid('C')
False

如果有一个子标签无效,则整个代码无效

>>> tag_is_valid('en-000')
False

iw是有效的,尽管它是he的已弃用别名

>>> tag_is_valid('iw')
True

空语言标签(und)是有效的

>>> tag_is_valid('und')
True

私人用途代码是有效的

>>> tag_is_valid('x-other')
True

>>> tag_is_valid('qaa-Qaai-AA-x-what-even-is-this')
True

非常不可能的语言标签仍然是有效的

>>> tag_is_valid('fr-Cyrl')
True

包含非ASCII字符的标签无效,因为它们无法解析

>>> tag_is_valid('zh-普通话')
False

获取alpha3代码

在BCP 47之前,有ISO 639-2。ISO试图为人类语言的多样性腾出空间,为每种语言分配一个3位代码,包括那些已经有了2位代码的语言。

不幸的是,这反而导致了更多的混淆。一些语言由于历史原因,最终出现了两个不同的三位字母代码,例如法语,作为“术语”代码是fra,而作为“书目”代码是fre。同时,fr仍然是如果你遵循ISO 639-1时会使用的代码。

在BCP 47中,你应该在可用时使用两位字母代码,这就是langcodes所做的事情。幸运的是,所有有两个不同三位字母代码的语言也都有两位字母代码,所以如果你更喜欢两位字母代码,你不必担心区分。

但有些应用程序特别需要三位字母代码,所以langcodes提供了一个获取这些代码的方法,Language.to_alpha3()。默认情况下,它返回“术语”代码,通过传递variant='B'返回书目代码。

当此方法返回时,它总是返回一个三位字符串。

>>> Language.get('fr').to_alpha3()
'fra'
>>> Language.get('fr-CA').to_alpha3()
'fra'
>>> Language.get('fr-CA').to_alpha3(variant='B')
'fre'
>>> Language.get('de').to_alpha3()
'deu'
>>> Language.get('no').to_alpha3()
'nor'
>>> Language.get('un').to_alpha3()
Traceback (most recent call last):
    ...
LookupError: 'un' is not a known language code, and has no alpha3 code.

对于许多语言,术语和书目alpha3代码是相同的。

>>> Language.get('en').to_alpha3(variant='T')
'eng'
>>> Language.get('en').to_alpha3(variant='B')
'eng'

在langcodes中使用任何这些“过长”的alpha3代码时,它们都会被规范化回alpha2代码

>>> Language.get('zho')
Language.make(language='zh')

与语言名称一起工作

本节中的方法需要一个名为language_data的可选包。您可以使用pip install language_data进行安装,或者使用pip install langcodes[data]请求langcodes的可选“数据”功能。

您在setup.py中设置的依赖项应该是langcodes[data]

用自然语言描述语言对象

能够用用户(或你)能理解的方式描述语言代码,而不是难以理解的简短代码,通常很有帮助。display_name方法允许你用一种语言来描述语言对象。

.display_name(language, min_score)方法将查找语言名称。这些名称来自IANA语言标签注册处,只提供英语,加上CLDR,它用许多常用语言命名语言。

命名事物的默认语言是英语

>>> Language.make(language='fr').display_name()
'French'

>>> Language.make().display_name()
'Unknown language'

>>> Language.get('zh-Hans').display_name()
'Chinese (Simplified)'

>>> Language.get('en-US').display_name()
'English (United States)'

但你可以要求以多种其他语言的语言名称

>>> Language.get('fr').display_name('fr')
'français'

>>> Language.get('fr').display_name('es')
'francés'

>>> Language.make().display_name('es')
'lengua desconocida'

>>> Language.get('zh-Hans').display_name('de')
'Chinesisch (Vereinfacht)'

>>> Language.get('en-US').display_name('zh-Hans')
'英语(美国)'

为什么每个人都把斯洛伐克语和斯洛文尼亚语搞混了?让我们问问他们。

>>> Language.get('sl').display_name('sl')
'slovenščina'
>>> Language.get('sk').display_name('sk')
'slovenčina'
>>> Language.get('sl').display_name('sk')
'slovinčina'
>>> Language.get('sk').display_name('sl')
'slovaščina'

如果语言附加了脚本或地区代码,这些代码将用括号括起来描述

>>> Language.get('en-US').display_name()
'English (United States)'

有时这些可能是标签规范化的结果,例如在这个例子中,遗留标签'sh'变成了'sr-Latn'

>>> Language.get('sh').display_name()
'Serbian (Latin)'

>>> Language.get('sh', normalize=False).display_name()
'Serbo-Croatian'

用自己的语言命名语言有时是件有用的事情,因此.autonym()方法使这变得容易,提供语言自身的显示名称

>>> Language.get('fr').autonym()
'français'
>>> Language.get('es').autonym()
'español'
>>> Language.get('ja').autonym()
'日本語'
>>> Language.get('en-AU').autonym()
'English (Australia)'
>>> Language.get('sr-Latn').autonym()
'srpski (latinica)'
>>> Language.get('sr-Cyrl').autonym()
'српски (ћирилица)'

这些名称来自Unicode CLDR数据文件,在英语中也可以来自IANA语言子标签注册处。它们共同可以为您提供CLDR支持的196种语言的语言名称。

描述语言代码的组成部分

您可以使用.language_name().script_name().territory_name()方法分别获取名称的各个部分,或者使用.describe()方法获取所有存在的部分的字典。这些方法也接受一个语言代码,用于它们应该描述的语言。

>>> shaw = Language.get('en-Shaw-GB')
>>> shaw.describe('en')
{'language': 'English', 'script': 'Shavian', 'territory': 'United Kingdom'}

>>> shaw.describe('es')
{'language': 'inglés', 'script': 'shaviano', 'territory': 'Reino Unido'}

在自然语言中识别语言名称

作为上述操作的逆操作,您可能想通过名称查找语言,将自然语言名称(如“法语”)转换为代码(如'fr')。

该名称可以是CLDR支持的任何语言(见下文“歧义”)。

>>> import langcodes
>>> langcodes.find('french')
Language.make(language='fr')

>>> langcodes.find('francés')
Language.make(language='fr')

但是,此方法目前忽略了来自.display_name()的括号表达式

>>> langcodes.find('English (Canada)')
Language.make(language='en')

仍有改进语言名称匹配方式的空间,因为一些语言名称并不始终以相同的方式命名。此方法目前与数百个在维基词典上使用的语言名称一起工作。

歧义

为了提高可用性,langcodes.find()不需要您指定通过名称查找语言的语言。这可能会导致冲突:如果名称“X”是语言A对语言B的名称,同时也是语言C对语言D的名称呢?

我们可以收集来自CLDR的语言代码,看看这种情况发生了多少次。在大多数类似情况下,B和D是名称也重叠在同一语言中的代码,可以通过某些一般原则来解决。

例如,无论您决定“Tagalog”指的是语言代码tl还是大量重叠的代码fil,这种区别都不取决于您说的是什么语言。我们只需始终返回tl

>>> langcodes.find('tagalog')
Language.make(language='tl')

在少数真正的跨语言歧义情况下,langcodes不会匹配结果。您可以通过传递一个language=参数来说明名称使用的语言。

例如,在多种语言中存在两个不同的名为“Tonga”的语言。它们是to,即英语中称为“Tongan”的汤加语;以及tog,它是马拉维的一种语言,在英语中可以称为“Nyasa Tonga”。

>>> langcodes.find('tongan')
Language.make(language='to')

>>> langcodes.find('nyasa tonga')
Language.make(language='tog')

>>> langcodes.find('tonga')
Traceback (most recent call last):
...
LookupError: Can't find any language named 'tonga'

>>> langcodes.find('tonga', language='id')
Language.make(language='to')

>>> langcodes.find('tonga', language='ca')
Language.make(language='tog')

其他用拉丁字母书写的有歧义的名字是“Kiga”、“Mbundu”、“Roman”和“Ruanda”。

人口语言数据

Language.speaking_population()Language.writing_population()方法获取Unicode对世界上使用某种语言的人数估计。

与语言名称数据一样,这需要安装可选的language_data包。

.speaking_population()估计说某种语言的人数。它可以限制在特定领土内(如国家代码)。

>>> Language.get('es').speaking_population()
493528077

>>> Language.get('pt').speaking_population()
237496885

>>> Language.get('es-BR').speaking_population()
76218

>>> Language.get('pt-BR').speaking_population()
192661560

>>> Language.get('vo').speaking_population()
0

脚本代码将被忽略,因为脚本不涉及说话

>>> Language.get('es-Hant').speaking_population() ==\
... Language.get('es').speaking_population()
True

.writing_population()估计写某种语言的人数。

>>> all = Language.get('zh').writing_population()
>>> all
1240841517

>>> traditional = Language.get('zh-Hant').writing_population()
>>> traditional
36863340

>>> simplified = Language.get('zh-Hans').writing_population()
>>> all == traditional + simplified
True

“书写人口”的估计通常过高,如CLDR关于领土数据的文档所述。在大多数情况下,它们是从那些地方使用的语言中关于识字率的已发布数据中得出的。这没有考虑到世界上许多有文化的人说一种通常不书写、而在不同语言中书写语言的情况。

.speaking_population()一样,这可以限制在特定领土内

>>> Language.get('zh-Hant-HK').writing_population()
6439733
>>> Language.get('zh-Hans-HK').writing_population()
338933

比较和匹配语言

tag_distance函数返回一个从0到134的数字,表示用户希望的语言与支持的语言之间的距离。

距离数据来自CLDR v38.1,并涉及到Unicode联盟所做的许多判断。

距离值

此表总结了语言距离值

含义 示例
0 这些代码代表相同的语言,可能在填充值和归一化之后。 挪威语Bokmål → 挪威语
1-3 这些代码指示微小的区域差异。 澳大利亚英语 → 英国英语
4-9 这些代码指示显著的但无问题的区域差异。 美国英语 → 英国英语
10-24 这取决于您的用例的一个灰色区域。可能会有理解或可用性问题。 南非荷兰语 → 荷兰语,吴语 → 普通话
25-50 这些语言不同,但存在人口原因,预计会有一些可理解性。 泰米尔语 → 英语,马拉地语 → 印地语
51-79 存在理解的大障碍。 日语 → 日语(黑本罗马字)
80-99 这些是使用相同脚本的不同语言。 英语 → 法语,阿拉伯语 → 乌尔都语
100+ 这些语言没有什么特别相似之处。 英语 → 日语,英语 → 泰米尔语

请参阅 tag_distance 的文档字符串以获取更多解释和示例。

寻找最佳匹配语言

假设您有一个支持任何 supported_languages 的软件。用户希望使用 desired_language

函数 closest_supported_match(desired_language, supported_languages) 允许您选择正确的语言,即使没有完全匹配。即使没有完全匹配,它也返回最佳支持语言的标签。

max_distance 参数允许您设置一个阈值,以确定什么可以算作语言支持。它的默认值为 25,这个值对于简单的国际化案例可能足够好,但您可能希望将其设置得更低以要求更高的精度。

>>> closest_supported_match('fr', ['de', 'en', 'fr'])
'fr'

>>> closest_supported_match('pt', ['pt-BR', 'pt-PT'])
'pt-BR'

>>> closest_supported_match('en-AU', ['en-GB', 'en-US'])
'en-GB'

>>> closest_supported_match('af', ['en', 'nl', 'zu'])
'nl'

>>> closest_supported_match('und', ['en', 'und'])
'und'

>>> print(closest_supported_match('af', ['en', 'nl', 'zu'], max_distance=10))
None

还有一个类似的函数 closest_match(desired_language, supported_language),它返回最佳匹配语言标签和距离。如果没有匹配项,它返回 ('und', 1000)。

>>> closest_match('fr', ['de', 'en', 'fr'])
('fr', 0)

>>> closest_match('sh', ['hr', 'bs', 'sr-Latn', 'sr-Cyrl'])
('sr-Latn', 0)

>>> closest_match('id', ['zsm', 'mhp'])
('zsm', 14)

>>> closest_match('ja', ['ja-Latn-hepburn', 'en'])
('und', 1000)

>>> closest_match('ja', ['ja-Latn-hepburn', 'en'], max_distance=60)
('ja-Latn-hepburn', 50)

更多API文档

有许多用于操作和比较语言代码的方法,您可以在代码本身中找到它们的详细文档。

所有有趣的功能都集中在这个文件中,具有丰富的文档字符串和注释。从文档字符串中创建单独的 Sphinx 页面是传统做法,但在这里似乎有些冗余。您可以在上下文中阅读文档字符串,在它们的自然栖息地中,并且它们总是最新的。

带有文档的代码

变更日志

版本 3.3(2021年11月)

  • 更新到 CLDR v40。

  • 将 IANA 子标签注册表更新到版本 2021-08-06。

  • 错误修复:即使某些脚本代码(如 'cu-Cyrs')在 CLDR 中缺失,也能识别出现在 IANA 注册表中的脚本代码。

  • 将构建系统从 setuptools 切换到 poetry

在 PEP 660 得到更好的支持之前,使用 poetry install 而不是 pip install -e . 以可编辑模式安装包。

版本 3.2(2021年10月)

  • 支持 Python 3.6 至 3.10。

  • 添加了顶层函数 tag_is_valid(tag),用于确定字符串是否为有效的语言标签,而无需先解析它。

  • 添加了顶层函数 closest_supported_match(desired, supported),它与 closest_match 类似,但返回值更简单。它返回最接近匹配的语言标签,如果没有足够接近的匹配项,则返回 None。

  • 错误修复:许多有效但格式良好的语言代码似乎有效,如 'aaj' 或 'en-Latnx',因为正则表达式可以匹配子标签的前缀。现在,有效性正则表达式必须完全匹配。

  • 解决有效性边缘情况的错误修复

    • 完全为私人使用的语言标签(如 'x-private')是有效的。
    • 使用相同扩展两次的语言标签(如 'en-a-bbb-a-ccc')是无效的。
    • 使用相同变体两次的语言标签(如 'de-1901-1901')是无效的。
    • 使用两个 extlangs 的语言标签(如 'sgn-ase-bfi')是无效的。
  • 更新依赖项,以兼容 Python 3.10,包括将 language_data 中的 marisa-trie-m 返回到 marisa-trie

  • 在 bugfix 版本 3.2.1 中,纠正了解析器接受格式不良的语言标签的情况。

    • 所有子标签必须由 1 到 8 个 ASCII 字符的字母数字字符组成。
    • 具有连续两个扩展 'singleton' 的标签(如 en-a-b-ccc)应该被拒绝。

版本 3.1(2021年2月)

  • 添加了 Language.to_alpha3() 方法,用于获取根据 ISO 639-2 获取语言的三个字母代码。

  • 更新了类型注释,从 obiwan-style 更改为 mypy-style。

版本 3.0(2021年2月)

  • 将体积较大的数据,尤其是语言名称,移动到单独的language_data包中。在不需要数据的情况下,langcodes成为一个更小、纯Python的包,没有依赖项。

  • 当语言段超过4个字母时,语言代码不再被解析:现在Language.get('nonsense')将返回错误。

    (这比BCP 47的解析规则更为严格,但不存在有效的此类语言代码,也不应该存在。尝试解析5-8个字母的语言代码很可能是错误或试图编造代码。)

  • 添加了检查语言代码有效性的方法。

  • 添加了估计语言人口的方法。

  • 更新到CLDR 38.1,其中包括语言匹配的差异。

  • 在Python 3.6到3.9上进行了测试;不再在Python 3.5上测试。

版本2.2(2021年2月)

  • marisa-trie依赖项替换为marisa-trie-m,以实现与Python 3.9的兼容性。

版本2.1(2020年6月)

  • 添加了display_name方法,以更直观的方式获取描述语言代码的字符串,并将autonym方法改为使用它而不是language_name

  • 更新到CLDR v37。

  • 以前,一些尝试获取语言名称的操作会返回其语言代码,这可能是由于请求的语言名称是CLDR没有名称数据的情况。这是不幸的,因为名称和代码不应互换。

    现在我们退回到英语名称,因为所有IANA代码都存在。如果代码未知,我们返回类似于“未知语言[xx]”的字符串。

版本2.0(2020年4月)

版本2.0涉及一些可能破坏与1.4版本兼容性的重大更改,除了更新到Unicode CLDR数据版本36.1和2020年4月的IANA子标签注册表。

此版本需要Python 3.5或更高版本。

匹配分数被替换为距离

最初,两个不同语言代码之间匹配的好坏是以“匹配分数”来定义的,最高分为100。大约在2016年,Unicode开始用不同的度量来替换它,“匹配距离”,它定义得更加明确,但我们必须继续使用“匹配分数”。

截至langcodes版本2.0,“分数”函数(如Language.match_scoretag_match_scorebest_match)已被弃用。它们将继续使用大约从CLDR 27开始的已弃用的语言匹配表。

为了更好地衡量两个语言代码的接近程度,请使用Language.distancetag_distanceclosest_match

'region'重命名为'territory'

我们在这里始终与CLDR不一致。按照IANA数据库的例子,我们将'en-US'中的'US'这样的东西称为“地区代码”,但Unicode标准始终称之为“地区代码”。

在langcodes 2.0中,参数、字典键和名为region的属性已重命名为territory。我们尝试使用弃用警告支持一些常见情况,例如查找Language对象的region属性。

这种做法的一个好处是,当字典按'language'、'script'和'territory'键按字母顺序显示时,它们与语言代码中的顺序相同。

项目详情


下载文件

下载适用于您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分发

langcodes-3.4.1.tar.gz (190.8 kB 查看哈希值)

上传时间

构建分发

langcodes-3.4.1-py3-none-any.whl (182.4 kB 查看哈希值)

上传时间 Python 3

由以下提供支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面