2011年4月25日星期一

从字符编码到本地化

    关于字符编码,其实我写过一篇blog。字符编码是一个很大的话题,我们挑选其中一部分来说。
    首先,我假定你会用python写程序。当然,只要你理解字符编码,C的原理也类似。你可以写一个print u'中文',然后,试图运行。你大概有一定的几率会得到一个UnicodeEncodeError。为什么?
    python中,u代表一个unicode字符串,这种字符串的编码代号叫做ucs-2。这种编码有几个特点,首先是每个字恒定占据16位。记得ascii编码么?这是另一种约定,每个字恒定占据8个位。因此一个ucs-2相当于两个ascii字,而且acsii和ucs-2无法兼容。所谓兼容,是指ucs-2的A,不转换是无法以acsii方式读取的。由于ascii的广泛运用,因此要猛然升级到ucs-2编码是有很大问题的。
    为了在兼容ascii的同时支持中文,有几个组织提出了几个不同的方案。第一个是中国大陆官方提出的gb2312标准,后续标准分别叫做gbk和gb18030。这个编码是一种变字节码。当第一个字节是普通ascii码的时候,表示这个ascii码本身的意思。而当第一个字节不是ascii码的时候,和后面一个字节合起来表示一个汉字。所以这种编码的平均字长度在8-16位之间,并且兼容ascii标准。
    另一个平行的标准是有台湾地区提出的big5系列标准。这系列标准的特性和gb系列非常类似,但是两个标准对于汉字的定义却大相径庭——big5定义的是繁体中文。
    最后一个,也是最重要的兼容编码是unicode组织的utf-8编码标准。这种编码使用三个字节表示一个字,所以平均字长在8-24位,是所有编码中最长的。然而额外的占用带来两个好处。一个是可恢复性编码。即编码在任何一个位置出错,或者不同步,都不会影响后续内容的表示。我们可以考虑一下,如果有100M的一个文本,使用gbk编码表示。我们上次关闭的时候在XXX字节的地方,然后在头部删除了一个字节。打开的时候,由于错了一位,因此我们对后续文本的解析都是错位的。如果运气不好,这个错误在很长的长度都无法得到纠正。而utf-8编码在每个字节都设立了验证高位,因此一但出现不同步,最多一个字就可以恢复正确的解释。更重要的好处是,utf-8是全区域的。通俗点说,就是可以在同一个文本流中同时使用简体,繁体和日文。这个特性对于多国交流非常重要。
    我们回到最初的问题,多数系统的终端是ascii编码设计的,因此无法支持ucs-2编码,所以python会尝试转换成某种ascii兼容编码格式。这个转换过程是透明的,然而,具体采用哪种编码取决于系统的LANG和LC_ALL环境变量。如果你的编码是zh_CN.UTF-8,那就可以正常显示。如果你的编码是C,那么就得到UnicodeEncodeError。如果你的编码是zh_CN.GBK,而你的终端却不是这个编码,就会出现乱码。
    为什么我们不做成透明的呢?因为,假如要做成透明转换,我们就必须知道你的系统终端支持什么编码格式。大多数linux支持所有编码,然而windows的终端只支持gbk编码。如果我们做成gbk编码固定下来,又无法支持多数linux下的默认utf-8编码的终端。我们知道很多事情最扯淡的地方在于,你要为了不是你的问题作出支持和让步。所以,python社区的结论是,不管windows那个变态玩意了。python默认根据LANG和LC_ALL选择默认编码转换格式,而windows下就需要自己手工做一点转换了。
    同样,我们知道,编程界提倡unicode化,提倡采用utf-8编码。然而如果一种语言只提供utf-8支持,做网页会被用户输入搞死,做文件处理会被各种奇怪的文件搞死。所以虽然我们提倡使用utf-8,我们仍旧不得不允许程序员使用各种编码,并且无法做到全透明化。当然,在一切设定都OK的情况下,汉语使用是透明的。
    然而,汉语的输入输出和处理的透明化就保证了程序中文本地化么?答案是非常悲观的,这根本不是万里长征的起点,而是万丈深渊的开始。本地化程序涉及的范围远远不止一个字符编码问题。例如,以英文而言,日期通常表为22/03/2011。然而中文就是2011年3月22日。这和翻译和中文处理都没关系。类似的问题还有很多,多的让人抓狂。例如文字的左右对齐,有些文字是右对齐的。还有更变态的例子,文字是从上到下,从右向左书写。(没错,就是古代中文)货币单位不同,日历不同。还有同一个文字在上下文中的意义不同,这阻碍了gettext的应用。有些术语的译法也没有统一结论,甚至经常出现一本外文翻译书最好配一本原文阅读才方便读懂的情况。
    对于这种纷繁复杂的情况,指望写中文程序如同写英文程序一样顺利是痴人说梦的事情。

没有评论: