|
|
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放到指定位置即可.
(完)
-----------------------------------------------------------------
我还有两个问题请教大家:
1.有没有现成的工具从源码中提出字串,让翻译者对这些字串翻译,然后自动编译成mo?
2.看到一些软件源码树中 po/zh_CN.po 内容中对应的翻译是乱码?为什么? |
|