Tag: 国际化

Perl: Locale::gettext – message handling functions

Posted by – December 27, 2009

Perl i18n:
http://search.cpan.org/~pvandry/gettext-1.05/gettext.pm

亲手打造 GNU/Linux 中文环境 (五) – 讯息国际化的解决方案 (gettext 简介)

Posted by – December 27, 2009

亲手打造 GNU/Linux 中文环境 (五) – 讯息国际化的解决方案 (gettext 简介)

参照: https://jinlab.com/blog/?attachment_id=97

转载自: http://cle.linux.org.tw/xcin/i18n/pc2000/p5/
感谢 http://babelfish.yahoo.com/ 提供翻译.
谢东翰 <thhsieh@linux.org.tw>,小虫 <platin@ms31.hinet.net>

1 前言

在前几期的文章里面,我们花了不少篇幅跟大家介绍了 GLIBC 提供的国际化支援以及 locale 的基本结构,我们也提到过 “LC_MESSAGES” 这个环境类别会决定程序用何种语言来显示讯息,在这一期里面,就让我们来更深入的研究一下讯息多国语言的解决方案,看看在一个多国语言环境之中,要如何让同一个程序在不同 locale 环境下可以自动显示不同语言的讯息。

1.1 讯息国际化的问题

有写过程序的人都知道,一般情况下程序的讯息都是写死在程序码里面的,例如说:

printf(“Hello World!!”); 亲手打造 GNU/Linux 中文环境 (五) – 讯息国际化的解决方案 (gettext 简介)

谢东翰 <thhsieh@linux.org.tw>,小虫 <platin@ms31.hinet.net>

1 前言

在前几期的文章里面,我们花了不少篇幅跟大家介绍了 GLIBC 提供的国际化支援以及 locale 的基本结构,我们也提到过 “LC_MESSAGES” 这个环境类别会决定程序用何种语言来显示讯息,在这一期里面,就让我们来更深入的研究一下讯息多国语言的解决方案,看看在一个多国语言环境之中,要如何让同一个程序在不同 locale 环境下可以自动显示不同语言的讯息。

1.1 讯息国际化的问题

有写过程序的人都知道,一般情况下程序的讯息都是写死在程序码里面的,例如说:

printf(“Hello World!!”);

这一段 C 程序码会印出 “Hello World!!” 这个字串,假如要把程序的讯息改成其他的语言的话,必须要修改原始码,然后再重新把程序编译一遍才行,这样的作法在国际化的系统里面是行不通的,因为一个国际化的系统应该要有能力随时依照使用者设定的语言环境 (就是 LC_MESSAGES 的内容) 来选取要输出讯息的版本,也就是说,用同一份程序码就可以显示不同语言的讯息,才能称之为国际化的系统。GLIBC 虽然提供了 setlocale() 的界面以及 LC_MESSAGES 的环境类别,不过却没有定义讯息多国语言的实作方式,所以要做到讯息的国际化,必须得要使用 GLIBC 以外的函式套件才行。

由于在 C 函式库中并没有标准的定义,所以实际上在 GNU/Linux 里面进行讯息国际化的解决方案并不只一种,常见方式可以归类成下面三种:

1. 利用 X 的资源定义资料库 (X resource database) 来作讯息国际化

这是 X 视窗系统提供的标准方法,许多 X 应用程式都是用这种方法来作讯息的国际化的,例如 Netscape 或 xedit 等程序就是属于这一类;X 允许您将程序使用到的讯息存放在 X 的资源资料库 (resource database) 里面,由于 X 应用程式在执行时会依照 locale 设定到 /usr/X11R6/lib/X11/{locale}/app-defaults/ 目录下去寻找对应到该 locale 的资源定义档案,所以只要把讯息字串定义在资源定义档案里面,把这些字串翻译好,然后再放到该 locale 的 app-defaults 目录底下,就可以依照 locale 设定显示不同语言的讯息了,读者可以在 /usr/X11R6/lib/X11/app-defaults/ 目录下找到一些未翻译的资料档案。假如您有加装一些中文套件的话,很可能还可以在 /usr/X11R6/lib/X11/zh_TW.Big5/app-defaults/ 目录下找到 Netscape 的繁体中文定义档,这里面以 labelString 结尾的资源项目就是 Netscape 程序用到的讯息,读者可以自己参考看看。

2. 利用 GNU gettext 套件

gettext 套件是 GNU 对讯息国际化提出的一个统一的解决方案,只要程序写作时注意几点小事情,对程序码做好适当的修改并链接到 gettext 程序库,编译一次以后,不须重新编译就能显示多国的讯息,让国际化 (i18n) 的工作可以更简单的达成;因为使用简单,支援的程序工具也多,gettext 已经渐渐成为 GNU/Linux 上面讯息国际化的标准作法,像 GNOME 程序、KDE 程序等等都是用 gettext 来作讯息的国际化。

3. 应用程式自行定义的方法

某些应用程式有自己的一套讯息国际化的架构,像 linuxconf 就自己定义了一套方式来作讯息的国际化;这些方法通常跟 gettext 的基本架构很类似,作法的流程也有很多可以类比之处,不过这都不是标准的作法,自己重新发展一套讯息资料的处理方法也是一件费时费力的工作,过去 GNU/Linux 上面的 gettext 还不成熟的时候或许有需要,不过在目前 gettext 已经被广泛接受使用的情况下,通常是没必要自己再重新发明一遍轮子罗。

上面所述的三个方法里面,X 提供标准出现的最早,但是因为在程序设计阶段就得自行将讯息分离处理,用起来并不方便,也缺乏一些工具程序的支援,因此迟迟未能受到大家广泛使用。在 GNU gettext 已经逐渐成熟以后,多数的程序也不再用这个方法。目前 gettext 套件已经算是被大家接受的标准作法了,虽然它现在的版本还只是 0.10 版,但是对于讯息国际化的支援 (LC_MESSAGES) 已经算是稳定,再加上几乎所有的 GNU 程序也都支援 gettext ,因此现在把它当作一个标准,来作 Linux 上讯息中文化的工作,应该是一个适当的时机,我们也建议所有有心让自己程序显示多国语言的程序设计者都能善加利用 gettext 套件,因此本文讨论的对象会专注在 gettext 这个套件的使用方式之上,X 或其他程序的作法我们就不详谈了。

接下来让我们先来看看 gettext 的基本原理吧。

2 gettext 的使用

2.1 gettext 的工作原理

图 1 是从 GNU 的 info page 抄出来的,它总括说明了 gettext 套件的工作原理,就请读者们参照后面的说明以及这张图上的流程,一起来看看 gettext 套件的工作流程吧。

Figure 1: gettext 的工作流程图
\resizebox*{11.7cm}{8.26cm}{\includegraphics{gettext-flow.eps}}

2.1.1 修改程序码

我们前面提过,使用 gettext 套件的时候原始码要作一些适当的修改,图 1 里面的 “Source Code” 指的就是还没有经过任何修改的程序码,”Marked Source Code” 指的就是修改后的程序码,那么,要作些甚么修改呢?

要让你的程序支援讯息国际化,程序设计师在写作程序的时候就必须把有需要国际化的讯息标出来,这个时候该注意的是应该只标示出需要被翻译成他国文字的讯息,比如 “%s %d” 这样的字串是不必被翻译的,也就不必标示,而 “Hello World!!” 这类的说明讯息就需要标示出来,标示的方法如下:

printf(gettext(“Hello World”));

懂 C 的朋友应该可以看出这个标示的意义,程序码这样修改以后,程序在印出讯息时,并不是直接取得讯息的字串,而是透过一个 gettext() 函式呼叫来获得字串;这个 gettext() 函式是在 libintl.h 里面定义的,它会在程序真正被执行时检查 locale 的情况 (事实上是在程序开始时先呼叫 setlocale() 函式来设定 LC_MESSAGES 类别,在这一步程序会去检查 LC_ALL 或 LC_MESSAGES 或 LANG 这环境变数,并做好 locale LC_MESSAGES 类别的设定),然后以 gettext() 的参数 ( “Hello World” ) 作为一个 id key,去该 locale 底下的讯息资料库里面抓取合乎这个 id key 的讯息,这样子同一个程序,就可以 show 出许多不同语言的讯息了。

从 “Source Code” -> “Marked Source Code” 这一段修改程序码的流程,是程序设计师的工作,也是程序讯息多国语言的过程之一,更详细的修改步骤我们在第 3.1 节里面会用实例跟大家介绍,且留待后面再讨论吧。

2.1.2 制作 PO 档案

改好程序码以后,接着应该就要把讯息从程序码里面抽出来,制作出 “讯息资料库” ,那讯息资料库是甚么?要怎么作呢?

GNU gettext 套件里面有一个程序叫 xgettext,可以用来处理 Marked Source Code,把原始码里面有标记的讯息抓出来,产生 PO 档 (Portable Object),xgettext 的基本使用如下:

xgettext [选项] INPUTFILE …

INPUTFILE 就是 “Marked Source Code”,假如给定的输入档名为 `-‘ 的话,则程序会从标准输入读入资料,有多个档案需要处理的话,在后面列入就可以了,这个程序常用的选项有:

-d NAME
用 NAME.po 做为输出档名 (预设是  messages.po)
-D DIRECTORY
增加 DIRECTORY 到档案搜寻列表中
-f FILE
从档案 FILE 里面读取输入档档名的列表。
-keyword=WORD
其他用来标示的关键字 (若没有指定 WORD , 表示使用预设的关键字 gettext)。
-o FILE
把产生的结果写到 FILE 这个档案去。
-p DIR
把输出的档案放到 DIR 这个目录底下。
-s
制造排序妥当的输出,并移除重覆的栏位。
-h
显示说明讯息。

2.1.3 对 PO 档进行翻译

xgettext 产生的 .po 档案是一个标准的文字档,用 more 或者一般的文字处理程序就可以观看它的内容,它里面包含了xgettext 从输入的原始码档案里面抓出的所有讯息,里面每一项讯息的格式大约如下:

#: hello.c:22
msgid “Hello!!”
msgstr “”

这里面 ‘#’ 开头的行是注解,msgid 栏位是未翻译前原先的讯息,msgstr 就是用来填翻译过的讯息的,我们可以把翻译过的讯息填进去,如下:

#: hello.c:22
msgid “Hello!!”
msgstr “您好!”

如何,翻译的动作非常简单吧;翻译 PO 讯息并不需要有特别的工具,只要用一般的文字编辑器就可以了,此外由于 PO 档案是单纯的文字档,所以跟使用的平台无关,您甚至可以把 GNU/Linux 程序的 PO 档案拿到 Microsoft Windows 操作系统里面用 Notepad 编辑、翻译好再拿回来 GNU/Linux 底下使用,这也就是 PO 的全名 “Portable Object” 的意思。经过使用者把每个项目里面的讯息一一翻译完成的 PO 档,只要档用 msgfmt 编译以后摆到适当的地方,就可以让翻译过的讯息发生作用,接着我们就来看看怎么让 PO 档案生效吧。

2.1.4 编译并安装讯息资料库

作好 PO 档以后,就可以用 “msgfmt” 这个程序来把 PO 档变成真正机器可以处理的 .mo 档 (Machine Object):

msgfmt [选项] filename.po …

msgfmt 常用的选项有:

-o FILE
把产生的结果写到 FILE 这个档案去,FILE 的档名一般会以 .mo 结尾,没有指定的话,结果会写到 messages 这个档案里面。
-v
列出输入档中异常的部份,多用几个选项 -v 的话会显示更多的讯息。
-h
显示说明讯息。

用 msgfmt 产生出来的这个 .mo 档才是真正的讯息资料库档案,它的名称一般是跟应用程式的名称一致,例如 gedit 的讯息就叫做 “gedit.mo”,在程序安装的时候,这些讯息资料档应该要被放到该讯息对应的 LOCALE 目录里面去,一般而言就是在 /usr/share/locale/*/LC_MESSAGES/ 底下 (这里的 * 指的就是 locale 的名称),例如繁体中文的 locale 为 zh_TW.Big5,所以繁体中文的讯息就应该放在 /usr/share/locale/zh_TW.Big5/LC_MESSAGES/ 底下,以 GB2312 编码的简体中文讯息就该摆在 /usr/share/locale/zh_CN.GB2312/LC_MESSAGES/ 底下。如此一来,各种不同语文翻译的讯息就能够很自然的分开摆放不相冲突,在应用程式执行的时候,gettext 也就可以依照 locale 设定的不同而去不同的目录底下抓到适当的讯息翻译。下面这行指令可以列出您目前系统上安装的程序的各种不同语言的讯息资料档,读者不妨自己看一看您的 GNU/Linux 上面有多少程序已经有中文翻译罗:

ls /usr/share/locale/*/LC_MESSAGES/

2.1.5 合并旧有的翻译讯息

在程序经历过几次改版以后,很自然的会加入新的讯息或修改旧的讯息,需要重新产生 PO 档案并进行翻译,不过这时候其实很多讯息都已经翻译过了,有旧版的 .po 讯息档,我们当然不希望把已经翻译过,而且还可以用的讯息再打一次,此时就可以用 “msgmerge” 这个程序来把旧的 .po 档里面的讯息合并到新的 PO 档案里面:

msgmerge [选项] def.po ref.po

常用的选项有:

-i
对输出结果做缩排处理。
-o FILE
把输出的结果写入 FILE 这个档案。
-S
使用严格统一标准的输出结果。
-h
显示说明讯息。

上面 def.po 是旧的翻译过的 PO 档,ref.po 是新的 PO 档案 (xgettext 做出来的),在 def.po 里面翻译过的讯息只要是仍然找得到相符合栏位的,都会被合并到 ref.po 里面去,在找不到完全一致的栏位时,程序还会用模糊逻辑的方法来得到类似的翻译,这些用模糊逻辑合并进去的栏位会被标上一个 “#, fuzzy” 的记号,根据我们实际的经验,这些模糊逻辑对出来的翻译常常是错的,读者在进行翻译的时候要特别重新检查这些栏位才行。

2.2 程序设计师与讯息翻译者的分工

在图 1 的流程里面,大家可以看到 xgettext 产生的档案叫做 .pot,这是 Portable Object Template 的意思,表示刚产生的档案里面只有原来的讯息,还没有翻译。接下来有一个 .pox 档,这是 GNU 建议对还没完全翻译好的PO 档的称呼,意思是施工中的 PO 档案,等到翻译完工定案了,再把它改成 .po 档;.pot、.pox、.po 这三种档案都是 PO 档,格式是一模一样的,在图里面作这种不同的称呼只是为了方便区分翻译的状况罢了,没有特殊的意义,读者也不见得一定要遵守这个命名规则的。

图中的 .gmo 其实指的就是用 msgfmt 编译出来的 .mo 档,GNU 建议用 .gmo 来称呼还没安装到定位的 .mo 档,表示这个档案是由 GNU gettext 套件产生的,这个命名法在许多 GNU 程序套件里面都可以看到;从程序码到翻译到安装的整个流程还可以用档案的附加档名慨括如下:

source code -> .pot -> .pox -> po -> .gmo -> .mo -> <run time>

在这整个流程里面,写作程序跟制作 .pot 档案是属于程序设计者的工作,然而 PO 档案的翻译却不一定要是程序设计者本人来作,事实上写程序的人也不可能懂得各国的语言,这些翻译跟维护 PO 档案的工作是属于 L10N 的范畴,应该由世界各地使用不同语言的人来担任翻译的工作才是。作这些翻译工作的人只要是单纯的使用者、懂英文、会使用文字编辑程序就可以了,并不需要懂得程序设计,因此人人都可以参与,gettext 套件提供了一个程序设计师跟讯息翻译者可以分工的架构,在图 1 里面深色的部份表示的就是属于讯息翻译者可以发挥的地方,外围浅色的部份则是程序设计师的工作,由程序设计者制作出 .pot 档案以后,有兴趣翻译的人就可以去抓回来翻译,翻好以后再寄回去给程序设计者包在套件里面发行,下一节我们先用一个例子实际让有心利用 gettext 写作多国语言程序的读者可以有个实作的机会,接着在第 4 节里面我们会从单纯使用者跟翻译者的角度来看 gettext 套件,请读者依照您个人的 “志愿” 选择有兴趣的部份来阅读吧。

3 一个实际的例子

上一节里面我们对整个 gettext 工作的流程做了简单的说明,接下来我们就来写一个简单的程序,希望能够利用这个完整的例子来让读者了解利用 gettext 套件写作 I18N 程序的详细方法。

3.1 使用 gettext 相关的函式

要利用 gettext 套件写作 I18N 程序,第一件事当然是要把 getext 套件安装好,现在所有的 GNU/Linux 安装套件里面应该都有内含这个套件了,因此安装应该不困难,读者只要从光盘或 FTP 站上面把 gettext 套件抓回来装好就可以,在此就不赘述了;除了 gettext 之外,你还必须有支援 I18N 的 LIBC,这一点读者可以查看一下 /usr/include/ 底下是不是有 locale.h 以及 libintl.h 这两个档案,有的话就表示你的 LIBC 是支援 I18N 的,这在目前各家 GNU/Linux 安装套件厂商使用的 glibc-2.1.x 里面是不会有问题的。要写一个讯息国际化的程序,需要用到底下这两个 C 函式:

char *setlocale(int category, const char * locale);

extern char *textdomain(const char *domainname);

上面的 setlocale() 是用来设定区域化环境的,用法在前几期已经详细介绍过,这边就不赘述。textdomain() 这个函式接受一个字串当作参量,用来指定这个程序所使用的 “文字领域” (textdomain),textdomain 会决定以后的 gettext() 函式要到那个档名的档案里面去找讯息资料,gettext() 会在程序执行时到 domainname.mo 这个档案里找字串的翻译,一般而言这个 domainname 都设成和程序的名称一样的以避免不同程序的讯息档案相互冲突。

3.2 范例程序

写作应用程式时要用 gettext 来作讯息多国语言的步骤其实非常简单,只有以下几点:

1. 在程序开头引入 libintl.h 跟 locale.h 两个标头档。
2. 在主程序开头时用 setlocale() 设定区域化环境。
3. 用 textdomain() 设定文字领域。
4. 把程序码中所有需要翻译的讯息都包在 gettext() 函式里面。

底下就是一个完整的程序,我们把它存在 gettext_test.c 里面,读者不妨先一边看程序码,一边观察上面几个步骤出现的位置:

/******************** Example program start here ********************/
/* gettext_test.c by Yuan-Chung Cheng */
#include <stdio.h>
#include <locale.h>
#include <libintl.h>
#define _(STRING) gettext(STRING)
#define PACKAGE “gettext_test”

char *program_name;

main(int argc, char *argv[])
{
program_name=argv[0];
setlocale(LC_ALL, “”);
textdomain(PACKAGE);

if(argc < 2){
printf(_(“%s: too few arguments\n”), program_name);
exit(0);
}
printf(_(“Hello, %s.\nBe a Happy Linuxer!!\n”), argv[1]);
}

/******************** Example program end here ********************/

这个程序的流程极简单,只是在一开始多了 setlocale、设定 textdomain 两个步骤而已,读者应该不难在这个短短的程序里面看出配合 gettext 修改的部份,值得特别注意的是,在这个程序里面我们把 _() 定义成 gettext(),然后再用 _() 来标示需要翻译的字串,这个简化的用法是 GNU 应用程式里面惯用的,可以让程序设计师少打很多 gettext(),也可以增加程序的可读性,建议大家可以学起来。

3.3 产生 .pot 档案

有了程序码以后,就可以利用 xgettext 程序生成 .pot 档案:

xgettext -keyword=_ -o gettext_test.pot gettext_test.c

前面的程序码里面用 _() 代替了 gettext() 函式,所以在用 xgettext 抽出讯息的时候要多加一个参数 “-keyword=_”,告诉程序用 _() 标示的字串也要当成待翻译的讯息抽出来,存到档案 gettext_test.pot 里面去,以下就是产生的 .pot 档案的内容:

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR Free Software Foundation, Inc.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid “”
msgstr “”
“Project-Id-Version: PACKAGE VERSION\n”
“POT-Creation-Date: 2000-06-19 05:51+0800\n”
“PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n”
“Last-Translator: FULL NAME <EMAIL@ADDRESS>\n”
“Language-Team: LANGUAGE <LL@li.org>\n”
“MIME-Version: 1.0\n”
“Content-Type: text/plain; charset=CHARSET\n”
“Content-Transfer-Encoding: ENCODING\n”

#: gettext_test.c:21
#, c-format
msgid “%s: too few arguments\n”
msgstr “”

#: gettext_test.c:25
#, c-format
msgid “”
“Hello, %s.\n”
“Be a Happy Linuxer!!\n”
msgstr “”

3.4 翻译 PO 档案并安装

得到 .pot 档案以后就可以进行翻译的工作了,这个程序只有两行讯息,当然轻轻松松三两下就翻译完毕了:

# SOME DESCRIPTIVE TITLE.
# Copyright (C) 2000 Free Software Foundation, Inc.
# Yuan-Chung Cheng <platin@ch.ntu.edu.tw>, 2000.
#
msgid “”
msgstr “”
“Project-Id-Version: hello-0.1\n”
“POT-Creation-Date: 2000-06-18 05:51+0800\n”
“PO-Revision-Date: 2000-06-19 08:30+0800\n”
“Last-Translator: Yuan-Chung Cheng <platin@ch.ntu.edu.tw>\n”
“Language-Team: Chinese <zh@li.org>\n”
“MIME-Version: 1.0\n”
“Content-Type: text/plain; charset=big5\n”
“Content-Transfer-Encoding: 8-bit\n”

#: gettext_test.c:21
#, c-format
msgid “%s: too few arguments\n”
msgstr “%s: 参数太少了\n”

#: gettext_test.c:25
#, c-format
msgid “”
“Hello, %s.\n”
“Be a Happy Linuxer!!\n”
msgstr “”
“您好, %s. \n”
“好好玩 Linux 喔!!\n”

上面便是完成翻译以后的 PO 档案,读者要注意除了翻译讯息以外,还要在一开头的标头部份把您的大名、E-Mail、翻译的编码等等栏位填一填才行。完成的 PO 档案可以用下面的指令编译成 .gmo 档案:

msgfmt -v -o gettext_test.gmo gettext_test.po

接着把完成的讯息资料库安装到定位,我们现在有的是繁体中文的翻译结果,所以安装在 zh_TW.Big5 目录底下:

install -m 644 gettext_test.gmo /usr/share/locale/zh_TW.Big5/LC_MESSAGES/gettext_test.mo

装好之后把原来的原始码用 gcc 编译起来,然后改一下 LANG 或 LC_MESSAGES 的设定,执行看看,是不是已经能够成功显示多国的讯息了呢?图 2 是在不同 LANG 设定下执行本程序的结果,读者可以看到当 locale 设定改变的时候,程序会自动显示不同语言的讯息出来。要加上其他语言的翻译的话,只要把 PO 档翻译好,用 msgfmt 编译成 .mo 档案并安装到该语言 locale 的 LC_MESSAGES 目录底下就可以了,并不需要重新编译程序。

Figure 2: 依照不同 locale 设定显示不同讯息
\resizebox*{6.63cm}{4.1cm}{\includegraphics{gettext_test.eps}}

在这一节里面,我们为了说明的方便,举的例子只是一个小小的档案而已,读者们在发展自己的程序时可能会遇到比较复杂的情况,例如程序码档案可能会不只一个,而且分布在不同目录底下,这个时候在产生 PO 档案的过程会比较复杂,不过只要善用 GNU 的 autoconf 套件与 gettextize 程序来帮您处理程序码,就可以省下不少功夫。限于篇幅我们只能为大家介绍最简单的情况,深入的应用就只好麻烦大家直接参考 gettext 的使用手册了:

http://www.gnu.org/manual/gettext/html_mono/gettext.html

4 从翻译者的观点来看 gettext 工具

让应用程式支援 gettext 是程序设计师的工作,不过要让程序在执行时能够显示出各种语言的讯息,就有赖讯息翻译者的努力了。参与翻译讯息的工作需要的知识跟写作 I18N 程序没什么相关性,也不需要对程序设计的原理有甚么了解,在翻译时更重要的是对该应用程式的 “使用经验”,知道怎么将程序的原文讯息适切的用本国的语言来表达,除此之外,就剩下要懂得编辑文字档而已了,所以任何人都可以参与翻译讯息的工作,事实上目前在 GNU/Linux 底下的中文讯息档很多都是由自愿的热心网友翻译出来的,许多参与的人也都完全不懂得设计程序呢。

4.1 PO 档案的翻译

4.1.1 取得 PO 档案

要进行 PO 档案翻译的第一件事就是取得还没翻好的 PO 档,PO 档一般含在应用程式的原始码里面,只要使用到 gettext 套件的应用程式都会有一个名为 po/ 的目录,这就是摆放 PO 档案的地方,通常在这底下可以找到一个结尾为 .pot 的未翻译档,以及许多各种语言的翻译结果,抓 POT 档案回去翻译之前,读者最好先检查看看以前是不是有人翻译过这个讯息了,以繁体中文为例,先找看看 po/ 目录底下是不是有叫做 zh_TW.Big5.po 这个档案,有的话,就表示已经有人翻译过这个讯息了。

在没人作过翻译的情况下,请注意一下 POT 档案里面标头部份显示的产生时间,记得不要抓太旧的档案回去,以免浪费时间在翻译过时的讯息,必要的时候写封 e-mail 请负责维护程序码的人更新一下 .pot 档案的版本,或者干脆自己把整个原始码抓回来,用 make 更新 .pot 档案,再重头翻译就是了。

发现档案已经有人翻译过的话,请尽量先跟前一位翻译的人取得联系再动手翻译;在 PO 档的标头部份都有翻译者的 e-mail 位址,写封信去询问他是否要继续维护这个讯息,甚至加入他一起来合作维护,我想读者也不希望您在辛苦翻译到一半的时候突然又冒出另外一个版本,结果浪费时间作白工了吧。假如确定前人已经不再继续维护这个讯息翻译以后,您就可以放心的以原来翻译的基础继续翻译的工作罗,不过还是要更新一下讯息的版本,保持翻译最新的程序讯息,此时请先拿到新版的 POT 档案,然后用下面指令拿到最新版本的 PO 档:

msgmerge zh_TW.Big5.po package_name.pot > package_name.po

上面指令中 zh_TW.Big5.po 是旧的 PO 档案,package_name.pot 表示新的 POT 档案,msgmerge 程序会把 zh_TW.Big5.po 里面还能用的翻译跟 package_name.pot 里面的讯息定义合并起来,输出到 package_name.po 里面去,这个新的 PO 档案里面会保留有旧的翻译再加上新的讯息定义,请拿这个档案来进行讯息翻译的工作。

4.1.2 翻译讯息

拿到最新的 PO 档案以后就可以开启您最爱用的文字编辑程序来进行翻译的工作罗。PO 档案是单纯的文字档,不管您用甚么程序来编辑它都没关系,前面已经说过,甚至用 Windows 的 Notepad 来翻译都没有关系,虽说如此,我们在网络上还是可以找到不少个可以帮助您翻译工作的程序,读者们到 http://freshmeat.net 底下用 gettext 为关键字找一下就可以找到好几个,请读者们自己试试看罗。小虫倒是建议有使用 Emacs 的朋友试试看 PO mode,在 gettext 套件里面可以找到一个 po-mode.el 的 emacs Lisp 程序档,参照里面的说明装好以后,您的 Emacs 在读到 PO 档的时候就会自动转到 PO mode 里面,PO mode 让您可以更简单的输入翻译出来的讯息,更另外提供了一些操作讯息项目的指令让您使用,像讯息的复制、删除等等都可以轻易的做到,读者可以自己在 PO mode 底下按 ‘h’ 看说明讯息来学习 PO mode 的使用方式,图 3 就是用 Emacs 的 PO mode 在编辑 PO 档案的情况。

Figure 3: Emacs 的 PO mode
\resizebox*{7.88cm}{7.1cm}{\includegraphics{gettext_emacs.eps}}

翻译时要记得填入标头资料,用 msgmerge 出来的档案进行翻译的时候要特别注意一下被标为 “#, fuzzy” 的项目,请务必检查一下这些项目,没问题的话要记得把 “#, fuzzy” 这一行删掉,否则接下来编译的时候会发生问题的。继承别人翻译成果的朋友请记得把标头资料改成自己,并且把前一位翻译者的资料放到一开始的版权信息里面,以示对前人翻译成果的尊重,并且也为这个档案的传承关系作一个见证。

在翻译时还有其他特别的事情需要注意,因为您在翻译的事实上是 C 语言的字串,所以一些讯息的写法得要依照 C 字串的规矩才行,例如字串中有引号 ‘”‘ 的时候要用打成 ‘\”‘ 才行,翻译出来的结果要保留原文讯息中的跳行字元跟 ‘%’ 带头的那些变数参考才行,例如下面的翻译:

#: locale/programs/ld-ctype.c:4131
#, c-format
msgid “%s: table for class \”%s\”: %lu bytes\n”
msgstr “%s: 类别 \”%s\” 表格: %lu 字节\n”

繁体中文翻译还会有一个额外的问题,因为许多中文字像许、功、摆… 等等的第二个字节是 ‘\’字元,这在 C 字串中会造成错误,因此在翻译遇到这些字的时候都要在后面多加一个 ‘\’ 才行,因为这个工作手动实在是太麻烦了,网络上有人写了一些小程序来处理这个问题,读者们可以在 http://i18n.linux.org.tw/ 找到 bg5cc 这个程序,用它来帮您加 ‘\’,我们在翻译的时候只要编辑 .pox 档案,不需要去担心这些中文字的问题,等到要编译成 .mo 档案的时候再来用 bg5cc 转一下:

bg5cc package_name.pox package_name.po

上面这个指令会帮您把 package_name.pox 里面需要多加 ‘\’ 的字找出来,把加上 ‘\’ 的结果存到 package_name.po 里面去,产生的这个 package_name.po 档案就可以放心交给 msgfmt 处理,不必担心中文字的问题罗。

4.1.3 测试翻译结果并送回给原作者

翻译好的 PO 档案可以用下面指令测试出是否正确无误:

msgfmt -v -o /dev/null package_name.po

例如:

[platin@bruceyu libc-zh_TW.Big5]$ msgfmt -v -o /dev/null libc-zh_TW.Big5.po 652 已翻译的讯息 , 496 未译的讯息 .

[platin@bruceyu libc-zh_TW.Big5]$

编译发生错误的话,msgfmt 会告诉您有问题的行号,您可以很快的找到错误所在;正确无误的 PO 档案就可以用第 3.4 节提到过的方法安装到 /usr/share/locale/… 底下,下次您开启这个程序的时候,就可以看到翻译的结果了。当然,独乐乐不如众乐乐,请记得把您翻译完成的 PO 档案用 e-mail 寄回去给程序的原作者,请他将这个讯息在程序下一次 release 的时候放进去,如此一来全世界所有使用这个程序的使用者都可以享受到您的贡献,也算是对 GNU/Linux 系统做出一番贡献喔。

由于程序是不停的在改版的,不时会有新的讯息加到程序里面,也时常会有修改原有讯息的情形,所以不要把翻译 PO 档案当成可以一次就搞定的工作,这些讯息翻译的持续维护是很重要的问题,讯息翻译者应该要时时检查自己负责的程序讯息是否有更新的情况,定时下载新的档案来更新、保持翻译结果的同步,这种持之以恒的持续维护也是很重要的喔。

4.2 翻译工作团体

翻译一个档案是很简单,可是要持续维护的工作就不容易了,因此世界各地都有热心人士出来组织翻译工作团体,希望可以简化 PO 档案的下载与更新工作以扩大参与的层面,更借着网站的协调机制避免有两人以上重复翻译同一份讯息,造成人力资源的浪费,还可以保存过去翻译的结果、持续 PO 档案翻译结果的维护工作。在台湾先是有李柏峰大哥 <pofeng.lee@ms7.url.com.tw> 出面担任 GNU 下游翻译组织 zh@li.org 的负责人,后有薛景中 <shyue@sonoma.com.tw>、陈更欣 <c17@linux.acer.com.tw> 等先进发起 “i18n 程序中文化计画” 来统合有兴趣参与翻译工作的力量,让有兴趣帮忙翻译的朋友可以透过这个网站取得最新的 PO 档案,也可以在这边找到翻译的技巧跟术语翻译的标准,图 4 就是这个网站的首页,其网址如下,有心进行翻译工作的朋友请务必进去看看:

http://i18n.linux.org.tw/

Figure 4: i18n 程序中文化计画首页
\resizebox*{10.16cm}{6.69cm}{\includegraphics{i18n_linux_org.eps}}

5 结语

看过上面的例子以后,读者是不是也觉得用 gettext 来让程序支援多国语言的讯息是很简单的一件事情呢?在当前软件国际化的风潮底下,让同一套程序码可以支援各种不同语言的讯息已经是写程序的人都必须面对的一件工作,GNU gettext 套件提供的架构可以让我们轻易的完成这件任务,有在 GNU/Linux 底下撰写应用程式的读者可要好好善加利用。

对单纯的使用者而言,如何用 gettext 来设计程序大概不是大多数人关心的问题,大家最在意的还是在使用程序时能不能看到讯息用自己熟悉的语言来显示出来吧?我们在网络上不时可以听到有人抱怨『GNU/Linux 底下怎么全都是英文,中文化那么差』,假如中文化指的是进去看不到中文讯息的话,那可就真的是冤枉了,其实缺少中文讯息这一点在技术上是最简单的问题,比起前几期讲的东西简单许多,而且还是任何人都可以一起来参与来改善的。可惜的是,跟其他国家的网友们参与的程度比起来,台湾朋友的热情似乎是少了一点,参与的人少,再加上国内的一些参与 GNU/Linux 市场的商业公司虽然都做过讯息翻译的工作,至今却还没能集中并持续的来维护中译的讯息。目前讯息中译的成果绝大多数都还是由网络上少数几个热心网友们贡献出来的,也因此目前 GNU/Linux 底下大多数的应用程式都还是没有中文讯息。我们在这边呼吁有心要改善这个问题的朋友或团体参考第 4.2 节里面的说明,一起来加入 i18n.linux.org.tw,只要看得懂英文、会编辑文字档案就可以帮 GNU/Linux 底下的应用程式加上中文的讯息,大家一起来,GNU/Linux 的明天会更好,您说是不是呢?

这一段 C 程序码会印出 “Hello World!!” 这个字串,假如要把程序的讯息改成其他的语言的话,必须要修改原始码,然后再重新把程序编译一遍才行,这样的作法在国际化的系统里面是行不通的,因为一个国际化的系统应该要有能力随时依照使用者设定的语言环境 (就是 LC_MESSAGES 的内容) 来选取要输出讯息的版本,也就是说,用同一份程序码就可以显示不同语言的讯息,才能称之为国际化的系统。GLIBC 虽然提供了 setlocale() 的界面以及 LC_MESSAGES 的环境类别,不过却没有定义讯息多国语言的实作方式,所以要做到讯息的国际化,必须得要使用 GLIBC 以外的函式套件才行。

由于在 C 函式库中并没有标准的定义,所以实际上在 GNU/Linux 里面进行讯息国际化的解决方案并不只一种,常见方式可以归类成下面三种:

1. 利用 X 的资源定义资料库 (X resource database) 来作讯息国际化

这是 X 视窗系统提供的标准方法,许多 X 应用程式都是用这种方法来作讯息的国际化的,例如 Netscape 或 xedit 等程序就是属于这一类;X 允许您将程序使用到的讯息存放在 X 的资源资料库 (resource database) 里面,由于 X 应用程式在执行时会依照 locale 设定到 /usr/X11R6/lib/X11/{locale}/app-defaults/ 目录下去寻找对应到该 locale 的资源定义档案,所以只要把讯息字串定义在资源定义档案里面,把这些字串翻译好,然后再放到该 locale 的 app-defaults 目录底下,就可以依照 locale 设定显示不同语言的讯息了,读者可以在 /usr/X11R6/lib/X11/app-defaults/ 目录下找到一些未翻译的资料档案。假如您有加装一些中文套件的话,很可能还可以在 /usr/X11R6/lib/X11/zh_TW.Big5/app-defaults/ 目录下找到 Netscape 的繁体中文定义档,这里面以 labelString 结尾的资源项目就是 Netscape 程序用到的讯息,读者可以自己参考看看。

2. 利用 GNU gettext 套件

gettext 套件是 GNU 对讯息国际化提出的一个统一的解决方案,只要程序写作时注意几点小事情,对程序码做好适当的修改并链接到 gettext 程序库,编译一次以后,不须重新编译就能显示多国的讯息,让国际化 (i18n) 的工作可以更简单的达成;因为使用简单,支援的程序工具也多,gettext 已经渐渐成为 GNU/Linux 上面讯息国际化的标准作法,像 GNOME 程序、KDE 程序等等都是用 gettext 来作讯息的国际化。

3. 应用程式自行定义的方法

某些应用程式有自己的一套讯息国际化的架构,像 linuxconf 就自己定义了一套方式来作讯息的国际化;这些方法通常跟 gettext 的基本架构很类似,作法的流程也有很多可以类比之处,不过这都不是标准的作法,自己重新发展一套讯息资料的处理方法也是一件费时费力的工作,过去 GNU/Linux 上面的 gettext 还不成熟的时候或许有需要,不过在目前 gettext 已经被广泛接受使用的情况下,通常是没必要自己再重新发明一遍轮子罗。

上面所述的三个方法里面,X 提供标准出现的最早,但是因为在程序设计阶段就得自行将讯息分离处理,用起来并不方便,也缺乏一些工具程序的支援,因此迟迟未能受到大家广泛使用。在 GNU gettext 已经逐渐成熟以后,多数的程序也不再用这个方法。目前 gettext 套件已经算是被大家接受的标准作法了,虽然它现在的版本还只是 0.10 版,但是对于讯息国际化的支援 (LC_MESSAGES) 已经算是稳定,再加上几乎所有的 GNU 程序也都支援 gettext ,因此现在把它当作一个标准,来作 Linux 上讯息中文化的工作,应该是一个适当的时机,我们也建议所有有心让自己程序显示多国语言的程序设计者都能善加利用 gettext 套件,因此本文讨论的对象会专注在 gettext 这个套件的使用方式之上,X 或其他程序的作法我们就不详谈了。

接下来让我们先来看看 gettext 的基本原理吧。

2 gettext 的使用

2.1 gettext 的工作原理

图 1 是从 GNU 的 info page 抄出来的,它总括说明了 gettext 套件的工作原理,就请读者们参照后面的说明以及这张图上的流程,一起来看看 gettext 套件的工作流程吧。

Figure 1: gettext 的工作流程图
\resizebox*{11.7cm}{8.26cm}{\includegraphics{gettext-flow.eps}}

2.1.1 修改程序码

我们前面提过,使用 gettext 套件的时候原始码要作一些适当的修改,图 1 里面的 “Source Code” 指的就是还没有经过任何修改的程序码,”Marked Source Code” 指的就是修改后的程序码,那么,要作些甚么修改呢?

要让你的程序支援讯息国际化,程序设计师在写作程序的时候就必须把有需要国际化的讯息标出来,这个时候该注意的是应该只标示出需要被翻译成他国文字的讯息,比如 “%s %d” 这样的字串是不必被翻译的,也就不必标示,而 “Hello World!!” 这类的说明讯息就需要标示出来,标示的方法如下:

printf(gettext(“Hello World”));

懂 C 的朋友应该可以看出这个标示的意义,程序码这样修改以后,程序在印出讯息时,并不是直接取得讯息的字串,而是透过一个 gettext() 函式呼叫来获得字串;这个 gettext() 函式是在 libintl.h 里面定义的,它会在程序真正被执行时检查 locale 的情况 (事实上是在程序开始时先呼叫 setlocale() 函式来设定 LC_MESSAGES 类别,在这一步程序会去检查 LC_ALL 或 LC_MESSAGES 或 LANG 这环境变数,并做好 locale LC_MESSAGES 类别的设定),然后以 gettext() 的参数 ( “Hello World” ) 作为一个 id key,去该 locale 底下的讯息资料库里面抓取合乎这个 id key 的讯息,这样子同一个程序,就可以 show 出许多不同语言的讯息了。

从 “Source Code” -> “Marked Source Code” 这一段修改程序码的流程,是程序设计师的工作,也是程序讯息多国语言的过程之一,更详细的修改步骤我们在第 3.1 节里面会用实例跟大家介绍,且留待后面再讨论吧。

2.1.2 制作 PO 档案

改好程序码以后,接着应该就要把讯息从程序码里面抽出来,制作出 “讯息资料库” ,那讯息资料库是甚么?要怎么作呢?

GNU gettext 套件里面有一个程序叫 xgettext,可以用来处理 Marked Source Code,把原始码里面有标记的讯息抓出来,产生 PO 档 (Portable Object),xgettext 的基本使用如下:

xgettext [选项] INPUTFILE …

INPUTFILE 就是 “Marked Source Code”,假如给定的输入档名为 `-‘ 的话,则程序会从标准输入读入资料,有多个档案需要处理的话,在后面列入就可以了,这个程序常用的选项有:

-d NAME
用 NAME.po 做为输出档名 (预设是  messages.po)
-D DIRECTORY
增加 DIRECTORY 到档案搜寻列表中
-f FILE
从档案 FILE 里面读取输入档档名的列表。
-keyword=WORD
其他用来标示的关键字 (若没有指定 WORD , 表示使用预设的关键字 gettext)。
-o FILE
把产生的结果写到 FILE 这个档案去。
-p DIR
把输出的档案放到 DIR 这个目录底下。
-s
制造排序妥当的输出,并移除重覆的栏位。
-h
显示说明讯息。

2.1.3 对 PO 档进行翻译

xgettext 产生的 .po 档案是一个标准的文字档,用 more 或者一般的文字处理程序就可以观看它的内容,它里面包含了xgettext 从输入的原始码档案里面抓出的所有讯息,里面每一项讯息的格式大约如下:

#: hello.c:22
msgid “Hello!!”
msgstr “”

这里面 ‘#’ 开头的行是注解,msgid 栏位是未翻译前原先的讯息,msgstr 就是用来填翻译过的讯息的,我们可以把翻译过的讯息填进去,如下:

#: hello.c:22
msgid “Hello!!”
msgstr “您好!”

如何,翻译的动作非常简单吧;翻译 PO 讯息并不需要有特别的工具,只要用一般的文字编辑器就可以了,此外由于 PO 档案是单纯的文字档,所以跟使用的平台无关,您甚至可以把 GNU/Linux 程序的 PO 档案拿到 Microsoft Windows 操作系统里面用 Notepad 编辑、翻译好再拿回来 GNU/Linux 底下使用,这也就是 PO 的全名 “Portable Object” 的意思。经过使用者把每个项目里面的讯息一一翻译完成的 PO 档,只要档用 msgfmt 编译以后摆到适当的地方,就可以让翻译过的讯息发生作用,接着我们就来看看怎么让 PO 档案生效吧。

2.1.4 编译并安装讯息资料库

作好 PO 档以后,就可以用 “msgfmt” 这个程序来把 PO 档变成真正机器可以处理的 .mo 档 (Machine Object):

msgfmt [选项] filename.po …

msgfmt 常用的选项有:

-o FILE
把产生的结果写到 FILE 这个档案去,FILE 的档名一般会以 .mo 结尾,没有指定的话,结果会写到 messages 这个档案里面。
-v
列出输入档中异常的部份,多用几个选项 -v 的话会显示更多的讯息。
-h
显示说明讯息。

用 msgfmt 产生出来的这个 .mo 档才是真正的讯息资料库档案,它的名称一般是跟应用程式的名称一致,例如 gedit 的讯息就叫做 “gedit.mo”,在程序安装的时候,这些讯息资料档应该要被放到该讯息对应的 LOCALE 目录里面去,一般而言就是在 /usr/share/locale/*/LC_MESSAGES/ 底下 (这里的 * 指的就是 locale 的名称),例如繁体中文的 locale 为 zh_TW.Big5,所以繁体中文的讯息就应该放在 /usr/share/locale/zh_TW.Big5/LC_MESSAGES/ 底下,以 GB2312 编码的简体中文讯息就该摆在 /usr/share/locale/zh_CN.GB2312/LC_MESSAGES/ 底下。如此一来,各种不同语文翻译的讯息就能够很自然的分开摆放不相冲突,在应用程式执行的时候,gettext 也就可以依照 locale 设定的不同而去不同的目录底下抓到适当的讯息翻译。下面这行指令可以列出您目前系统上安装的程序的各种不同语言的讯息资料档,读者不妨自己看一看您的 GNU/Linux 上面有多少程序已经有中文翻译罗:

ls /usr/share/locale/*/LC_MESSAGES/

2.1.5 合并旧有的翻译讯息

在程序经历过几次改版以后,很自然的会加入新的讯息或修改旧的讯息,需要重新产生 PO 档案并进行翻译,不过这时候其实很多讯息都已经翻译过了,有旧版的 .po 讯息档,我们当然不希望把已经翻译过,而且还可以用的讯息再打一次,此时就可以用 “msgmerge” 这个程序来把旧的 .po 档里面的讯息合并到新的 PO 档案里面:

msgmerge [选项] def.po ref.po

常用的选项有:

-i
对输出结果做缩排处理。
-o FILE
把输出的结果写入 FILE 这个档案。
-S
使用严格统一标准的输出结果。
-h
显示说明讯息。

上面 def.po 是旧的翻译过的 PO 档,ref.po 是新的 PO 档案 (xgettext 做出来的),在 def.po 里面翻译过的讯息只要是仍然找得到相符合栏位的,都会被合并到 ref.po 里面去,在找不到完全一致的栏位时,程序还会用模糊逻辑的方法来得到类似的翻译,这些用模糊逻辑合并进去的栏位会被标上一个 “#, fuzzy” 的记号,根据我们实际的经验,这些模糊逻辑对出来的翻译常常是错的,读者在进行翻译的时候要特别重新检查这些栏位才行。

2.2 程序设计师与讯息翻译者的分工

在图 1 的流程里面,大家可以看到 xgettext 产生的档案叫做 .pot,这是 Portable Object Template 的意思,表示刚产生的档案里面只有原来的讯息,还没有翻译。接下来有一个 .pox 档,这是 GNU 建议对还没完全翻译好的PO 档的称呼,意思是施工中的 PO 档案,等到翻译完工定案了,再把它改成 .po 档;.pot、.pox、.po 这三种档案都是 PO 档,格式是一模一样的,在图里面作这种不同的称呼只是为了方便区分翻译的状况罢了,没有特殊的意义,读者也不见得一定要遵守这个命名规则的。

图中的 .gmo 其实指的就是用 msgfmt 编译出来的 .mo 档,GNU 建议用 .gmo 来称呼还没安装到定位的 .mo 档,表示这个档案是由 GNU gettext 套件产生的,这个命名法在许多 GNU 程序套件里面都可以看到;从程序码到翻译到安装的整个流程还可以用档案的附加档名慨括如下:

source code -> .pot -> .pox -> po -> .gmo -> .mo -> <run time>

在这整个流程里面,写作程序跟制作 .pot 档案是属于程序设计者的工作,然而 PO 档案的翻译却不一定要是程序设计者本人来作,事实上写程序的人也不可能懂得各国的语言,这些翻译跟维护 PO 档案的工作是属于 L10N 的范畴,应该由世界各地使用不同语言的人来担任翻译的工作才是。作这些翻译工作的人只要是单纯的使用者、懂英文、会使用文字编辑程序就可以了,并不需要懂得程序设计,因此人人都可以参与,gettext 套件提供了一个程序设计师跟讯息翻译者可以分工的架构,在图 1 里面深色的部份表示的就是属于讯息翻译者可以发挥的地方,外围浅色的部份则是程序设计师的工作,由程序设计者制作出 .pot 档案以后,有兴趣翻译的人就可以去抓回来翻译,翻好以后再寄回去给程序设计者包在套件里面发行,下一节我们先用一个例子实际让有心利用 gettext 写作多国语言程序的读者可以有个实作的机会,接着在第 4 节里面我们会从单纯使用者跟翻译者的角度来看 gettext 套件,请读者依照您个人的 “志愿” 选择有兴趣的部份来阅读吧。

3 一个实际的例子

上一节里面我们对整个 gettext 工作的流程做了简单的说明,接下来我们就来写一个简单的程序,希望能够利用这个完整的例子来让读者了解利用 gettext 套件写作 I18N 程序的详细方法。

3.1 使用 gettext 相关的函式

要利用 gettext 套件写作 I18N 程序,第一件事当然是要把 getext 套件安装好,现在所有的 GNU/Linux 安装套件里面应该都有内含这个套件了,因此安装应该不困难,读者只要从光盘或 FTP 站上面把 gettext 套件抓回来装好就可以,在此就不赘述了;除了 gettext 之外,你还必须有支援 I18N 的 LIBC,这一点读者可以查看一下 /usr/include/ 底下是不是有 locale.h 以及 libintl.h 这两个档案,有的话就表示你的 LIBC 是支援 I18N 的,这在目前各家 GNU/Linux 安装套件厂商使用的 glibc-2.1.x 里面是不会有问题的。要写一个讯息国际化的程序,需要用到底下这两个 C 函式:

char *setlocale(int category, const char * locale);

extern char *textdomain(const char *domainname);

上面的 setlocale() 是用来设定区域化环境的,用法在前几期已经详细介绍过,这边就不赘述。textdomain() 这个函式接受一个字串当作参量,用来指定这个程序所使用的 “文字领域” (textdomain),textdomain 会决定以后的 gettext() 函式要到那个档名的档案里面去找讯息资料,gettext() 会在程序执行时到 domainname.mo 这个档案里找字串的翻译,一般而言这个 domainname 都设成和程序的名称一样的以避免不同程序的讯息档案相互冲突。

3.2 范例程序

写作应用程式时要用 gettext 来作讯息多国语言的步骤其实非常简单,只有以下几点:

1. 在程序开头引入 libintl.h 跟 locale.h 两个标头档。
2. 在主程序开头时用 setlocale() 设定区域化环境。
3. 用 textdomain() 设定文字领域。
4. 把程序码中所有需要翻译的讯息都包在 gettext() 函式里面。

底下就是一个完整的程序,我们把它存在 gettext_test.c 里面,读者不妨先一边看程序码,一边观察上面几个步骤出现的位置:

/******************** Example program start here ********************/
/* gettext_test.c by Yuan-Chung Cheng */
#include <stdio.h>
#include <locale.h>
#include <libintl.h>
#define _(STRING) gettext(STRING)
#define PACKAGE “gettext_test”

char *program_name;

main(int argc, char *argv[])
{
program_name=argv[0];
setlocale(LC_ALL, “”);
textdomain(PACKAGE);

if(argc < 2){
printf(_(“%s: too few arguments\n”), program_name);
exit(0);
}
printf(_(“Hello, %s.\nBe a Happy Linuxer!!\n”), argv[1]);
}

/******************** Example program end here ********************/

这个程序的流程极简单,只是在一开始多了 setlocale、设定 textdomain 两个步骤而已,读者应该不难在这个短短的程序里面看出配合 gettext 修改的部份,值得特别注意的是,在这个程序里面我们把 _() 定义成 gettext(),然后再用 _() 来标示需要翻译的字串,这个简化的用法是 GNU 应用程式里面惯用的,可以让程序设计师少打很多 gettext(),也可以增加程序的可读性,建议大家可以学起来。

3.3 产生 .pot 档案

有了程序码以后,就可以利用 xgettext 程序生成 .pot 档案:

xgettext -keyword=_ -o gettext_test.pot gettext_test.c

前面的程序码里面用 _() 代替了 gettext() 函式,所以在用 xgettext 抽出讯息的时候要多加一个参数 “-keyword=_”,告诉程序用 _() 标示的字串也要当成待翻译的讯息抽出来,存到档案 gettext_test.pot 里面去,以下就是产生的 .pot 档案的内容:

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR Free Software Foundation, Inc.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid “”
msgstr “”
“Project-Id-Version: PACKAGE VERSION\n”
“POT-Creation-Date: 2000-06-19 05:51+0800\n”
“PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n”
“Last-Translator: FULL NAME <EMAIL@ADDRESS>\n”
“Language-Team: LANGUAGE <LL@li.org>\n”
“MIME-Version: 1.0\n”
“Content-Type: text/plain; charset=CHARSET\n”
“Content-Transfer-Encoding: ENCODING\n”

#: gettext_test.c:21
#, c-format
msgid “%s: too few arguments\n”
msgstr “”

#: gettext_test.c:25
#, c-format
msgid “”
“Hello, %s.\n”
“Be a Happy Linuxer!!\n”
msgstr “”

3.4 翻译 PO 档案并安装

得到 .pot 档案以后就可以进行翻译的工作了,这个程序只有两行讯息,当然轻轻松松三两下就翻译完毕了:

# SOME DESCRIPTIVE TITLE.
# Copyright (C) 2000 Free Software Foundation, Inc.
# Yuan-Chung Cheng <platin@ch.ntu.edu.tw>, 2000.
#
msgid “”
msgstr “”
“Project-Id-Version: hello-0.1\n”
“POT-Creation-Date: 2000-06-18 05:51+0800\n”
“PO-Revision-Date: 2000-06-19 08:30+0800\n”
“Last-Translator: Yuan-Chung Cheng <platin@ch.ntu.edu.tw>\n”
“Language-Team: Chinese <zh@li.org>\n”
“MIME-Version: 1.0\n”
“Content-Type: text/plain; charset=big5\n”
“Content-Transfer-Encoding: 8-bit\n”

#: gettext_test.c:21
#, c-format
msgid “%s: too few arguments\n”
msgstr “%s: 参数太少了\n”

#: gettext_test.c:25
#, c-format
msgid “”
“Hello, %s.\n”
“Be a Happy Linuxer!!\n”
msgstr “”
“您好, %s. \n”
“好好玩 Linux 喔!!\n”

上面便是完成翻译以后的 PO 档案,读者要注意除了翻译讯息以外,还要在一开头的标头部份把您的大名、E-Mail、翻译的编码等等栏位填一填才行。完成的 PO 档案可以用下面的指令编译成 .gmo 档案:

msgfmt -v -o gettext_test.gmo gettext_test.po

接着把完成的讯息资料库安装到定位,我们现在有的是繁体中文的翻译结果,所以安装在 zh_TW.Big5 目录底下:

install -m 644 gettext_test.gmo /usr/share/locale/zh_TW.Big5/LC_MESSAGES/gettext_test.mo

装好之后把原来的原始码用 gcc 编译起来,然后改一下 LANG 或 LC_MESSAGES 的设定,执行看看,是不是已经能够成功显示多国的讯息了呢?图 2 是在不同 LANG 设定下执行本程序的结果,读者可以看到当 locale 设定改变的时候,程序会自动显示不同语言的讯息出来。要加上其他语言的翻译的话,只要把 PO 档翻译好,用 msgfmt 编译成 .mo 档案并安装到该语言 locale 的 LC_MESSAGES 目录底下就可以了,并不需要重新编译程序。

Figure 2: 依照不同 locale 设定显示不同讯息
\resizebox*{6.63cm}{4.1cm}{\includegraphics{gettext_test.eps}}

在这一节里面,我们为了说明的方便,举的例子只是一个小小的档案而已,读者们在发展自己的程序时可能会遇到比较复杂的情况,例如程序码档案可能会不只一个,而且分布在不同目录底下,这个时候在产生 PO 档案的过程会比较复杂,不过只要善用 GNU 的 autoconf 套件与 gettextize 程序来帮您处理程序码,就可以省下不少功夫。限于篇幅我们只能为大家介绍最简单的情况,深入的应用就只好麻烦大家直接参考 gettext 的使用手册了:

http://www.gnu.org/manual/gettext/html_mono/gettext.html

4 从翻译者的观点来看 gettext 工具

让应用程式支援 gettext 是程序设计师的工作,不过要让程序在执行时能够显示出各种语言的讯息,就有赖讯息翻译者的努力了。参与翻译讯息的工作需要的知识跟写作 I18N 程序没什么相关性,也不需要对程序设计的原理有甚么了解,在翻译时更重要的是对该应用程式的 “使用经验”,知道怎么将程序的原文讯息适切的用本国的语言来表达,除此之外,就剩下要懂得编辑文字档而已了,所以任何人都可以参与翻译讯息的工作,事实上目前在 GNU/Linux 底下的中文讯息档很多都是由自愿的热心网友翻译出来的,许多参与的人也都完全不懂得设计程序呢。

4.1 PO 档案的翻译

4.1.1 取得 PO 档案

要进行 PO 档案翻译的第一件事就是取得还没翻好的 PO 档,PO 档一般含在应用程式的原始码里面,只要使用到 gettext 套件的应用程式都会有一个名为 po/ 的目录,这就是摆放 PO 档案的地方,通常在这底下可以找到一个结尾为 .pot 的未翻译档,以及许多各种语言的翻译结果,抓 POT 档案回去翻译之前,读者最好先检查看看以前是不是有人翻译过这个讯息了,以繁体中文为例,先找看看 po/ 目录底下是不是有叫做 zh_TW.Big5.po 这个档案,有的话,就表示已经有人翻译过这个讯息了。

在没人作过翻译的情况下,请注意一下 POT 档案里面标头部份显示的产生时间,记得不要抓太旧的档案回去,以免浪费时间在翻译过时的讯息,必要的时候写封 e-mail 请负责维护程序码的人更新一下 .pot 档案的版本,或者干脆自己把整个原始码抓回来,用 make 更新 .pot 档案,再重头翻译就是了。

发现档案已经有人翻译过的话,请尽量先跟前一位翻译的人取得联系再动手翻译;在 PO 档的标头部份都有翻译者的 e-mail 位址,写封信去询问他是否要继续维护这个讯息,甚至加入他一起来合作维护,我想读者也不希望您在辛苦翻译到一半的时候突然又冒出另外一个版本,结果浪费时间作白工了吧。假如确定前人已经不再继续维护这个讯息翻译以后,您就可以放心的以原来翻译的基础继续翻译的工作罗,不过还是要更新一下讯息的版本,保持翻译最新的程序讯息,此时请先拿到新版的 POT 档案,然后用下面指令拿到最新版本的 PO 档:

msgmerge zh_TW.Big5.po package_name.pot > package_name.po

上面指令中 zh_TW.Big5.po 是旧的 PO 档案,package_name.pot 表示新的 POT 档案,msgmerge 程序会把 zh_TW.Big5.po 里面还能用的翻译跟 package_name.pot 里面的讯息定义合并起来,输出到 package_name.po 里面去,这个新的 PO 档案里面会保留有旧的翻译再加上新的讯息定义,请拿这个档案来进行讯息翻译的工作。

4.1.2 翻译讯息

拿到最新的 PO 档案以后就可以开启您最爱用的文字编辑程序来进行翻译的工作罗。PO 档案是单纯的文字档,不管您用甚么程序来编辑它都没关系,前面已经说过,甚至用 Windows 的 Notepad 来翻译都没有关系,虽说如此,我们在网络上还是可以找到不少个可以帮助您翻译工作的程序,读者们到 http://freshmeat.net 底下用 gettext 为关键字找一下就可以找到好几个,请读者们自己试试看罗。小虫倒是建议有使用 Emacs 的朋友试试看 PO mode,在 gettext 套件里面可以找到一个 po-mode.el 的 emacs Lisp 程序档,参照里面的说明装好以后,您的 Emacs 在读到 PO 档的时候就会自动转到 PO mode 里面,PO mode 让您可以更简单的输入翻译出来的讯息,更另外提供了一些操作讯息项目的指令让您使用,像讯息的复制、删除等等都可以轻易的做到,读者可以自己在 PO mode 底下按 ‘h’ 看说明讯息来学习 PO mode 的使用方式,图 3 就是用 Emacs 的 PO mode 在编辑 PO 档案的情况。

Figure 3: Emacs 的 PO mode
\resizebox*{7.88cm}{7.1cm}{\includegraphics{gettext_emacs.eps}}

翻译时要记得填入标头资料,用 msgmerge 出来的档案进行翻译的时候要特别注意一下被标为 “#, fuzzy” 的项目,请务必检查一下这些项目,没问题的话要记得把 “#, fuzzy” 这一行删掉,否则接下来编译的时候会发生问题的。继承别人翻译成果的朋友请记得把标头资料改成自己,并且把前一位翻译者的资料放到一开始的版权信息里面,以示对前人翻译成果的尊重,并且也为这个档案的传承关系作一个见证。

在翻译时还有其他特别的事情需要注意,因为您在翻译的事实上是 C 语言的字串,所以一些讯息的写法得要依照 C 字串的规矩才行,例如字串中有引号 ‘”‘ 的时候要用打成 ‘\”‘ 才行,翻译出来的结果要保留原文讯息中的跳行字元跟 ‘%’ 带头的那些变数参考才行,例如下面的翻译:

#: locale/programs/ld-ctype.c:4131
#, c-format
msgid “%s: table for class \”%s\”: %lu bytes\n”
msgstr “%s: 类别 \”%s\” 表格: %lu 字节\n”

繁体中文翻译还会有一个额外的问题,因为许多中文字像许、功、摆… 等等的第二个字节是 ‘\’字元,这在 C 字串中会造成错误,因此在翻译遇到这些字的时候都要在后面多加一个 ‘\’ 才行,因为这个工作手动实在是太麻烦了,网络上有人写了一些小程序来处理这个问题,读者们可以在 http://i18n.linux.org.tw/ 找到 bg5cc 这个程序,用它来帮您加 ‘\’,我们在翻译的时候只要编辑 .pox 档案,不需要去担心这些中文字的问题,等到要编译成 .mo 档案的时候再来用 bg5cc 转一下:

bg5cc package_name.pox package_name.po

上面这个指令会帮您把 package_name.pox 里面需要多加 ‘\’ 的字找出来,把加上 ‘\’ 的结果存到 package_name.po 里面去,产生的这个 package_name.po 档案就可以放心交给 msgfmt 处理,不必担心中文字的问题罗。

4.1.3 测试翻译结果并送回给原作者

翻译好的 PO 档案可以用下面指令测试出是否正确无误:

msgfmt -v -o /dev/null package_name.po

例如:

[platin@bruceyu libc-zh_TW.Big5]$ msgfmt -v -o /dev/null libc-zh_TW.Big5.po 652 已翻译的讯息 , 496 未译的讯息 .

[platin@bruceyu libc-zh_TW.Big5]$

编译发生错误的话,msgfmt 会告诉您有问题的行号,您可以很快的找到错误所在;正确无误的 PO 档案就可以用第 3.4 节提到过的方法安装到 /usr/share/locale/… 底下,下次您开启这个程序的时候,就可以看到翻译的结果了。当然,独乐乐不如众乐乐,请记得把您翻译完成的 PO 档案用 e-mail 寄回去给程序的原作者,请他将这个讯息在程序下一次 release 的时候放进去,如此一来全世界所有使用这个程序的使用者都可以享受到您的贡献,也算是对 GNU/Linux 系统做出一番贡献喔。

由于程序是不停的在改版的,不时会有新的讯息加到程序里面,也时常会有修改原有讯息的情形,所以不要把翻译 PO 档案当成可以一次就搞定的工作,这些讯息翻译的持续维护是很重要的问题,讯息翻译者应该要时时检查自己负责的程序讯息是否有更新的情况,定时下载新的档案来更新、保持翻译结果的同步,这种持之以恒的持续维护也是很重要的喔。

4.2 翻译工作团体

翻译一个档案是很简单,可是要持续维护的工作就不容易了,因此世界各地都有热心人士出来组织翻译工作团体,希望可以简化 PO 档案的下载与更新工作以扩大参与的层面,更借着网站的协调机制避免有两人以上重复翻译同一份讯息,造成人力资源的浪费,还可以保存过去翻译的结果、持续 PO 档案翻译结果的维护工作。在台湾先是有李柏峰大哥 <pofeng.lee@ms7.url.com.tw> 出面担任 GNU 下游翻译组织 zh@li.org 的负责人,后有薛景中 <shyue@sonoma.com.tw>、陈更欣 <c17@linux.acer.com.tw> 等先进发起 “i18n 程序中文化计画” 来统合有兴趣参与翻译工作的力量,让有兴趣帮忙翻译的朋友可以透过这个网站取得最新的 PO 档案,也可以在这边找到翻译的技巧跟术语翻译的标准,图 4 就是这个网站的首页,其网址如下,有心进行翻译工作的朋友请务必进去看看:

http://i18n.linux.org.tw/

Figure 4: i18n 程序中文化计画首页
\resizebox*{10.16cm}{6.69cm}{\includegraphics{i18n_linux_org.eps}}

5 结语

看过上面的例子以后,读者是不是也觉得用 gettext 来让程序支援多国语言的讯息是很简单的一件事情呢?在当前软件国际化的风潮底下,让同一套程序码可以支援各种不同语言的讯息已经是写程序的人都必须面对的一件工作,GNU gettext 套件提供的架构可以让我们轻易的完成这件任务,有在 GNU/Linux 底下撰写应用程式的读者可要好好善加利用。
对单纯的使用者而言,如何用 gettext 来设计程序大概不是大多数人关心的问题,大家最在意的还是在使用程序时能不能看到讯息用自己熟悉的语言来显示出来吧?我们在网络上不时可以听到有人抱怨『GNU/Linux 底下怎么全都是英文,中文化那么差』,假如中文化指的是进去看不到中文讯息的话,那可就真的是冤枉了,其实缺少中文讯息这一点在技术上是最简单的问题,比起前几期讲的东西简单许多,而且还是任何人都可以一起来参与来改善的。可惜的是,跟其他国家的网友们参与的程度比起来,台湾朋友的热情似乎是少了一点,参与的人少,再加上国内的一些参与 GNU/Linux 市场的商业公司虽然都做过讯息翻译的工作,至今却还没能集中并持续的来维护中译的讯息。目前讯息中译的成果绝大多数都还是由网络上少数几个热心网友们贡献出来的,也因此目前 GNU/Linux 底下大多数的应用程式都还是没有中文讯息。我们在这边呼吁有心要改善这个问题的朋友或团体参考第 5.2 节里面的说明,一起来加入 i18n.linux.org.tw,只要看得懂英文、会编辑文字档案就可以帮 GNU/Linux 底下的应用程式加上中文的讯息,大家一起来,GNU/Linux 的明天会更好,您说是不是呢?