親手打造 GNU/Linux 中文環境 (五) - 訊息國際化的解決方案 (gettext 簡介)

謝東翰 ,小虫

1 前言

在前幾期的文章裡面,我們花了不少篇幅跟大家介紹了 GLIBC 提供的國際化支援以及 locale 的基本結構,我們也提到過 "LC_MESSAGES" 這個環境類別會決定程式用何種語言來顯示訊息,在這一期裡面,就讓我們來更深入的研究一下訊息多國語言的解決方案,看看在一個多國語言環境之中,要如何讓同一個程式在不同 locale 環境下可以自動顯示不同語言的訊息。

1.1 訊息國際化的問題

有寫過程式的人都知道,一般情況下程式的訊息都是寫死在程式碼裡面的,例如說:

printf("Hello World!!");
這一段 C 程式碼會印出 "Hello World!!" 這個字串,假如要把程式的訊息改成其他的語言的話,必須要修改原始碼,然後再重新把程式編譯一遍才行,這樣的作法在國際化的系統裡面是行不通的,因為一個國際化的系統應該要有能力隨時依照使用者設定的語言環境 (就是 LC_MESSAGES 的內容) 來選取要輸出訊息的版本,也就是說,用同一份程式碼就可以顯示不同語言的訊息,才能稱之為國際化的系統。GLIBC 雖然提供了 setlocale() 的介面以及 LC_MESSAGES 的環境類別,不過卻沒有定義訊息多國語言的實作方式,所以要做到訊息的國際化,必須得要使用 GLIBC 以外的函式套件才行。

由於在 C 函式庫中並沒有標準的定義,所以實際上在 GNU/Linux 裡面進行訊息國際化的解決方案並不只一種,常見方式可以歸類成下面三種:

  1. 利用 X 的資源定義資料庫 (X resource database) 來作訊息國際化

    這是 X 視窗系統提供的標準方法,許多 X 應用程式都是用這種方法來作訊息的國際化的,例如 Netscape 或 xedit 等程式就是屬於這一類;X 允許您將程式使用到的訊息存放在 X 的資源資料庫 (resource database) 裡面,由於 X 應用程式在執行時會依照 locale 設定到 /usr/X11R6/lib/X11/{locale}/app-defaults/ 目錄下去尋找對應到該 locale 的資源定義檔案,所以只要把訊息字串定義在資源定義檔案裡面,把這些字串翻譯好,然後再放到該 locale 的 app-defaults 目錄底下,就可以依照 locale 設定顯示不同語言的訊息了,讀者可以在 /usr/X11R6/lib/X11/app-defaults/ 目錄下找到一些未翻譯的資料檔案。假如您有加裝一些中文套件的話,很可能還可以在 /usr/X11R6/lib/X11/zh_TW.Big5/app-defaults/ 目錄下找到 Netscape 的繁體中文定義檔,這裡面以 labelString 結尾的資源項目就是 Netscape 程式用到的訊息,讀者可以自己參考看看。

  2. 利用 GNU gettext 套件

    gettext 套件是 GNU 對訊息國際化提出的一個統一的解決方案,只要程式寫作時注意幾點小事情,對程式碼做好適當的修改並連結到 gettext 程式庫,編譯一次以後,不須重新編譯就能顯示多國的訊息,讓國際化 (i18n) 的工作可以更簡單的達成;因為使用簡單,支援的程式工具也多,gettext 已經漸漸成為 GNU/Linux 上面訊息國際化的標準作法,像 GNOME 程式、KDE 程式等等都是用 gettext 來作訊息的國際化。

  3. 應用程式自行定義的方法

    某些應用程式有自己的一套訊息國際化的架構,像 linuxconf 就自己定義了一套方式來作訊息的國際化;這些方法通常跟 gettext 的基本架構很類似,作法的流程也有很多可以類比之處,不過這都不是標準的作法,自己重新發展一套訊息資料的處理方法也是一件費時費力的工作,過去 GNU/Linux 上面的 gettext 還不成熟的時候或許有需要,不過在目前 gettext 已經被廣泛接受使用的情況下,通常是沒必要自己再重新發明一遍輪子囉。

上面所述的三個方法裡面,X 提供標準出現的最早,但是因為在程式設計階段就得自行將訊息分離處理,用起來並不方便,也缺乏一些工具程式的支援,因此遲遲未能受到大家廣泛使用。在 GNU gettext 已經逐漸成熟以後,多數的程式也不再用這個方法。目前 gettext 套件已經算是被大家接受的標準作法了,雖然它現在的版本還只是 0.10 版,但是對於訊息國際化的支援 (LC_MESSAGES) 已經算是穩定,再加上幾乎所有的 GNU 程式也都支援 gettext ,因此現在把它當作一個標準,來作 Linux 上訊息中文化的工作,應該是一個適當的時機,我們也建議所有有心讓自己程式顯示多國語言的程式設計者都能善加利用 gettext 套件,因此本文討論的對象會專注在 gettext 這個套件的使用方式之上,X 或其他程式的作法我們就不詳談了。

接下來讓我們先來看看 gettext 的基本原理吧。

2 gettext 的使用

2.1 gettext 的工作原理

1 是從 GNU 的 info page 抄出來的,它總括說明了 gettext 套件的工作原理,就請讀者們參照後面的說明以及這張圖上的流程,一起來看看 gettext 套件的工作流程吧。

Figure 1: gettext 的工作流程圖

2.1.1 修改程式碼

我們前面提過,使用 gettext 套件的時候原始碼要作一些適當的修改,圖 1 裡面的 "Source Code" 指的就是還沒有經過任何修改的程式碼,"Marked Source Code" 指的就是修改後的程式碼,那麼,要作些甚麼修改呢?

要讓你的程式支援訊息國際化,程式設計師在寫作程式的時候就必須把有需要國際化的訊息標出來,這個時候該注意的是應該只標示出需要被翻譯成他國文字的訊息,比如 "%s %d" 這樣的字串是不必被翻譯的,也就不必標示,而 "Hello World!!" 這類的說明訊息就需要標示出來,標示的方法如下:

printf(gettext("Hello World"));
懂 C 的朋友應該可以看出這個標示的意義,程式碼這樣修改以後,程式在印出訊息時,並不是直接取得訊息的字串,而是透過一個 gettext() 函式呼叫來獲得字串;這個 gettext() 函式是在 libintl.h 裡面定義的,它會在程式真正被執行時檢查 locale 的情況 (事實上是在程式開始時先呼叫 setlocale() 函式來設定 LC_MESSAGES 類別,在這一 步程式會去檢查 LC_ALL 或 LC_MESSAGES 或 LANG 這環境變數,並做好 locale LC_MESSAGES 類別的設定),然後以 gettext() 的參數 ( "Hello World" ) 作為一個 id key,去該 locale 底下的訊息資料庫裡面抓取合乎這個 id key 的訊息,這樣子同一個程式,就可以 show 出許多不同語言的訊息了。

從 "Source Code" -> "Marked Source Code" 這一段修改程式碼的流程,是程式設計師的工作,也是程式訊息多國語言的過程之一,更詳細的修改步驟我們在第 3.1 節裡面會用實例跟大家介紹,且留待後面再討論吧。

2.1.2 製作 PO 檔案

改好程式碼以後,接著應該就要把訊息從程式碼裡面抽出來,製作出 "訊息資料庫" ,那訊息資料庫是甚麼?要怎麼作呢?

GNU gettext 套件裡面有一個程式叫 xgettext,可以用來處理 Marked Source Code,把原始碼裡面有標記的訊息抓出來,產生 PO 檔 (Portable Object),xgettext 的基本使用如下:

xgettext [選項] INPUTFILE ...
INPUTFILE 就是 "Marked Source Code",假如給定的輸入檔名為 `-' 的話,則程式會從標準輸入讀入資料,有多個檔案需要處理的話,在後面列入就可以了,這個程式常用的選項有:

-d NAME
用 NAME.po 做為輸出檔名 (預設是 messages.po)
-D DIRECTORY
增加 DIRECTORY 到檔案搜尋列表中
-f FILE
從檔案 FILE 裡面讀取輸入檔檔名的列表。
-keyword=WORD
其他用來標示的關鍵字 (若沒有指定 WORD , 表示使用預設的關鍵字 gettext)。
-o FILE
把產生的結果寫到 FILE 這個檔案去。
-p DIR
把輸出的檔案放到 DIR 這個目錄底下。
-s
製造排序妥當的輸出,並移除重覆的欄位。
-h
顯示說明訊息。

2.1.3 對 PO 檔進行翻譯

xgettext 產生的 .po 檔案是一個標準的文字檔,用 more 或者一般的文字處理程式就可以觀看它的內容,它裡面包含了xgettext 從輸入的原始碼檔案裡面抓出的所有訊息,裡面每一項訊息的格式大約如下:

#: hello.c:22
msgid "Hello!!"
msgstr ""
這裡面 '#' 開頭的行是註解,msgid 欄位是未翻譯前原先的訊息,msgstr 就是用來填翻譯過的訊息的,我們可以把翻譯過的訊息填進去,如下:

#: hello.c:22
msgid "Hello!!"
msgstr "您好!"
如何,翻譯的動作非常簡單吧;翻譯 PO 訊息並不需要有特別的工具,只要用一般的文字編輯器就可以了,此外由於 PO 檔案是單純的文字檔,所以跟使用的平台無關,您甚至可以把 GNU/Linux 程式的 PO 檔案拿到 Microsoft Windows 作業系統裡面用 Notepad 編輯、翻譯好再拿回來 GNU/Linux 底下使用,這也就是 PO 的全名 "Portable Object" 的意思。經過使用者把每個項目裡面的訊息一一翻譯完成的 PO 檔,只要檔用 msgfmt 編譯以後擺到適當的地方,就可以讓翻譯過的訊息發生作用,接著我們就來看看怎麼讓 PO 檔案生效吧。

2.1.4 編譯並安裝訊息資料庫

作好 PO 檔以後,就可以用 "msgfmt" 這個程式來把 PO 檔變成真正機器可以處理的 .mo 檔 (Machine Object):

msgfmt [選項] filename.po ...
msgfmt 常用的選項有:

-o FILE
把產生的結果寫到 FILE 這個檔案去,FILE 的檔名一般會以 .mo 結尾,沒有指定的話,結果會寫到 messages 這個檔案裡面。
-v
列出輸入檔中異常的部份,多用幾個選項 -v 的話會顯示更多的訊息。
-h
顯示說明訊息。
用 msgfmt 產生出來的這個 .mo 檔才是真正的訊息資料庫檔案,它的名稱一般是跟應用程式的名稱一致,例如 gedit 的訊息就叫做 "gedit.mo",在程式安裝的時候,這些訊息資料檔應該要被放到該訊息對應的 LOCALE 目錄裡面去,一般而言就是在 /usr/share/locale/*/LC_MESSAGES/ 底下 (這裡的 * 指的就是 locale 的名稱),例如繁體中文的 locale 為 zh_TW.Big5,所以繁體中文的訊息就應該放在 /usr/share/locale/zh_TW.Big5/LC_MESSAGES/ 底下,以 GB2312 編碼的簡體中文訊息就該擺在 /usr/share/locale/zh_CN.GB2312/LC_MESSAGES/ 底下。如此一來,各種不同語文翻譯的訊息就能夠很自然的分開擺放不相衝突,在應用程式執行的時候,gettext 也就可以依照 locale 設定的不同而去不同的目錄底下抓到適當的訊息翻譯。下面這行指令可以列出您目前系統上安裝的程式的各種不同語言的訊息資料檔,讀者不妨自己看一看您的 GNU/Linux 上面有多少程式已經有中文翻譯囉:

ls /usr/share/locale/*/LC_MESSAGES/

2.1.5 合併舊有的翻譯訊息

在程式經歷過幾次改版以後,很自然的會加入新的訊息或修改舊的訊息,需要重新產生 PO 檔案並進行翻譯,不過這時候其實很多訊息都已經翻譯過了,有舊版的 .po 訊息檔,我們當然不希望把已經翻譯過,而且還可以用的訊息再打一次,此時就可以用 "msgmerge" 這個程式來把舊的 .po 檔裡面的訊息合併到新的 PO 檔案裡面:

msgmerge [選項] def.po ref.po
常用的選項有:

-i
對輸出結果做縮排處理。
-o FILE
把輸出的結果寫入 FILE 這個檔案。
-S
使用嚴格統一標準的輸出結果。
-h
顯示說明訊息。
上面 def.po 是舊的翻譯過的 PO 檔,ref.po 是新的 PO 檔案 (xgettext 做出來的),在 def.po 裡面翻譯過的訊息只要是仍然找得到相符合欄位的,都會被合併到 ref.po 裡面去,在找不到完全一致的欄位時,程式還會用模糊邏輯的方法來得到類似的翻譯,這些用模糊邏輯合併進去的欄位會被標上一個 "#, fuzzy" 的記號,根據我們實際的經驗,這些模糊邏輯對出來的翻譯常常是錯的,讀者在進行翻譯的時候要特別重新檢查這些欄位才行。

2.2 程式設計師與訊息翻譯者的分工

在圖 1 的流程裡面,大家可以看到 xgettext 產生的檔案叫做 .pot,這是 Portable Object Template 的意思,表示剛產生的檔案裡面只有原來的訊息,還沒有翻譯。接下來有一個 .pox 檔,這是 GNU 建議對還沒完全翻譯好的PO 檔的稱呼,意思是施工中的 PO 檔案,等到翻譯完工定案了,再把它改成 .po 檔;.pot、.pox、.po 這三種檔案都是 PO 檔,格式是一模一樣的,在圖裡面作這種不同的稱呼只是為了方便區分翻譯的狀況罷了,沒有特殊的意義,讀者也不見得一定要遵守這個命名規則的。

圖中的 .gmo 其實指的就是用 msgfmt 編譯出來的 .mo 檔,GNU 建議用 .gmo 來稱呼還沒安裝到定位的 .mo 檔,表示這個檔案是由 GNU gettext 套件產生的,這個命名法在許多 GNU 程式套件裡面都可以看到;從程式碼到翻譯到安裝的整個流程還可以用檔案的附加檔名慨括如下:

source code -> .pot -> .pox -> po -> .gmo -> .mo ->
在這整個流程裡面,寫作程式跟製作 .pot 檔案是屬於程式設計者的工作,然而 PO 檔案的翻譯卻不一定要是程式設計者本人來作,事實上寫程式的人也不可能懂得各國的語言,這些翻譯跟維護 PO 檔案的工作是屬於 L10N 的範疇,應該由世界各地使用不同語言的人來擔任翻譯的工作才是。作這些翻譯工作的人只要是單純的使用者、懂英文、會使用文字編輯程式就可以了,並不需要懂得程式設計,因此人人都可以參與,gettext 套件提供了一個程式設計師跟訊息翻譯者可以分工的架構,在圖 1 裡面深色的部份表示的就是屬於訊息翻譯者可以發揮的地方,外圍淺色的部份則是程式設計師的工作,由程式設計者製作出 .pot 檔案以後,有興趣翻譯的人就可以去抓回來翻譯,翻好以後再寄回去給程式設計者包在套件裡面發行,下一節我們先用一個例子實際讓有心利用 gettext 寫作多國語言程式的讀者可以有個實作的機會,接著在第 4 節裡面我們會從單純使用者跟翻譯者的角度來看 gettext 套件,請讀者依照您個人的 "志願" 選擇有興趣的部份來閱讀吧。

3 一個實際的例子

上一節裡面我們對整個 gettext 工作的流程做了簡單的說明,接下來我們就來寫一個簡單的程式,希望能夠利用這個完整的例子來讓讀者瞭解利用 gettext 套件寫作 I18N 程式的詳細方法。


3.1 使用 gettext 相關的函式

要利用 gettext 套件寫作 I18N 程式,第一件事當然是要把 getext 套件安裝好,現在所有的 GNU/Linux 安裝套件裡面應該都有內含這個套件了,因此安裝應該不困難,讀者只要從光碟或 FTP 站上面把 gettext 套件抓回來裝好就可以,在此就不贅述了;除了 gettext 之外,你還必須有支援 I18N 的 LIBC,這一點讀者可以查看一下 /usr/include/ 底下是不是有 locale.h 以及 libintl.h 這兩個檔案,有的話就表示你的 LIBC 是支援 I18N 的,這在目前各家 GNU/Linux 安裝套件廠商使用的 glibc-2.1.x 裡面是不會有問題的。要寫一個訊息國際化的程式,需要用到底下這兩個 C 函式:

char *setlocale(int category, const char * locale);

extern char *textdomain(const char *domainname);
上面的 setlocale() 是用來設定區域化環境的,用法在前幾期已經詳細介紹過,這邊就不贅述。textdomain() 這個函式接受一個字串當作引數,用來指定這個程式所使用的 "文字領域" (textdomain),textdomain 會決定以後的 gettext() 函式要到那個檔名的檔案裡面去找訊息資料,gettext() 會在程式執行時到 domainname.mo 這個檔案裡找字串的翻譯,一般而言這個 domainname 都設成和程式的名稱一樣的以避免不同程式的訊息檔案相互衝突。

3.2 範例程式

寫作應用程式時要用 gettext 來作訊息多國語言的步驟其實非常簡單,只有以下幾點:

  1. 在程式開頭引入 libintl.h 跟 locale.h 兩個標頭檔。
  2. 在主程式開頭時用 setlocale() 設定區域化環境。
  3. 用 textdomain() 設定文字領域。
  4. 把程式碼中所有需要翻譯的訊息都包在 gettext() 函式裡面。
底下就是一個完整的程式,我們把它存在 gettext_test.c 裡面,讀者不妨先一邊看程式碼,一邊觀察上面幾個步驟出現的位置:

/******************** Example program start here ********************/
/* gettext_test.c by Yuan-Chung Cheng */
#include
#include
#include
#define _(STRING) gettext(STRING)
#define PACKAGE "gettext_test"

char *program_name;

main(int argc, char *argv[])
{
program_name=argv[0];
setlocale(LC_ALL, "");
textdomain(PACKAGE);

if(argc < 2){
printf(_("%s: too few arguments\n"), program_name);
exit(0);
}
printf(_("Hello, %s.\nBe a Happy Linuxer!!\n"), argv[1]);
}

/******************** Example program end here ********************/
這個程式的流程極簡單,只是在一開始多了 setlocale、設定 textdomain 兩個步驟而已,讀者應該不難在這個短短的程式裡面看出配合 gettext 修改的部份,值得特別注意的是,在這個程式裡面我們把 _() 定義成 gettext(),然後再用 _() 來標示需要翻譯的字串,這個簡化的用法是 GNU 應用程式裡面慣用的,可以讓程式設計師少打很多 gettext(),也可以增加程式的可讀性,建議大家可以學起來。

3.3 產生 .pot 檔案

有了程式碼以後,就可以利用 xgettext 程式生成 .pot 檔案:

xgettext -keyword=_ -o gettext_test.pot gettext_test.c
前面的程式碼裡面用 _() 代替了 gettext() 函式,所以在用 xgettext 抽出訊息的時候要多加一個參數 "-keyword=_",告訴程式用 _() 標示的字串也要當成待翻譯的訊息抽出來,存到檔案 gettext_test.pot 裡面去,以下就是產生的 .pot 檔案的內容:

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR Free Software Foundation, Inc.
# FIRST AUTHOR , YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2000-06-19 05:51+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"

#: gettext_test.c:21
#, c-format
msgid "%s: too few arguments\n"
msgstr ""

#: gettext_test.c:25
#, c-format
msgid ""
"Hello, %s.\n"
"Be a Happy Linuxer!!\n"
msgstr ""


3.4 翻譯 PO 檔案並安裝

得到 .pot 檔案以後就可以進行翻譯的工作了,這個程式只有兩行訊息,當然輕輕鬆鬆三兩下就翻譯完畢了:

# SOME DESCRIPTIVE TITLE.
# Copyright (C) 2000 Free Software Foundation, Inc.
# Yuan-Chung Cheng , 2000.
#
msgid ""
msgstr ""
"Project-Id-Version: hello-0.1\n"
"POT-Creation-Date: 2000-06-18 05:51+0800\n"
"PO-Revision-Date: 2000-06-19 08:30+0800\n"
"Last-Translator: Yuan-Chung Cheng \n"
"Language-Team: Chinese \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=big5\n"
"Content-Transfer-Encoding: 8-bit\n"

#: gettext_test.c:21
#, c-format
msgid "%s: too few arguments\n"
msgstr "%s: 參數太少了\n"

#: gettext_test.c:25
#, c-format
msgid ""
"Hello, %s.\n"
"Be a Happy Linuxer!!\n"
msgstr ""
"您好, %s. \n"
"好好玩 Linux 喔!!\n"
上面便是完成翻譯以後的 PO 檔案,讀者要注意除了翻譯訊息以外,還要在一開頭的標頭部份把您的大名、E-Mail、翻譯的編碼等等欄位填一填才行。完成的 PO 檔案可以用下面的指令編譯成 .gmo 檔案:

msgfmt -v -o gettext_test.gmo gettext_test.po
接著把完成的訊息資料庫安裝到定位,我們現在有的是繁體中文的翻譯結果,所以安裝在 zh_TW.Big5 目錄底下:

install -m 644 gettext_test.gmo /usr/share/locale/zh_TW.Big5/LC_MESSAGES/gettext_test.mo
裝好之後把原來的原始碼用 gcc 編譯起來,然後改一下 LANG 或 LC_MESSAGES 的設定,執行看看,是不是已經能夠成功顯示多國的訊息了呢?圖 2 是在不同 LANG 設定下執行本程式的結果,讀者可以看到當 locale 設定改變的時候,程式會自動顯示不同語言的訊息出來。要加上其他語言的翻譯的話,只要把 PO 檔翻譯好,用 msgfmt 編譯成 .mo 檔案並安裝到該語言 locale 的 LC_MESSAGES 目錄底下就可以了,並不需要重新編譯程式。

Figure 2: 依照不同 locale 設定顯示不同訊息

在這一節裡面,我們為了說明的方便,舉的例子只是一個小小的檔案而已,讀者們在發展自己的程式時可能會遇到比較複雜的情況,例如程式碼檔案可能會不只一個,而且分佈在不同目錄底下,這個時候在產生 PO 檔案的過程會比較複雜,不過只要善用 GNU 的 autoconf 套件與 gettextize 程式來幫您處理程式碼,就可以省下不少功夫。限於篇幅我們只能為大家介紹最簡單的情況,深入的應用就只好麻煩大家直接參考 gettext 的使用手冊了:

http://www.gnu.org/manual/gettext/html_mono/gettext.html


4 從翻譯者的觀點來看 gettext 工具

讓應用程式支援 gettext 是程式設計師的工作,不過要讓程式在執行時能夠顯示出各種語言的訊息,就有賴訊息翻譯者的努力了。參與翻譯訊息的工作需要的知識跟寫作 I18N 程式沒什麼相關性,也不需要對程式設計的原理有甚麼了解,在翻譯時更重要的是對該應用程式的 "使用經驗",知道怎麼將程式的原文訊息適切的用本國的語言來表達,除此之外,就剩下要懂得編輯文字檔而已了,所以任何人都可以參與翻譯訊息的工作,事實上目前在 GNU/Linux 底下的中文訊息檔很多都是由自願的熱心網友翻譯出來的,許多參與的人也都完全不懂得設計程式呢。

4.1 PO 檔案的翻譯

4.1.1 取得 PO 檔案

要進行 PO 檔案翻譯的第一件事就是取得還沒翻好的 PO 檔,PO 檔一般含在應用程式的原始碼裡面,只要使用到 gettext 套件的應用程式都會有一個名為 po/ 的目錄,這就是擺放 PO 檔案的地方,通常在這底下可以找到一個結尾為 .pot 的未翻譯檔,以及許多各種語言的翻譯結果,抓 POT 檔案回去翻譯之前,讀者最好先檢查看看以前是不是有人翻譯過這個訊息了,以繁體中文為例,先找看看 po/ 目錄底下是不是有叫做 zh_TW.Big5.po 這個檔案,有的話,就表示已經有人翻譯過這個訊息了。

在沒人作過翻譯的情況下,請注意一下 POT 檔案裡面標頭部份顯示的產生時間,記得不要抓太舊的檔案回去,以免浪費時間在翻譯過時的訊息,必要的時候寫封 e-mail 請負責維護程式碼的人更新一下 .pot 檔案的版本,或者乾脆自己把整個原始碼抓回來,用 make 更新 .pot 檔案,再重頭翻譯就是了。

發現檔案已經有人翻譯過的話,請盡量先跟前一位翻譯的人取得聯繫再動手翻譯;在 PO 檔的標頭部份都有翻譯者的 e-mail 位址,寫封信去詢問他是否要繼續維護這個訊息,甚至加入他一起來合作維護,我想讀者也不希望您在辛苦翻譯到一半的時候突然又冒出另外一個版本,結果浪費時 間作白工了吧。假如確定前人已經不再繼續維護這個訊息翻譯以後,您就可以放心的以原來翻譯的基礎繼續翻譯的工作囉,不過還是要更新一下訊息的版本,保持翻 譯最新的程式訊息,此時請先拿到新版的 POT 檔案,然後用下面指令拿到最新版本的 PO 檔:

msgmerge zh_TW.Big5.po package_name.pot > package_name.po
上面指令中 zh_TW.Big5.po 是舊的 PO 檔案,package_name.pot 表示新的 POT 檔案,msgmerge 程式會把 zh_TW.Big5.po 裡面還能用的翻譯跟 package_name.pot 裡面的訊息定義合併起來,輸出到 package_name.po 裡面去,這個新的 PO 檔案裡面會保留有舊的翻譯再加上新的訊息定義,請拿這個檔案來進行訊息翻譯的工作。

4.1.2 翻譯訊息

拿到最新的 PO 檔案以後就可以開啟您最愛用的文字編輯程式來進行翻譯的工作囉。PO 檔案是單純的文字檔,不管您用甚麼程式來編輯它都沒關係,前面已經說過,甚至用 Windows 的 Notepad 來翻譯都沒有關係,雖說如此,我們在網路上還是可以找到不少個可以幫助您翻譯工作的程式,讀者們到 http://freshmeat.net 底下用 gettext 為關鍵字找一下就可以找到好幾個,請讀者們自己試試看囉。小虫倒是建議有使用 Emacs 的朋友試試看 PO mode,在 gettext 套件裡面可以找到一個 po-mode.el 的 emacs Lisp 程式檔,參照裡面的說明裝好以後,您的 Emacs 在讀到 PO 檔的時候就會自動轉到 PO mode 裡面,PO mode 讓您可以更簡單的輸入翻譯出來的訊息,更另外提供了一些操作訊息項目的指令讓您使用,像訊息的複製、刪除等等都可以輕易的做到,讀者可以自己在 PO mode 底下按 'h' 看說明訊息來學習 PO mode 的使用方式,圖 3 就是用 Emacs 的 PO mode 在編輯 PO 檔案的情況。

Figure 3: Emacs 的 PO mode

翻譯時要記得填入標頭資料,用 msgmerge 出來的檔案進行翻譯的時候要特別注意一下被標為 "#, fuzzy" 的項目,請務必檢查一下這些項目,沒問題的話要記得把 "#, fuzzy" 這一行刪掉,否則接下來編譯的時候會發生問題的。繼承別人翻譯成果的朋友請記得把標頭資料改成自己,並且把前一位翻譯者的資料放到一開始的版權資訊裡面, 以示對前人翻譯成果的尊重,並且也為這個檔案的傳承關係作一個見證。

在翻譯時還有其他特別的事情需要注意,因為您在翻譯的事實上是 C 語言的字串,所以一些訊息的寫法得要依照 C 字串的規矩才行,例如字串中有引號 '"' 的時候要用打成 '\"' 才行,翻譯出來的結果要保留原文訊息中的跳行字元跟 '%' 帶頭的那些變數參考才行,例如下面的翻譯:

#: locale/programs/ld-ctype.c:4131
#, c-format
msgid "%s: table for class \"%s\": %lu bytes\n"
msgstr "%s: 類別 \"%s\" 表格: %lu 位元組\n"
繁體中文翻譯還會有一個額外的問題,因為許多中文字像許、功、擺... 等等的第二個位元組是 '\'字元,這在 C 字串中會造成錯誤,因此在翻譯遇到這些字的時候都要在後面多加一個 '\' 才行,因為這個工作手動實在是太麻煩了,網路上有人寫了一些小程式來處理這個問題,讀者們可以在 http://i18n.linux.org.tw/ 找到 bg5cc 這個程式,用它來幫您加 '\',我們在翻譯的時候只要編輯 .pox 檔案,不需要去擔心這些中文字的問題,等到要編譯成 .mo 檔案的時候再來用 bg5cc 轉一下:

bg5cc package_name.pox package_name.po
上面這個指令會幫您把 package_name.pox 裡面需要多加 '\' 的字找出來,把加上 '\' 的結果存到 package_name.po 裡面去,產生的這個 package_name.po 檔案就可以放心交給 msgfmt 處理,不必擔心中文字的問題囉。

4.1.3 測試翻譯結果並送回給原作者

翻譯好的 PO 檔案可以用下面指令測試出是否正確無誤:

msgfmt -v -o /dev/null package_name.po
例如:

[platin@bruceyu libc-zh_TW.Big5]$ msgfmt -v -o /dev/null libc-zh_TW.Big5.po 652 已翻譯的訊息 , 496 未譯的訊息 .

[platin@bruceyu libc-zh_TW.Big5]$
編譯發生錯誤的話,msgfmt 會告訴您有問題的行號,您可以很快的找到錯誤所在;正確無誤的 PO 檔案就可以用第 3.4 節提到過的方法安裝到 /usr/share/locale/... 底下,下次您開啟這個程式的時候,就可以看到翻譯的結果了。當然,獨樂樂不如眾樂樂,請記得把您翻譯完成的 PO 檔案用 e-mail 寄回去給程式的原作者,請他將這個訊息在程式下一次 release 的時候放進去,如此一來全世界所有使用這個程式的使用者都可以享受到您的貢獻,也算是對 GNU/Linux 系統做出一番貢獻喔。

由於程式是不停的在改版的,不時會有新的訊息加到程式裡面,也時常會有修改原有訊息的情形,所以不要把翻譯 PO 檔案當成可以一次就搞定的工作,這些訊息翻譯的持續維護是很重要的問題,訊息翻譯者應該要時時檢查自己負責的程式訊息是否有更新的情況,定時下載新的檔案 來更新、保持翻譯結果的同步,這種持之以恆的持續維護也是很重要的喔。


4.2 翻譯工作團體

翻譯一個檔案是很簡單,可是要持續維護的工作就不容易了,因此世界各地都有熱心人士出來組織翻譯工作團體,希望可以簡化 PO 檔案的下載與更新工作以擴大參與的層面,更藉著網站的協調機制避免有兩人以上重複翻譯同一份訊息,造成人力資源的浪費,還可以保存過去翻譯的結果、持續 PO 檔案翻譯結果的維護工作。在台灣先是有李柏峰大哥 出面擔任 GNU 下游翻譯組織 zh@li.org 的負責人,後有薛景中 、陳更欣 等先進發起 "i18n 程式中文化計畫" 來統合有興趣參與翻譯工作的力量,讓有興趣幫忙翻譯的朋友可以透過這個網站取得最新的 PO 檔案,也可以在這邊找到翻譯的技巧跟術語翻譯的標準,圖 4 就是這個網站的首頁,其網址如下,有心進行翻譯工作的朋友請務必進去看看:

http://i18n.linux.org.tw/
Figure 4: i18n 程式中文化計畫首頁

5 結語

看過上面的例子以後,讀者是不是也覺得用 gettext 來讓程式支援多國語言的訊息是很簡單的一件事情呢?在當前軟體國際化的風潮底下,讓同一套程式碼可以支援各種不同語言的訊息已經是寫程式的人都必須面對的一件工作,GNU gettext 套件提供的架構可以讓我們輕易的完成這件任務,有在 GNU/Linux 底下撰寫應用程式的讀者可要好好善加利用。

對單純的使用者而言,如何用 gettext 來設計程式大概不是大多數人關心的問題,大家最在意的還是在使用程式時能不能看到訊息用自己熟悉的語言來顯示出來吧?我們在網路上不時可以聽到有人抱怨 『GNU/Linux 底下怎麼全都是英文,中文化那麼差』,假如中文化指的是進去看不到中文訊息的話,那可就真的是冤枉了,其實缺少中文訊息這一點在技術上是最簡單的問題,比 起前幾期講的東西簡單許多,而且還是任何人都可以一起來參與來改善的。可惜的是,跟其他國家的網友們參與的程度比起來,台灣朋友的熱情似乎是少了一點,參 與的人少, 再加上國內的一些參與 GNU/Linux 市場的商業公司雖然都做過訊息翻譯的工作,至今卻還沒能集中並持續的來維護中譯的訊息。目前訊息中譯的成果絕大多數都還是由網路上少數幾個熱心網友們貢獻 出來的,也因此目前 GNU/Linux 底下大多數的應用程式都還是沒有中文訊息。我們在這邊呼籲有心要改善這個問題的朋友或團體參考第 4.2 節裡面的說明,一起來加入 i18n.linux.org.tw,只要看得懂英文、會編輯文字檔案就可以幫 GNU/Linux 底下的應用程式加上中文的訊息,大家一起來,GNU/Linux 的明天會更好,您說是不是呢?


Yuan-Chung Cheng
2000-08-15

台南小新 發表在 痞客邦 PIXNET 留言(0) 人氣()