Gettext 在 C 中的用法
1、原始文件
这里使用一个简单的 C 程序作为例子,注意这个文件是采用 utf-8 编码的,我故意选择了一个同时包含中 文和英文字符串的代码。
#include <stdio.h> char *gs = "Global string\n" int main (void) { printf ("Hello, world!\n"); printf ("中文字符串\n"); return 0; }
2、必要的修改
为了让这个程序能够用 gettext 进行翻译,需要作一些修改(红字部分)。
#include <stdio.h> #include <locale.h> #include <libintl.h> #define _(STR) gettext(STR) #define N_(STR) STR #define LOCALEDIR "/usr/share/locale" #define PACKAGE "fool" char *gs = N_("Global string\n"); int main (void) { setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); printf (_("Hello wrold!\n")); printf (_("中文字符串\n")); printf (_(gs)); return 0; }
LOCALEEDIR 指定程序要到什么地方查找 mo 文件。通常是/usr/share/local/, 这个目录下面包含
着诸如:en_US、zh_CN、zh_TW 的目录。这些目录以 LL_CC 的形式命名,其中 LL 是语言名,CC 是国家名。我们需要把 mo 文件放入 LL_CC 下的 LC_MESSAGES 目录下面。 PACKAGE 指定了程 序的包名,假如这个程序叫做 fool。当程序在某种语言(LANG=en_US)下运行时,就会在LOCALEDIR/LL_CC/LC_MESSAGES 下查找 fool.mo。如果找到了,程序的结果就会按照这个 mo
的内容输出,否则按照源代码中的字符串输出。定义_( )宏是一种惯常用法,减少了打字。
另外一个值得注意的地方是N_()这个宏,它仅仅是扩展为原来的字符串。为什么要定义这个宏,因为gs是一个全局的字符串,需要在编译期初始化,如果同样的用_()宏包括这个字符串,程序甚至不能通过编译(至少C语言是这样,C++或许有点不同,不过我不太了解)。这里N_()宏的作用仅仅是为了做个标记,告诉gettext,这个字符串需要翻译。它与_()宏的区别是,后者既是一个标记,又是一个函数调用。所有静态字符串和全局字符串,都可以用N_()宏翻译,而实际显示这个字符串时,还要再加上_()宏来调用gettext。
3、生成 po 文件的模板
xgettext --keyword=_ --keywork=N_ --from-code=utf-8 --output=fool.pot --package-name=fool --package-version=1.0 fool.c
我们在源代码中把 gettext() 定义为 _(),因此这里要手动指定关键字,也就是一个下划线(–keyword=_)。在默认情况下,xgettext 会假设源文件的编码为 ASCII,但是我们的文件中包含了一个中 文字符串,并且以 utf-8 编码,这时候需要手动指定文件的编码()。–output=fool.pot 指定了输出文件名 为 fool.pot,在默认的情况下是 message.po,采用 pot 的后缀表示这个一个 po 的 template。–package-name=fool 和–package-version=1.0 分别指定包名和版本,它们填充 fool.pot 中的相应字段。这条命令在当前目录下生成了 fool.pot 文件。
PS. xgettext 后面可以同时写上多个文件。或者是在一个单独的文件中列举所有的文件名,并用– files-from=FILE 选项指定该文件。
4、生成语言的 po 文件
源文件中既包含英文,又包含中文,我们可以同时生成两种语言的翻译。
4.1 英文翻译
a) 生成 po 文件
msginit --locale=en_US --output=en_US.po --input=fool.pot
命令执行时会提示你输入自己的邮件地址,最后生成 en_US.po 文件,你可以对比一下 en_US.po 跟 fool.pot 有什么异同。en_US.po 的内容如下:
# English translations for PACKAGE package. # Copyright (C) 2011 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # lyre <lyre@poetpalace.org>, 2011. # msgid "" msgstr "" "Project-Id-Version: fool 1.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-01-19 13:11+0800\n" "PO-Revision-Date: 2011-01-19 13:11+0800\n" "Last-Translator: lyre <lyre@poetpalace.org>\n" "Language-Team: English\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: fool.c:12 msgid "Global string\n" msgstr "Global string\n" #: fool.c:20 #, c-format msgid "Hello wrold!\n" msgstr "Hello wrold!\n" #: fool.c:21 #, c-format msgid "中文字符串\n" msgstr "中文字符串\n"
留意最后一部分,其中每一行 msgstr 是我们需要翻译的地方,原始文件是中英文混杂的,而我 们目前做的是英文翻译,注意到第一个字符串本身就是英文,我们可以简单的清空对应的 msgstr,让程 序使用默认的字符串。第二个字符串是需要翻译的。修改后的结果为:
#: fool.c:12 msgid "Global string\n" msgstr "" #: fool.c:20 #, c-format msgid "Hello wrold!\n" msgstr "" #: fool.c:21 #, c-format msgid "中文字符串\n" msgstr "chinese string\n"
PS. 有专门的程序可以编辑 po 文件,如 gtranslator
b) 把 po 编译成二进制的 mo 文件
msgfmt en_US.po --output=en_US.mo
c)把 mo 复制到指定位置
sudo cp en_US.mo /usr/share/locale/en_US/LC_MESSAGES/fool.mo
注意路径和文件名。
4.2 中文翻译
与英文是类似的
a) 生成 po 文件
msginit --locale=zh_CN.utf-8 --output=zh_CN.po --input=fool.pot
注意–locale=zh_CN.utf-8,中文除了要指定语言国家以外,还要指定编码。修改这个 po :
#: fool.c:12 msgid "Global string\n" msgstr "全局字符串" #: fool.c:20 #, c-format msgid "Hello wrold!\n" msgstr "你好世界" #: fool.c:21 #, c-format msgid "中文字符串\n" msgstr ""
b) 把 po 编译成二进制的 mo 文件
msgfmt zh_CN.po –output=zh_CN.mo
c)把 mo 复制到指定位置
sudo cp zh_CN.mo /usr/share/locale/zh_CN/LC_MESSAGES/fool.mo
注意路径和文件名。
5.编译程序并测试
gcc fool.c -Wall -o fool
lyre@linux-4179e1:~/Desktop/sample> LANG=en_US ./fool
Hello, world!
chinese string
#注意,这个地方指定了 utf-8 的编码
lyre@linux-4179e1:~/Desktop/sample> LANG=zh_CN.utf-8 ./fool
你好,世界!
中文字符串
#我们没有翻译法语,所以是按照原样输出的。
lyre@linux-4179e1:~/Desktop/sample> LANG=fr_FR ./fool
Hello, world!
中文字符串
6、更新 po
有时候,我们会修改源文件中的字符串,这个时候需要更新 po 和 mo。 比如,我们把源代码中的”Hello, world!”修改为”We’ve repalaced this string\n””。
重新生成 po 模板:
xgettext --keyword=_ --from-code=utf-8 --output=fool.pot --package-name=fool --package-version=1.0 fool.c
更新 po 文件
msgmerge --update en_US.po fool.pot msgmerge --update zh_CN.po fool.pot
剩下的内容就是重新翻译,生成 mo 并安装。
7、现实使用
gettext 一般是配合 autotool 一起使用的,使用起来没有那么繁琐。但是学习 autotool 需要一定的时
间,目前网上很多的教程都是过时的,而 autotool 的 manual 并不适合用于学习。我能找到的最好的 autotool 的教程来自 http://www.lrde.epita.fr/~adl/autotools.html,介绍autoconf、automake、libtool 以及 gettext 的使用,其内容比较新,上一次的更新时间在 2010 年 5 月,
这一点很重要,保证了按照里面的例子可以得到正确的结果。