PHP 網頁訊息國際化與 gettext 使用經驗
PHP 很早就已經支援 GNU gettext 的國際化模組。在線上手冊與網路上也可以找到許多相關的討論文章。 但是部份內容沒有完善地理解區碼的設定方式,以至於程序員在 LANG 和 setlocale() 的問題之中糾纏不休。
本文首先將說明區碼的設定問題,再示範 gettext 的使用方式。
區碼的問題
當我們想要用 gettext 將我們以 PHP 設計的網站添加國際化的區域性訊息功能時,首先我們必須要使用 setlocale() 指定訊息區碼;這點與一般應用程式透過環境變數 LANG 的方式不同。例如《Vala with GNU gettext》中的範例,調用 setlocale() 時都不指定區碼,而由環境變數決定。但這種作法不適用於 PHP 建立的 Web 應用程式。因為 PHP 程式係由 httpd 服務行程調用,而 httpd 行程在系統啟動時便已載入,其環境變數已經固定。故我們基本上不應透過環境變數取定訊息區碼,而應使用 setlocale() 指定明確的區碼。
初學者在使用 setlocale() 設定區碼時,最常反應的問題是「我明明照文章上的寫法 setlocale(LC_ALL, "zh_TW")了,但是仍然不會顯示中文訊息」。這個問題的正確答案是「你的系統不認得 zh_TW 這個區碼」。基於歷史與傳統因素,I18N 的區碼並沒有採用嚴格的格式,同一個文化語系在不同的系統上,可能是用不同的區碼表示。例如臺灣地區正體中文語系的區碼,有些系統是用 "zh_TW",有些卻是 "zh_TW.utf8",甚至更早期的還有 "zh_TW.big5"。區區數字之差,就是令使用者抱怨明明設定了環境變數或 setlocale() 了,但軟體顯示的訊息還是沒變的原因。
php-gettext 可用的區碼係由作業系統的區碼表決定。執行指令 locale -a 將會列出作業系統目前的區碼表。 以 Ubuntu 10.04 為例,在我的系統設置上, locale -a 列出的臺灣區碼只有'zh_TW.utf8'。故我調用 setlocale() 時,必須指定 'zh_TW.utf8',gettext 才會正常地取得本地訊息。指定 'zh_TW' 則不會有影響。 但是有些系統並不是用這個名稱,例如有些系統用 'zh_TW'。此時我傳 'zh_TW.utf8' 給 setlocale() 反而錯了。
克服此系統設置差異的解決方案有二種:
-
修改區碼名稱表。
在 Debian/Ubuntu 家族中,區碼名稱表的文件名稱是 /etc/locale.alias 。 但這個方案需要經由系統管理者操作,則會影響到整個作業系統,並不建議採用。 -
調用 setlocale() 時,給它多個區域代碼。
PHP 4.3.0 之後,setlocale() 允許傳多個區域代碼給它,它將逐一嘗試直到可用為止。 下節將細說此方案。
setlocale 的用法
PHP 4.3.0 之後,setlocale() 允許傳多個區域代碼給它,它將逐一嘗試直到可用為止。例如
setlocale(LC_ALL, "zh_TW.utf8", "zh_TW", "zh");
至於舊版的 PHP 用戶,也有解。當 setlocale() 發現你給它的區域代碼不可用時,它會回傳 false 。利用這個特性,舊版 PHP 也可以自行撰寫嘗試動作。下列為範例
使用 gettext 顯示區域化訊息
當我們正確地理解 setlocale() 與區碼的使用方式後,接著就要使用 gettext 取出本地化訊息顯示了。
在動手撰寫或修改程式之前,你需要先了解關於 gettext 訊息文件的編輯與產生的知識。這部份內容請參考《Vala with GNU gettext》,本文不再複述。
下列的範例程式 hello.php,有兩種執行形式。其一是在命令列執行,第一個參數指定訊息區碼。其二是網頁形式,在網頁網址後加上查詢字串 ?locale=區碼。如果不指定區碼,那麼就會顯示原始訊息 - 倒寫的句子。
首先, hello.php 對於區碼格式採用新的方法從寬認定。例如使用者輸入 'zh_TW' 時,就會產生 {'zh_TW.utf8', 'zh_TW'}
的區碼清單,交給 setlocale() 指定訊息區碼。
指定區碼之後,接著要指示區域化內容放置路徑、文字範圍與訊息字元編碼格式。分別調用 bindtextdomain()、textdomain()、bind_textdomain_codeset() 函數指示前述項目。由於 PHP 被 httpd 行程調用時,其工作目錄就是 PHP 程式碼所在目錄,故我們可以將區域化內容資料夾建立在 PHP 程式碼所在目錄下。例如你的 PHP 程式碼放在 /var/www/my_web ,則可以建立 /var/www/my_web/locale 資料夾與區域化內容的目錄架構;將區域訊息文件(MO文件)放置在相對應用的區碼路徑,如 /var/www/my_web/locale/zh_TW/LC_MESSAGES。
最後使用 php-gettext 的函式取得本地化訊息。php-gettext 提供了多個相關的訊息函式,但以 gettext() 最為常用,並有一個慣例的簡短別名,即 _()。
寫好程式後,我們就可以使用 xgettext 工具,將待處理的訊息文字擷取為 POT 文件,再分別翻譯為各語系區碼的 PO 文件。 最後使用 msgfmt 工具產生 MO 文件後,便大工告成。具體的工具操作如下列所示。
$ xgettext --language=php --from-code=utf-8 --output=hello.pot hello.php
$ cp hello.pot hello-en_US.po
$ edit hello-en_US.po
$ cp hello.pot hello-zh_TW.po
$ edit hello-zh_TW.po
$ msgfmt --output=locale/en/LC_MESSAGES/hello.mo hello-en_US.po
$ msgfmt --output=locale/zh_TW/LC_MESSAGES/hello.mo hello-zh_TW.po
以下為範例程式以命令列形式執行的結果。
$ php hello.php
好你
行一第的息訊長。
行二第的息訊長。
$ php hello.php zh_TW
current locale: zh_TW.utf8
你好
長訊息的第一行。
長訊息的第二行。
$ php hello.php en_US
current locale: en_US.utf8
Hello
First line of long message.
Second line of long message.
以下為範例程式透過 Web 呈現的結果。
參考文件
- Vala with GNU gettext - 本文雖然是說明 Vala 語言使用 gettext 的經驗,但相關觀念卻是相同的。
- GNU gettext manual
- PHP Gettext
- PHP setlocale()
- Localizing PHP web sites using gettext - 資訊有些不足,並沒有十分詳細地說明 locale 的設定方式。
樂多舊回應