[译文] 如何开始一个真正的 GTK 项目(第五部分:配置 Gettext)

原文地址: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.amSUBDIRS 中将 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 汇报到我的邮箱中(再一个大型项目中,你可能需要设立不同的邮箱)。

AutoconfPACKAGE_BUGREPORT 变量定义为我们在 configure.acAC_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 文件。你可以用你喜欢的编辑器编辑它,在更大型的项目中,你可能会需要一个专业的翻译文件编辑器,比如 VirtaalPoedit。你可以自己翻译,也可以直接用我的:

#: 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 CodesLocale 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 选项,因为 autoreconfintltoolize 会覆盖掉彼此的 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,翻译文件就会被安装。

以上就是全部编译基础构建的内容。现在我们可以正式开始写代码了。:-)


文章许可协议:Attribution-NonCommercial-ShareAlike 3.0 Unported

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容