字符及字符集处理字符以及字符集的处理,是 Linux 国际化与本地化的另一重要内容。在计算机的早期字符集中,每个字符仅使用 6 个、7 个或 8 个位 (bits),但这对于类似东方语系的来说是明显不够的,因此出现了双字节字符集甚至更多字节。在字符集编码上有着两个重要的概念,即内码与外码。内码是在计算机内存中使用的编码,而外码则是在计算机外部使用的编码,如存储和传输等。常用的宽字符集内码有 Unicode 和 ISO 10646 ( 也叫做 UCS,即 Universal Character Set)。Unicode 的最初设计是 16 位,而 ISO 10646 使用的是 31 位。经发展这两个标准现几乎没有差异,Unicode 标准对应于 ISO 10646 实现级别 3 ( 即支持所有的 UCS 组合字符 )。而我们常用的 UTF-8 即 UCS 变形格式 8,是一种兼容于 ASCII 编码和所有 POSIX 文件系统的可变长编码。
通常,程序要依靠一些分类函数来处理字母、数字及空白等字符,而这些函数是会受到当前 locale 中的 LC_CTYPE 值的影响的。在 ISO C 标准中描述了两种不同的字符处理方式,即是 char 类型和 wchar_t 的宽字符 ( 即 wc)。它们的分类函数分别被定义在头文件 ctype.h 和 wctype.h 中。对于多字节字符串 (mbs) 以及宽字符串 (wcs) 的处理函数则被定义在头文件 wchar.h 中。显然,宽字符分类函数更为通用,因为它允许字符集分类的扩展可超过它的可用值。而这在 POSIX 中有描述的字符集扩展已在 glibc 的程序 localedef 中实现。另外,glibc 考虑到 locale 对程序字符的影响,提供了独立于 locale 的更为通用的字符集处理函数及工具 iconv ( 见清单 7)。
清单 7. iconv 相关函数1
2
3
4
5
6
| #include <iconv.h>
iconv_t iconv_open(const char *tocode,
const char *fromcode);
int iconv_close(iconv_t cd);
size_t iconv(iconv_t cd, char **inbuf, size_t *inbytesleft,
char **outbuf, size_t *outbytesleft);
|
下面我们通过查看文件系统 /proc 中的内容,以此来观察 Linux 国际化与本地化机制在内存中的情况 ( 见 )。使用命令 cat 查看到的 /proc/self/maps 文件内容应与命令 locale 返回的系统当前区域环境值相一致 ( 即将 与 相比较 )。
清单 8. 当前进程的内存映射及访问权限1
2
3
4
5
6
7
8
9
10
11
12
13
14
| $ cat /proc/self/maps
...
085a2000-085c3000 rw-p 085a2000 00:00 0 [heap]
b7a90000-b7acf000 r--p 00000000 08:08 740190 /usr/lib/locale/zh_CN.utf8/LC_CTYPE
b7acf000-b7ad0000 r--p 00000000 08:08 729171 /usr/lib/locale/en_US.utf8/LC_NUMERIC
b7ad0000-b7ad1000 r--p 00000000 08:08 781364 /usr/lib/locale/en_US.utf8/LC_TIME
b7bbd000-b7dbd000 r--p 00000000 08:08 704987 /usr/lib/locale/locale-archive
b7dbd000-b7dbe000 rw-p b7dbd000 00:00 0
b7dbe000-b7f1a000 r-xp 00000000 08:08 852866 /lib/tls/i686/cmov/libc-2.9.so
...
b7f27000-b7f2e000 r--s 00000000 08:08 704989 /usr/lib/gconv/gconv-modules.cache
...
b7f32000-b7f4e000 r-xp 00000000 08:08 827406 /lib/ld-2.9.so
...
|
在 中我们看到了 locale-archive 文件,如上述这个文件等同于其他含 locale 相关数据的目录文件 ( 如 /usr/lib/locale/en_US.utf8/LC_TIME 等 )。文件 gconv-modules.cache 是由命令 iconvconfig 以文件 gconv-modules 生成的 iconv 配置缓存文件,但该文件并不影响 iconv 的使用。当 gconv-modules.cache 不存在时,iconv 将会尝试打开配置文件 gconv-modules。
简述 gettextGNU gettext 是为程序实现国际化与本地化而设计的,它作为 GNU 实现软件翻译项目的一个重要部分为程序开发者、翻译者甚至用户提供了一个可生成多语言信息的框架 ( 通过 gettext 提供的一些接口可使程序与系统的语言或区域环境相一致 )。由此 gettext 可帮助我们快速的完成程序或软件的国际化与本地化,而 gettext 工具集则帮助我们更好的管理和维护翻译文档,其具体包含了:
- 一组关于如何编写程序来支持信息分类的约定;
- 一个支持信息分类的目录以及文件的命名管理;
- 一个支持获取翻译信息的动态库文件;
- 一些用于管理翻译信息 ( 或已翻译文件 ) 的独立程序;
- 一个支持解析、创建翻译信息的库文件;
- 一个专为 Emacs 提供的模块,方便设置、获取时间戳。
图 2. 使用 gettext 工具集处理程序国际化与本地化图 2所示为使用 gettext 工具集处理程序国际化和本地化的流程。事实上,在 glibc 中提供了两组不同的接口来完成信息的翻译,但是它们都没被 POSIX 标准所接受。其中一组接口就是 gettext,而另一组接口则是 catgets。虽然 catgets 被定义进了 X/Open 标准中,但这是来着产业的决定,因此它可能并不合理。相比于 gettext 方案,catgets 的不足之处是函数 catgets 的第三个参数 ( 即消息 ID) 是唯一的,这将导致程序编写者及翻译者在字符信息管理和维护上的困难,因此建议使用 gettext。所示的是一些与 catgets 相关的函数。
清单 9. 与 catgets 相关的一些函数1
2
3
4
5
| #include <nl_types.h>
nl_catd catopen(const char *name, int flag);
int catclose(nl_catd catalog);
char *catgets(nl_catd catalog, int set_number,
int message_number, const char *message);
|
一些相关的函数在 Linux 实现国际化和本地化时的一个重要函数就是 setlocale(),我们可以在系统工具集 ( 如 date 命令,相关源码片段已在 清单 6中展示。命令 locale 及 localedef 均有使用此函数。) 或是一些软件 ( 如文本编辑软件 gedit ) 的源码中发现其存在的痕迹。
#include <locale.h>
char *setlocale(int CATEGORY, const char *LOCALE);
函数 setlocale 被用于设置或查询程序当前的 locale 值。参数 CATEGORY 一共有 13 个可用值 ( 如 LC_COLLATE 等 ),它们被定义在头文件 locale.h 中,分别表示从 0 至 12 的整数值。第二个参数 LOCALE 应为 locale 名称 ( 如 zh_CN.UTF-8 等 ),但是仍有两个特殊的值可使用 "" 和 NULL。如果参数 LOCALE 的值是 "",则函数返回系统当前的环境值 ( 即置程序的环境值与系统相一致 )。若 LOCALE 的值为 NULL,则仅返回程序当前 locale 的设置。程序在设置 locale 值之前,其环境值默认是 "C" 或 "POSIX"。对于国际化程序,函数 setlocale 总被调用,同时为了保持程序的通用性,我们一般把 LOCALE 置为 ""。另外,在 清单 6中的另两个函数均被定义在头文件 libintl.h 中。
#include <libintl.h>
char * textdomain (const char * domainname);
函数 textdomain 的作用是重设当前的 domain 值以供 gettext() 等函数使用,其参数 domainname 为期望设置的新的 domain 值。如果参数 domainname 为 "",那么函数将返回默认值 "messages",但是似乎没人愿意使用该值,因为那样会使程序间出现冲突以至混乱。若 domainname 值为 NULL,则返回程序当前的 domain 值 ( 在先前没有设置的情况下,仍会返回预定值 "messages")。另外需注意的是,dcgettext() 等自带有参数 domainname 的函数,将不受函数 textdomain 的影响 ( 除非有人把它们的参数 domainname 值设为 NULL)。
#include <libintl.h>
char * bindtextdomain (const char * domainname, const char * dirname);
char * bind_textdomain_codeset (const char * domainname,
const char * codeset);
函数 bindtextdomain 的作用是通过给定的 domain 查找路径,可用信息的路径被指定为:
dirname/locale/category/domainname.mo
dirname 即为参数 dirname,若参数 dirname 为 NULL,则函数返回程序当前路径值 ( 默认是 /usr/share/locale),若 dirname 值为 "",则返回值为空。locale 即为 locale 名称,category 是 locale 的分类,如 LC_MESSAGES 等。domainname 即为参数 domainname。函数 bind_textdomain_codeset 的功能与 bindtextdomain 相近,因此 glibc 在实现时采用了一个内部函数 set_binding_values 并通过对该函数输入参数的控制分别实现以上 2 个函数 ( 见 )。
清单 10. glibc 中实现函数 bindtextdomain1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| /* intl/bindtextdom.c */
static void
set_binding_values (domainname, dirnamep, codesetp)
const char *domainname;
const char **dirnamep;
const char **codesetp;
{ ... }
/* 函数 bindtextdomain */
char *
BINDTEXTDOMAIN (domainname, dirname)
const char *domainname;
const char *dirname;
{
set_binding_values (domainname, &dirname, NULL);
return (char *) dirname;
}
/* 函数 bind_textdomain_codeset */
char *
BIND_TEXTDOMAIN_CODESET (domainname, codeset)
const char *domainname;
const char *codeset;
{
set_binding_values (domainname, NULL, &codeset);
return (char *) codeset;
}
|
另外,还有一个可访问 locale 相关信息的重要函数 nl_langinfo,该函数定义于头文件 langinfo.h 中,其作用是通过给定的 item 返回与 locale 相关的信息。
清单 11. 函数 nl_langinfo1
2
3
4
5
6
7
8
9
10
| #include <langinfo.h>
char *nl_langinfo(nl_item item);
/* locale/nl_langinfo.c */
char *
nl_langinfo (item)
nl_item item;
{
return __nl_langinfo_l (item, _NL_CURRENT_LOCALE);
}
|
|