GNU软件本地化技术初探
最近在阅读coreutils下面的一些工具的源码,对其中采用的本地化(国际化)技术进行了一些学习,在这里简单的进行了整理,希望能起到抛砖引玉的作用.
这其中涉及到一些开发的技术,但重点在于介绍本地化技术,这些对有兴趣成为GNU软件翻译者(可能是非开发人员)也是有一定帮助的,当然如果您是软件开发者,那么GNU中采用的本地化技术对您也会有一定借鉴意义.
我们的目标很简单:实现Hello, World的国际化 --- 开发软件的人不必关必太多各国语言版本的问题,翻译人员不必关心开发的问题.
在这里,先引出一个概念MESSAGE DOMAIN, 它表现为一个.mo文件,包含有原文及翻译的集合的信息.
比如有一个叫zh_CN/LC_MESSAGES/testlocale.mo 的MESSAGE DOMAIN,它可能包含类似的信息
代码:
#简体中文翻译
原文: "Hello, World!"
译文: "你好, 世界!"
类似,可能还有一个ja/LC_MESSAGES/testlocale.mo
代码:
#日语
原文: "Hello, World!"
译文: "#$%^&)(*&!"
注:.mo文件是经编译的二进制信息
有了包括这样信息的文件,GNU软件就可能利用这个MESSAGE DOMAIN(.mo文件)实现软件的国际化.
但有个疑问 :
某个软件到底是采用哪个MESSAGE DOMAIN进行本地化呢?要知道在你的系统中可能存在成千上万个MESSAGE DOMAIN(每个软件都可能对应多种语言的多份.mo)
好了,现在介绍几个函数
代码:
#include <libintl.h>
char * bindtextdomain (const char * domainname, const char * dirname);
指定某个MESSAGE DOMAIN的位置, 其中domainname是它的名称(文件名去掉后缀),dirname 所在位置,这里有点注意的地方
MESSAGE DOMAIN文件的存放位置是这样的: dirname/语言类(zh_CN,ja)/LOCALE的分类(LC_MESSAGE LC_TIME等)/xxx.mo
我机器上的目录结构是这样的
/home/coder/testlocaledir/zh_CN/LC_MESSAGES/testlocale.mo
/home/coder/testlocaledir/ja/LC_MESSAGES/testlocale.mo
所以在调用这个函数时传入的dirname是/home/coder/testlocaledir
第二个函数
代码:
char * textdomain (const char * domainname);
让你的程序使用某一个domain(也可以在具体调用翻译函数时指定,如下文),这样配合前面的bindtextdomain函数,就能找到要到哪个dirname下去找.mo文件了.
但dirname下有 很多子目录 zh_CN ja fr .. 要采用哪个目录下哪个LOCALE分类下的文件呢?(这也是上面的第二个问题)
代码:
char * dcgettext (const char * domainname, const char * msgid,int category);
这个函数是翻译函数,通过传入原文msgid,返回经过翻译后的译文. 其中 domainname 如果是NULL则采用之前textdomain选定的MESSAGE DOMAIN, int category是指LOCALE分类,系统通过它来确定要翻译成哪种语言
category 可能为LC_MESSAGE LC_TIME等,它的意义是说通过得到本程序指定LOCALTE分类(int category)的当前值 (zh_CN, C, ..)来确定将原消息msgid翻译成哪种语言,这样,在程序调用dcgettext时,系统会先通过domainname确定dirname,然后通过程序中指定LOCALE分类(如LC_MESSAGE)的值如zh_CN.GB2312 确定下一及目录 zh_CN,再加上LC_MESSAGE这样就确定了.mo文件的位置, 在这个位置下的domainname.mo就是存有翻译信息的文件.(如果locale类为C或是没找到.mo,则不翻译)
dcgettext 同族中还有一个简单的
char * gettext (const char * msgid);
它的意思是使用之前textdomain调用时指定的domain, LOCALE类别使用LC_MESSAGE
程序中的LC_MESSAGE LC_TIME等的值是如何设定的呢?
代码:
#include <locale.h>
char *setlocale(int category, const char *locale);
其中 char *locale 是设定category对应的值,如zh_CN.GB2312 zh_CN.GBK等,可用的值可以通过 $locale -l 查看.
如果locale为"",则采用环境变量中的值.
说得比较乱,可能有些朋友还有些糊涂.看例子:
代码:
#include <locale.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <libintl.h>
#define TEXTDOMAIN "testlocale"
#define TEXTDOMAINDIR "/home/coder/progs/testlocaledir"
int
main(int argc, char *argv[])
{
setlocale(LC_ALL, "");
bindtextdomain(TEXTDOMAIN, TEXTDOMAINDIR);
textdomain(TEXTDOMAIN);
printf("%s/n",gettext("Hello, World!"));
return 0;
}
其中setlocale(LC_ALL, "");把LC_ALL(即所有的分类),设置为与当前环境变量中的locale值一至(具体的对应方式man 3 setlocale).
如果在程序运行前, EXPORT LC_ALL=zh_CN.GB2312 则在这句代码执行后程序中的locale 各项值(LC_MESSAGE LC_TIME ..)都被设为zh_CN.GB2312.
bindtextdomain一句是设定了 testlocale 这个MESSAGE DOMAIN的位置.
textdomain一句是说程序中使用名称为testlocale的DOMAIN.
gettext("Hello, World!") 通过LC_MESSAGE的值zh_CN.GB2312 确定.mo文件在 /home/coder/progs/testlocaledir/zh_CN/LC_MESSAGES/testlocale.mo
这样函数就返回了"世界,你好!"
这样的程序在设计时,设计者只需要调用这几个函数就不用关心翻译的细节了[可能过宏定义#define _(X) gettext(X),这样有翻译需要的地方只需要_("string")即可].
翻译者也不必去了解程序代码,只需要把需要翻译的字串提出来加上翻译后的字串形成.mo文件放到指定位置即可.
开始时我们提过.mo是编译后的二进制,它是怎么制作的呢?
有一个工具叫msgfmt,能把源文件.po 编译成目标文件 .mo ,如我们用的中文po 文件
$more testlocale.po
msgid "Hello, World!"
msgstr "你好, 世界!"
$msgfmt testlocale.po
把生成的 messages.mo mv 到 /home/code/progs/testlocaledir/zh_CN/LC_MESSAGES/testlocale.mo
这样全部工作就完成了.
代码:
coder@deb3:~/progs$ export LC_ALL=C
coder@deb3:~/progs$ ./testlocale
Hello, World!
coder@deb3:~/progs$ export LC_ALL=zh_CN.GB2312
coder@deb3:~/progs$ ./testlocale
你好, 世界!
有其它语种翻译需要时只需要制作po编译成mo放到指定位置即可.
(完)