原文地址:Advanced GTK Techniques。
这篇教程中你将学会:
- 使用
Gettext
完成源代码中的翻译工作;
- 使用
Intltool
完成其它文件的翻译工作。
这篇文章是《如何开始一个真正的 GTK 项目》的一部分,如果你不想回看之前的章节,可以直接下载教学示例程序
app-skeleton3
。你也可以从头开始。
这是我们在开始正式的代码工作前又一个基础准备工作,这将实现程序在不同语言环境下的本地化。你可能觉得翻译工作一般都是在应用成型后才展开,但是在编写程序时同步开始翻译往往更利于保持掌控翻译的质量。
你是否需要为了这项工作学习 30 门外语呢?当然不用。在真实的场合中,人们用英语编写程序(准确地说是 C locale
),然后依赖一个名为 gettext
的工具将所有程序中显示给用户的词句归纳到一个 翻译模板
或一个 .pot
文件中。
之后要做的就是招募一些翻译人员到你的项目中来(这才是最困难的部分),让他们翻译模板中的所有词句,生成一个 .po
文件。通常,你需要给你的翻译人员向源代码仓库 commit
的权限,这样他们就能提交 .po
文件。之后,make install
命令会负责为用户系统选择正确的翻译。
一个程序开始运行之后,它会查询 LANG
系统变量。如果程序自身存在一个与 LANG
对应的翻译文件就会调用它,如果不存在就显示英语。另外,如果翻译文件没有实现完整的翻译工作,程序会调用所有已经翻译的内容,而未被翻译的依旧使用英语。这使得翻译工作可以逐步展开,只要翻译者贡献尽可能多的翻译即可;并且,如果你在程序新的版本中添加了一些新词句,而翻译人员度假去了,你也不用担心整个工程前功尽弃。
使用 gettext
首先,将我们现在的目录拷贝一份到 app-skeleton4
,或者直接重命名,然后修改版本号。为了将 gettext
添加到我们的编译系统中,我们需要在 configure.ac
的“工具箱”部分添加如下内容:
app-skeleton4/configure.ac
AM_GNU_GETTEXT([external])
AM_GNU_GETTEXT_VERSION([0.18.1])
此外,在“输出”部分的 AC_CONFIG_FILES
中添加一行:po/Makefile.in
,在 Makefile.am
的 SUBDIRS
中将 po
添加进去(po
目录是 gettext
文件默认存储的地方。
将
Makefile.in
而非Makefile
放入AC_CONFIG_FILES
中可能有些令人困惑。这是因为gettext
生成的文件名为Makefile.in.in
,我们需要把它转换为Makefile.in
。随后,由AM_GNU_GETTEXT
宏生成的代码会自动将它转换为Makefile
。当然,我们希望这种运行逻辑以后尽量少点出现,否则我们就可能会看到类似于Makefile.in.in.in.in
的文件了。
我们现在需要运行 autoreconf -i
来安装 gettext
相关文件。在执行 configure
之前,我们还需要在 po
文件夹中添加一些文件。第一个是 POTFILES.in
,这个文件列出了 gettext
需要翻译的所有文件;第二个是 LINGUAS
,它列出了所有可用的翻译语言,元素之间用空格分隔。现在我们先创建这样两个空文件,随后我们再添加内容。
最后一个需要添加的文件名为 Makevars
。这个文件包含了一些可由用户自定义的变量,这些变量将被应用于生成 po
目录中的 Makefile
。刚才运行了 autoreconf
之后,目录中应该已经有了一个模板文件 Makevars.template
。我们用它拷贝出一个 Makevars
文件并修改少量内容:我把 COPYRIGHT_HOLDER
改成了自己的名字,MSGID_BUGS_ADDRESS
改成了 $(PACKAGE_BUGREPORT)
,这样翻译人员就将会把 bug 汇报到我的邮箱中(再一个大型项目中,你可能需要设立不同的邮箱)。
Autoconf
将PACKAGE_BUGREPORT
变量定义为我们在configure.ac
的AC_INIT
中设定的邮箱。
接下来,我们需要标记 hello-world.c
中所有需要翻译的界面字符串。首先在文件开头加入一行:
// app-skeleton4/src/hello-world.c
#include <glib/gi18n.h>
这是 GLib
中专门定义 gettext
的库(i18n
表示 “internationalization”,因为在首字母 “i” 和末字母 “n” 之间有 18 个字母)。这个头文件中定义了函数 _()
用于处理翻译字符串(如果 gettext
没有启用,_()
就不会做任何事;程序名设计成 _
是为了尽可能减少对读写代码造成的影响)。
随后,我们在 main()
函数开头添加下面这段代码,在第 40 行输入:
// app-skeleton4/src/hello-world.c
/* Set up internationalization */
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
这段代码使得程序能够读取翻译文件的信息。最后,还要在 Makefile.am
中告诉程序去哪里寻找这些翻译文件:
# app-skeleton4/src/Makefile.am
AM_CPPFLAGS = -DLOCALEDIR=\""$(localedir)"\"
目前我们有四个地方需要被标记成翻译位置:一个在 print_hello()
中,一个在 on_delete_event()
中,一个在 main()
的 gtk_window_set_title()
的参处中,还有一个在 main()
的 gtk_button_new_with_label()
中。我们用 _()
把这些字符串括起来,比如 "Hello World"
就变成了 _("Hello World")
。
注意并不是所有的字符都需要翻译!例如在函数
g_signal_connect()
中的信号名和gtk_window_set_icon_name()
中的图标名就不需要任何处理。程序内部调用的字符串均不需要翻译,只有程序界面显示的字符串才需要。如果你将"destroy"
信号翻译成德语,那程序就崩溃了,毕竟它可不知道 “zerstören” 信号是个什么鬼。
完成上述工作后,我们需要在 POTFILES.in
中添加需要被翻译的文件:
# app-skeleton4/po/POTFILES.in
src/hello-world.c
如果我们现在在 po
目录下运行 make update-po
,就会产生一个名为 app-skeleton.pot
的文件。这是翻译人员的模板文件。
翻译程序
我们试着将程序翻译成其它的语言来测试上面的工作。我将以荷兰语(语言代码 nl
)做介绍。
首先要做的是创建一个翻译语言的 .po
文件,在 po
目录下运行:
msginit -l nl
这将会基于之前的翻译模板生成一个名为 nl.po
文件。你可以用你喜欢的编辑器编辑它,在更大型的项目中,你可能会需要一个专业的翻译文件编辑器,比如 Virtaal 和 Poedit。你可以自己翻译,也可以直接用我的:
#: src/hello-world.c:11
msgid "Hello World\n"
msgstr "Hallo Wereld\n"
#: src/hello-world.c:27
msgid "delete event occurred\n"
msgstr "delete event heeft plaatsgevonden\n"
#: src/hello-world.c:52
msgid "Hello"
msgstr "Hallo"
#: src/hello-world.c:76
msgid "Hello World"
msgstr "Hallo Wereld"
翻译完成后,将语言代码添加到 LINGUAS
中:
nl
随后在 po
目录下再一次运行 make update-po
。这条命令很重要,每次有翻译人员上传了新的文件时,你都应该运行一次,否则文件就不会被应用。随后我们可以运行 make install
来安装。
你现在应该已经可以运行荷兰语的程序了(或者其它你选择的语言)。如果你的系统区域设定为荷兰,直接运行程序就能看到效果。如果是其它的区域则需要在运行程序时单独设定 LANG
环境变量(在终端中输入 LANG=nl app-skeleton
)。
译者注:常用的语言代码有:
zh_CN
:中文(中国大陆)
zh_HK
:中文(中国香港)
zh_TW
:中文(中国台湾)
zh_SG
:中文(新加坡)en_US
:英语(美国)
en_UK
:英语(英国)
en_CA
:英语(加拿大)
en_HK
:英语(中国香港)
更多语言代码请参阅 Language Codes 和 Locale Helper。
翻译非代码文件
gettext
有一个缺陷:它只对代码文件有效,可以通过调用 gettext()
读取翻译。而类似于 xml
、桌面文件等数据文件则爱莫能助。这便是引入 intltool
的原因。它将这些文件中需要翻译的字符串也添加到了翻译模板中。在这些词句被翻译后,它还会将这些翻译整合回数据文件中。我们现在将 intltool
添加到我们的程序中来翻译桌面文件。
首先,intltool
有它自己的引导程序——intltoolize
,它必须在 autoreconf
之后才能运行。使用了 intltool
之后我们的引导过程就不能再只用一句 autoreconf -i
敷衍了事了,我们需要制作一个引导脚本。在项目根目录下,我们创建一个 autogen.sh
文件,赋予它可执行权限,写入:
# app-skeleton4/autogen.sh
#!/bin/bash
echo "Regenerating autotools files"
autoreconf --force --install || exit 1
echo "Setting up Intltool"
intltoolize --copy --force --automake || exit 1
我们需要用到 --force
选项,因为 autoreconf
和 intltoolize
会覆盖掉彼此的 po/Makefile.in.in
文件。写好之后我们就可以运行一次引导脚本。
随后,在 configure.ac
的 “工具箱” 部分添加如下内容:
# app-skeleton4/configure.ac
IT_PROG_INTLTOOL([0.40])
在 “库” 部分,我们添加一个新的 “变量” 部分。为了能够正常使用 intltool
,我们需要定义一个名为 GETTEXT_PACKAGE
的变量,它指向程序在 gettext
中被定义的名字。
# app-skeleton4/configure.ac
# Needed by intltool
GETTEXT_PACKAGE=${PACKAGE_TARNAME}
AC_SUBST([GETTEXT_PACKAGE])
AC_SUBST
宏会确保所有 Makefile
中的 @GETTEXT_PACKAGE@
变量都会被替换成变量指代的实际内容。现在再运行一次 make
。
接下来,我们需要告诉 intltool
哪些数据文件包含了需要被翻译的语句。庆幸的是,有一个叫作 intltool-prepare
的脚本会自动帮我们完成这些工作。在项目根目录下运行 intltool-prepare
,你会注意到它自动将桌面文件添加到了 po/POTFILES
中并且修改了 Makefile.am
中桌面文件的安装规则。
它还新建了一个文件,app-skeleton.desktop.in
。如果你使用了代码控制系统的话,最好遵循它给出的建议从仓库中移除你的 app-skeleton.desktop
文件 —— 它现在可以由编译系统根据 app-skeleton.desktop.in
自动创建出来。从现在起,如果你想编辑桌面文件,请在 app-skeleton.desktop.in
中做修改。
如果需要翻译桌面文件,我们需要在 po
目录下再运行一次 make update-po
命令。你将会看到 .pot
和 .po
文件中新添加了两句未翻译的语句。你可以自行翻译它们,也可以用我的翻译:
#: ../app-skeleton.desktop.in.h:1
msgid "A sample application from the Advanced GTK+ Techniques tutorial"
msgstr "Een voorbeeldapplicatie uit de cursus Gevorderde GTK+ Technieken"
#: ../app-skeleton.desktop.in.h:2
msgid "App Skeleton"
msgstr "Skeletapplicatie"
接着,再运行一次 make update-po
,然后是 make
。你可以看到在生成的 app-skeleton.desktop
中你的翻译已经被整合进去了。如果你现在运行 make install
,翻译文件就会被安装。
以上就是全部编译基础构建的内容。现在我们可以正式开始写代码了。:-)