(PS. xedit 还有点问题,所以用这个方式用 xedit 读取档案可能会不正常)
六、撰写 I18N 的程式:
在此我将我的一些心得与大家分享。由於这个问题牵涉的层面很广,而我只有针对部分 子题稍微摸索一下,因此本文的目的不在於成为一个「完整」的文件说明,也许做为入 门导引来得好些,希望能对 I18N 程式写作有兴趣的朋友提供一个方向。有兴趣的朋友 同时也可以参考 info libc 的 Locale 章节,以及
ftp.nctu.edu.tw:
/documents/FAQ/comp/answers/internationalization/programming-faq.gz
(感谢 stevel 兄提供)
□头有更完整的说明。
除此之外,在这□我也会参杂一些我个人的理念,不一定是对的,仅供参考,也请各位 多多给予我批评与指教。
第一步: setlocale (详见 man setlocale 与其他相关 man page) 程式的第一步必须要设定 locale, 而一般的写法是 locale 资料是经由环境变数取 得,而不要写死在程式□头,例如:
#include
main()
{
setlocale(LC_ALL, "");
.....
}
或分别设定:
setlocale(LC_CTYPE, "");
setlocale(LC_MESSAGES, "");
.....
我个人的建议是,在 setlocale() 时只要设我们程式中需要的项目即可,而不要设
LC_ALL, 原因是在某些 locale 下 (如我们的 zh_TW.Big5), 并非所有的项目都能正确 运作。我想对大部分的程式而言,设好 LC_CTYPE 与 LC_MESSAGES 就差不多了,故以
下我针对这两个做说明。
wcs. vs. mbs. (详见 man mbstowcs 与相关 man page)
"wcs" 是 "wide-chararater string" 的缩写,而 "mbs" 是 "multi-byte string" 的
缩写,二者分别代表字串的表现方式。所谓的 multi-byte 是指数个 char 组成一个字 (如全形字或中文字是由两个 char 组成),而 wide-char 是指一个 wchar_t type 就
是一个字, 而 sizeof(wchar_t) 的大小与系统有关,一般而言是 4 bytes。一般我们 可以直接看、输出输入等都是 multi-byte, 如:
char *str = "这是一个句子: abcd";
但我们会建议在程式内部,用 mbstowcs() 将它转成 wchar_t 来统一处理,这个转换 其实是根据 locale 中的 LC_CTYPE 的机制,它定义了 multi-byte 与 wide- char 值 二者间的对应关系。做这样转换的好处是,您不用担心全形、半形的问题,因为一个 wchar_t 矩阵元就是一个字。 wchar_t 有一组与 string.h 中相对应的字串处理函式 (目前在 Linux 中可能还没有 man page 说明),就定义在 wchar.h 中,让我们可以如同处理 (char *) 那样地处理 (wchar_t *), 其部分的对应关系如下,其他的可以直接看 wchar.h 的内容:
wcscpy() <====> strcpy()
wcsncpy() <====> strncpy()
wcslen() <====> strlen()
wcsdup() <====> strdup()
wcscmp() <====> strcmp()
wcsncmp() <====> strncmp()
........................................
由於 mbs 码与 wcs 码的对应关系是由该 locale 的 LC_CTYPE 来决定的,也就是不同 的 locale 写法其对应关系可能会不一样。就我们的 glibc2, zh_TW.Big5 locale 而 言,由 mbs 转成的 wcs 即为 unicode (有关 unicode 的资讯可以在 http://www.unicode.org/ 中找到),但不能保证在其他的系统或环境下也是如此。故 最保险的做法,是将字串储存成 multi-byte, 然後在 run-time 时才用 mbstowcs() 转成 wide-char 来运作。
讯息输出 (详见 info gettext):
一般我们程式的讯息输出,是经由 stdio.h □头的函式,直接输出到 stdout 或 stderr, 而输出的内容是直接写死在程式码中。这样的程式要做多国语文化会造成困 扰,因为我们必须要修改原始码,将所有的讯息字串翻译成另一种语文。因此,我们必 须透过 locale 的 LC_MESSAGES 来处理讯息输出。其原理很简单,就是将程式中的所
有讯息抽离出来,为每一个locale 分别做好一个讯息档,当程式要输出讯息时,则透 过 libc 的函式依目前的 locale 去正确的讯息档中抓取讯息。
在此我用 GNU gettext 为例,简单说明其原理。在 /usr/share/locale 中,□头有各 种 locale 的资料目录。而每个目录下,都会有一LC_MESSAGES 的目录,而这些目 录就是用来放各别程式的讯息档。例如:
/usr/share/locale/ja/LC_MESSAGES/prog.mo (日文)
/usr/share/locale/zh_TW.Big5/LC_MESSAGES/prog.mo (Big5)
其中在 ja/ 目录下的 prog.mo 就是 prog 这个程式的日文讯息,而 zh_TW.Big5/ 下
的 prog.mo 就是 prog 这个程式的中文讯息。假设在还没加入 LC_MESSAGES 支援之
前, prog.c 长得像这样:
#include
main()
{
printf("This is a test string.\n");
}
现在我们要用 gettext 来加入支援,则程式只要改成:
#include
#include
#define _(STRING) gettext(STRING)
#define PACKAGE "prog"
main()
{
setlocale(LC_MESSAGES, "");
textdomain(PACKAGE);
/* 这□就是指定用
/usr/share/locale/$LOC/LC_MESSAGES/prog.mo
作为讯息档。其中 $LOC 是在 setlocale 中设定的 */
printf(_("This is a test string.\n"));
/* 使用 gettext 来抓出讯息,再交给 printf 来印 */
}
如果在指定的 locale 下找不到 prog.mo 档,则程式就直接以原英文讯息印出。因
此,加入 LC_MESSAGES 的支援,原 source code 修改并不多,其实相当方便。
比较麻烦的是各 locale 下的讯息档制作,而这些步骤可以经由 GNU gettext 套件很
容易地达成,其步骤简述如下 (详见 info gettext):
xgettext editor msgfmt (install)
source code --> .pot --> .pox --> .gmo --> .mo -->
(节录自 Platin.bbs@csie.nctu.edu.tw 的文章:
[REF] 关於 gettext (一、简介))
使用 xgettext 产生 .pot 档:
xgettext -a -o prog.pot prog.c
而 prog.pot 档的内容如下:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR Free Software Foundation, Inc.
# FIRST AUTHOR , YEAR.
#
#: prog.c:8
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 1999-02-28 19:18+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"
#: prog.c:10
msgid "This is a test string.\n"
msgstr ""
各位可以注意到倒数两行, msgid 就是原来 source □头的英文讯息,而我 们可以直
接在 msgstr 中将原讯息翻译成中文。所以,接下来的工作其实就 是翻译,我们可以
用任意的编辑器编辑这个档案,并将翻译好的档案存成 prog.pox 档。
将 prog.pox 编译成 prog.gmo:
msgfmt -o prog.gmo prog.pox
其中 prog.gmo。就是我们要的讯息档,等到我们把它安装到
/usr/share/locale/..../LC_MESSAGES/