Unix 类系统中的本地化

之前总结过 Linux 的引导过程,这次打算把本地化的实现方法和原理总结一下。由于本文是我结合多篇 wiki 所得,其中也包含了一部分个人推断,因此有何错漏之处,还望指正。

在 Unix 类系统中,本地化环境的支持取决于三个方面:

1、内核对各语言的 codepage 支持。

2、C 标准库和 perl 等基础库对各语言的 locale 支持。

3、软件本身的 NLS 功能。

具体说一下这三方面各自包含哪些内容。

首先是内核的 codepage 支持。codepage 即代码页,是一张说明了如何对特定语言进行编码的表。codepage 和字符编码是一一对应的关系,如 cp936 对应简体中文 GBK,cp950 对应繁体中文 Big5。因此,内核的 codepage 是上层本地化支持的基础。

然后是 C 标准库和 perl 等基础库的 locale 支持,即对特定语言的字符编码支持。只有 C 标准库和 perl 等基础库中有了对应字符编码的支持,才能通过 setlocale 函数正确编码特定语言。

最后是软件本身的 NLS(或 i18n 和 l10n)功能,NLS 表示 National Language Support 或 Native Language Support。以简体中文为例,可以把 NLS 粗略理解为我们常说的“汉化版”或“中文版”,也就是通过翻译,把软件源语言转换为目标语言的过程。NLS 可以根据系统的 locale 设置智能匹配显示语言,如果目标语言还没有对应的翻译,那么就以源语言显示。

虽然本地化支持取决于这三方面的综合作用,但是个人认为其中最重要的就是 C 标准库和 perl 等基础库的 locale 支持。因此,我打算具体说一下 C 标准库和 perl 等基础库中的 locale 支持。平常使用系统过程中,locale 支持是比较底层的部分,也是确保具有 NLS 的软件正确显示目标语言的基础。这里我将以 Linux 的 locale 为例进行说明,因为 Linux 的 locale 支持相对最全面。FreeBSD 虽然也有相对完整的 locale 支持,但是类别并没有 Linux 那么覆盖全面,而 OpenBSD 干脆只提供非常有限的 locale 支持。

Linux 的 locale 包含 14 个 LC_* 变量和一个 LANG 变量。这些变量的含义分别如下:

LANG:其他 LC_* 变量的默认值。

LC_ADDRESS:地址书写格式,例如国家/地区名称写在最前还是最后。

LC_ALL:强制覆盖其他 LC_* 变量指定的数值。如果设置了此变量,其他 LC_* 变量的设置都将无效。

LC_COLLATE:字符串(如文件名)的排序方式。

LC_CTYPE:字符的分类方式。决定了系统内有效的字符以及这些字符的分类,诸如什么是大写字母、小写字母、大小写转换、标点符号、可打印字符和其他的字符属性等方面。

LC_IDENTIFICATION:包含关于 locale 信息的元数据。

LC_MEASUREMENT:度量衡单位。

LC_MESSAGES:系统信息(如提示信息、错误信息、状态信息、标题、标签、按钮和菜单等)采用的语言。

LC_MONETARY:使用的货币名称、单位、符号。

LC_NAME:姓名的书写方式。

LC_NUMERIC:数字的书写方式。例如,有些国家使用逗号(,)作为千位分隔符,而有的国家使用点(.)作为千位分隔符。

LC_PAPER:默认纸张大小。

LC_RESPONSE:决定本地语言的应答方式(如“是”或“否”)。

LC_TELEPHONE:电话号码的书写方式。

LC_TIME:时间和日期的显示格式。

在这 15 个环境变量中,其优先级如下:LC_ALL > LC_* > LANG。也就是说,LC_ALL 是优先级最高的变量,而 LANG 是在其他 LC_* 变量未设置的情况下系统 locale 所采用的默认值。

这里举几个例子加以说明:

1、假如设置 LC_ALL=zh_CN.UTF-8,那么不管 LC_* 和 LANG 设置成什么值,它们都会强制服从 LC_ALL 的设置,成为 zh_CN.UTF-8。

2、假如设置 LANG=zh_CN.UTF-8,而其他的 LC_*=en_US.UTF-8,并且没有设置 LC_ALL 的话,那么系统的 locale 设置服从 LC_*=en_US.UTF-8

3、假如设置 LANG=zh_CN.UTF-8,而其他的 LC_* 和 LC_ALL 均未设置的话,系统会将 LC_* 设置成默认值,也就是 LANG 的值 zh_CN.UTF-8 。

4、假如设置 LANG=zh_CN.UTF-8LC_CTYPE=en_US.UTF-8,其他的 LC_* 和 LC_ALL 均未设置的话,那么系统的 locale 设定将是:LC_CTYPE=en_US.UTF-8,其余的 LC_COLLATE、LC_MESSAGES 等等均会采用默认值,也就是 LANG 的值。

5、假如什么也不做的话,也就是 LC_ALL、LANG 和 LC_* 均不指定特定值的话,系统将采用 POSIX 作为 lcoale,也就是 C locale。

通过设置不同的变量组合,即可让系统适应不同的本地化环境。locale 的变量值由三部分组成:语言_地域.字符集。有时候也会根据使用习惯而引入一个修正值,变量值变为:语言_地域.字符集@修正值。例如,zh_CN.UTF-8 表示以中华人民共和国的简体中文,采用 UTF-8 字符集来对字符进行编码。而 de_DE.UTF-8@euro 表示以德国的德文,采用 UTF-8 字符集并以符合欧洲书写习惯的方式来对字符进行编码。

这里我再说一下字符集和字符编码的区别。字符集,顾名思义,就是各种字符的集合,而字符编码则是对字符集进行编码的一套机制。例如,GB2312、GB18030、UTF-8 等是字符集,而 ANSI 和 Unicode 则是字符编码。

那么有时候看到的乱码和方框又是怎么回事呢?

乱码经常出现在 Linux 和 Windows 进行文件互通的时候。而乱码的原因也分为两种情况:一种是系统存在正确的字符集和字符编码,但是在对目标文字进行编码时采用了错误的字符集或字符编码。还有一种是系统中没有正确的字符集和字符编码,因此尝试使用默认字符集和字符编码对目标文字进行编码,导致了乱码。归根结底,其实都是因为采取的字符集和字符编码不正确。这种问题的解决方案有两种,一种是转换原文字的字符集和字符编码,与系统的字符集和字符编码保持一致。或者,修改系统的字符集和字符编码,和原文字保持一致。

而方框的形成原因是,系统存在正确的字符集和字符编码,对目标文字进行编码时也采用了正确的字符集或字符编码,但是系统默认字体的字体库中没有对应的文字,因此就采用了方框来代替。解决方案很简单,更换字体即可。

对于主流的 Linux 发行版和 FreeBSD,一般都集成了完整的 locale 和 NLS 支持,因此用户很轻松就可以切换本地化环境,本质就是更改系统的 locale 环境变量。

另外,某些软件和桌面环境具有单独的语言包。如果要使用本地化环境,除了修改 locale 之外,还要安装对应的语言包。

而对于 Ubuntu 这样的发行版,它的语言包要做得更完善一点。Ubuntu 的语言包不仅包含了软件的显示语言,还包含了本地化语言环境对应的字体、输入法等一系列软件包,非常方便。

差不多就是这样了,文章结构有点混乱,但是应该基本交代清楚了。