gettext

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 月,
这一点很重要,保证了按照里面的例子可以得到正确的结果。

gettext.tar

updatedupdated2022-02-222022-02-22