原文地址:Advanced GTK Techniques。
這篇教程中你將學會:
- 使用
Gettext
完成源代碼中的翻譯工作;
- 使用
Intltool
完成其它文件的翻譯工作。
這篇文章是《如何開始一個真正的 GTK 項目》的一部分,如果你不想回看之前的章節(jié),可以直接下載教學示例程序
app-skeleton3
。你也可以從頭開始。
這是我們在開始正式的代碼工作前又一個基礎準備工作,這將實現程序在不同語言環(huán)境下的本地化。你可能覺得翻譯工作一般都是在應用成型后才展開,但是在編寫程序時同步開始翻譯往往更利于保持掌控翻譯的質量。
你是否需要為了這項工作學習 30 門外語呢?當然不用。在真實的場合中,人們用英語編寫程序(準確地說是 C locale
),然后依賴一個名為 gettext
的工具將所有程序中顯示給用戶的詞句歸納到一個 翻譯模板
或一個 .pot
文件中。
之后要做的就是招募一些翻譯人員到你的項目中來(這才是最困難的部分),讓他們翻譯模板中的所有詞句,生成一個 .po
文件。通常,你需要給你的翻譯人員向源代碼倉庫 commit
的權限,這樣他們就能提交 .po
文件。之后,make install
命令會負責為用戶系統(tǒng)選擇正確的翻譯。
一個程序開始運行之后,它會查詢 LANG
系統(tǒng)變量。如果程序自身存在一個與 LANG
對應的翻譯文件就會調用它,如果不存在就顯示英語。另外,如果翻譯文件沒有實現完整的翻譯工作,程序會調用所有已經翻譯的內容,而未被翻譯的依舊使用英語。這使得翻譯工作可以逐步展開,只要翻譯者貢獻盡可能多的翻譯即可;并且,如果你在程序新的版本中添加了一些新詞句,而翻譯人員度假去了,你也不用擔心整個工程前功盡棄。
使用 gettext
首先,將我們現在的目錄拷貝一份到 app-skeleton4
,或者直接重命名,然后修改版本號。為了將 gettext
添加到我們的編譯系統(tǒng)中,我們需要在 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
相關文件。在執(zhí)行 configure
之前,我們還需要在 po
文件夾中添加一些文件。第一個是 POTFILES.in
,這個文件列出了 gettext
需要翻譯的所有文件;第二個是 LINGUAS
,它列出了所有可用的翻譯語言,元素之間用空格分隔。現在我們先創(chuàng)建這樣兩個空文件,隨后我們再添加內容。
最后一個需要添加的文件名為 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
)做介紹。
首先要做的是創(chuàng)建一個翻譯語言的 .po
文件,在 po
目錄下運行:
msginit -l nl
這將會基于之前的翻譯模板生成一個名為 nl.po
文件。你可以用你喜歡的編輯器編輯它,在更大型的項目中,你可能會需要一個專業(yè)的翻譯文件編輯器,比如 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
來安裝。
你現在應該已經可以運行荷蘭語的程序了(或者其它你選擇的語言)。如果你的系統(tǒng)區(qū)域設定為荷蘭,直接運行程序就能看到效果。如果是其它的區(qū)域則需要在運行程序時單獨設定 LANG
環(huán)境變量(在終端中輸入 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
敷衍了事了,我們需要制作一個引導腳本。在項目根目錄下,我們創(chuàng)建一個 autogen.sh
文件,賦予它可執(zhí)行權限,寫入:
# 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
中桌面文件的安裝規(guī)則。
它還新建了一個文件,app-skeleton.desktop.in
。如果你使用了代碼控制系統(tǒng)的話,最好遵循它給出的建議從倉庫中移除你的 app-skeleton.desktop
文件 —— 它現在可以由編譯系統(tǒng)根據 app-skeleton.desktop.in
自動創(chuàng)建出來。從現在起,如果你想編輯桌面文件,請在 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
,翻譯文件就會被安裝。
以上就是全部編譯基礎構建的內容。現在我們可以正式開始寫代碼了。:-)