技術交流

L10N with PHP

何謂 L10n?

開發人員把 Localization 簡寫成 L10N,中間的數字 10 是前後兩個字母 L 和 N 之間的字母個數。它允許開發人員寫一個簡單的文件,就可以將顯示的功能表和文本翻譯成本地語言。

為什麼要使用 L10N?

L10N 標準能夠很好地支援您查看、輸入或處理非英語語言。

本地化設置需要具備三個條件:

  • 語言代碼 ( Language Code)
  • 國家代碼 ( Country Code )
  • 編碼 ( Encoding )

本地名字可以用下面這些部分組成:語言代碼_國家代碼.編碼。例如(zh_CN.UTF-8, en_US等)。

哪些軟體用到 gettext?

  • 大名鼎鼎的個人 blog 系統 wordpress。
  • Linux 系統上大部分應用軟體。

本文主要討論利用 PHP 的 built-in 函數 gettext 來實現程式的本地化。本文假定編碼 ( encoding ) 都為 UTF-8。

自 PHP4 版本開始,PHP已經內建支援 gettext function了,不需要額外安裝。直接打開 php.ini,找到 extension=php_gettext.dll 把前面的分號去掉,重啟 Apache Server。

Gettext 的實現方式

程式設計者在程式碼中寫入所要顯示的欄位的標籤或是相關 messages,在運行程式時並不會直接顯示程式設計師所寫的資訊,它會根據設定的語系去找對應語系的文字,如果沒有找到才會去顯示程式碼中的原本的語言。例如:程式設計者在源代碼裏使用的是繁體中文,如果系統在 run 的時候設定成簡體中文( zh_CN ) 語系,程式會自動到相關的目錄 ( local/zh_CN/LC_MESSAGES/ ) 中尋找簡體中文的語系,如果找不到就會顯示原來的繁體中文,而這一切我們只需要做簡單的設定即可,gettext 會自動幫我們完成這些瑣碎的工作。

相關函數

  • bind_textdomain_codeset — Specify the character encoding in which the messages from the DOMAIN message catalog will be returned
  • bindtextdomain — Sets the path for a domain
  • dcgettext — Overrides the domain for a single lookup
  • dcngettext — Plural version of dcgettext
  • dgettext — Override the current domain
  • dngettext — Plural version of dgettext
  • gettext — Lookup a message in the current domain
  • ngettext — Plural version of gettext
  • textdomain — Sets the default domain

本文只涉及到 bintextdomain()textdomain()bind_text_domain_codeset() 這三個 functions。

Directories Structure

目錄

Local: 自定義名稱,可以按喜好改成 languages 等等,代碼中做相應調整就可以了。

Local\en_US: 英文語系所在資料夾,也可改成自己喜歡的,但是推薦用標準的 language code。

Local\zh_CN: 簡體中文語系所在資料夾,也可改成自己喜歡的,但是推薦用標準的 language code。

LC_MESSAGES:固定名稱,請不要修改。

Messages.mo/po: 多語的檔案名稱,可以按自己的喜好來命名,程式代碼中要做相關調整. .po 可編輯,.mo 不可編輯。

PHP 代碼實現

在程式初始化時,初始化相關多語參數:

  1. 語系代碼
  2. .mo 檔案名稱
  3. 多語文件所在資料夾

  /**
     * Init Local Language
     * 目錄結構必須是
     * xxx/local/zh_CN/LC_MESSAGES/messages.po/.mo
     * xxx/local/zh_TW/LC_MESSAGES/messages.po/.mo
     * @param string $lang_code 標準語言代碼,如: zh_TW, zh_CN
     * @param string $mo_filename mo 檔案名
     * @param string $localfile_url "local" 目錄所在的相對或是絕對路徑
     * @author Dennis 20090826
     * @return void
     */
     function _initLocal ($lang_code,$mo_filename,$localfile_url)
     {
        //設置目的語言
        putenv("LANG=$lang_code");
        setlocale(LC_ALL, $lang_code);
        //$package為 mo 文件的名字
        $package = $mo_filename;
        //綁定 mo 檔的路徑,
        bindtextdomain($package, $localfile_url);
        //設置搜索的 mo 檔的檔案名
        textdomain($package);
        //指定mo檔的返回到gettext的編碼
        bind_textdomain_codeset($package, 'UTF-8');
      }
       _initLocal('en_US','messages', '/ehr/local');
  

多語書寫方式


  include_once 'func.php';
     echo gettext ('資通電腦');
  // 也可以寫成如下
      echo _('資通電腦');
  

創建 .po 多語二進位檔

編輯指令如下:xgettext -d [您定義的 PACKAGE 名稱] [程序文件名]

WIN32 下面的 xgettext、msgfmt 程序檔可以從 ( http://switch.dl.sourceforge.net ... ext-0.10.40-bin.zip (http://switch.dl.sourceforge.net ... ext-0.10.40-bin.zip) ) 下載,需要 libiconv.dll、libintl.dll 的支援。具體的指令使用方法都是相同的。

以上面 test.php 檔為例,xgettext -d message test.php,運行後將產生一個 message.po 檔,內容如下:


  # SOME DESCRIPTIVE TITLE.
    # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
    # This file is distributed under the same license as the PACKAGE package.
    # FIRST AUTHOR , YEAR.
    #
    #, fuzzy
    msgid ""
    msgstr ""
    "Project-Id-Version: PACKAGE VERSION\n"
    "Report-Msgid-Bugs-To: \n"
    "POT-Creation-Date: 2009-08-31 15:24+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=UTF-8\n"
    "Content-Transfer-Encoding: 8bit\n"
  
    #: test.php:2
    msgid "資通電腦"
    msgstr ""
  
    "message.po" 21L, 607C 21,1 All
    # SOME DESCRIPTIVE TITLE.
    # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
    # This file is distributed under the same license as the PACKAGE package.
    # FIRST AUTHOR , YEAR.
    #
    #, fuzzy
    msgid ""
    msgstr ""
    "Project-Id-Version: PACKAGE VERSION\n"
    "Report-Msgid-Bugs-To: \n"
    "POT-Creation-Date: 2009-08-31 15:24+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=UTF-8\n"
    "Content-Transfer-Encoding: 8bit\n"
  
    #: test.php:2
    msgid "資通電腦"
    msgstr ""
  

裏面列出 test.php 檔裏所有調用 gettext 函式的字串,翻譯的時候只需將 msgid 值翻譯填入msgstr 即可,如翻譯成中文。

File message.po 內容如下:


  # SOME DESCRIPTIVE TITLE.
    # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
    # This file is distributed under the same license as the PACKAGE package.
    # FIRST AUTHOR , YEAR.
    #
    #, fuzzy
    msgid ""
    msgstr ""
    "Project-Id-Version: PACKAGE VERSION\n"
    "Report-Msgid-Bugs-To: \n"
    "POT-Creation-Date: 2009-08-31 15:24+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=UTF-8\n"
    "Content-Transfer-Encoding: 8bit\n"
  
    #: test.php:2
    msgid "資通電腦"
    msgstr ""
  
    "message.po" 21L, 607C 21,1 All
    # SOME DESCRIPTIVE TITLE.
    # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
    # This file is distributed under the same license as the PACKAGE package.
    # FIRST AUTHOR , YEAR.
    #
    #, fuzzy
    msgid ""
    msgstr ""
    "Project-Id-Version: PACKAGE VERSION\n"
    "Report-Msgid-Bugs-To: \n"
    "POT-Creation-Date: 2009-08-31 15:24+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=UTF-8\n"
    "Content-Transfer-Encoding: 8bit\n"
  
    #: test.php:2
    msgid "資通電腦"
    msgstr "ARES"
  

創建 MO 檔

.mo 多語文件是二進位,需要用特別的指令或是軟體來創建,檔的內容已編譯成二進位(打開看到的都是亂碼),不能用一般的 text editor 軟體編輯。創建指令如下:msgfmt -o message.mo message.po,運行後將產生一個 message.mo 二進位檔。

最後將 message.po、message.mo 拷貝到相關語系(這裡翻譯的是一個英文語系,把 message.mo、message.po 放到 local/en_US/LC_MESSAGES/)的目錄下即可。

上面討論了利用 PHP gettext built-in functions 來實現應用程式的本地化。其優點:

  • 執行效率高(有興趣的可以測試一下,其效率明顯高於把多語文件放在 php 的 array () 中和 database table 中)。
  • 多語的唯一性,也就是說一個多語在多處引用時,只需要翻譯一次就好(例如 ehr系統中的「員工代碼」,幾乎系統中到處都有用到,用此種方式只需要翻譯一次就好)。
  • 不會遺漏多語未本地化(沒有翻譯的仍會顯示原來的語言,在前台一下就可以辨別)。

當然事情都有兩面性,同樣 gettext 也有缺點:

  • 必須要有特別的編輯(譯)工具。
  • 學習 gettext 相關用法。

參考資料:

閱讀更多