國際化與本土化
(作者: 謝東翰)
--------------
A. 簡介:
當我們在發展程式時可能會遇到如下的問題: 當一個程式已按照該地區慣用的編碼
系統發展完成,已經可以順利處理該地區的文字語言了。如果這時國外,或使用其
他語文的地區發覺這個程式很好用,而要將它改成適用於他們地區的語文時,一個
很常見的做法就是設法取得該程式的原始碼,然後逐行去改,將隱藏在原始碼中的
文字處理、訊息顯示等部分全部改成當地的語文與編碼系統,最後還要重新編譯程
式,才能讓它上路。
在這裡很明顯我們見到一個問題,要讓一個程式可以適用於當地的語文與編碼系統,
必須直接去改程式碼。萬一該程式正持續發展中,未來出新版本時,整個修改工作
勢必又要從頭來過一遍,相當費時費力,同時也很不容易追趕程式原作者的發展腳
步。
為了解決此問題,在 UNIX 的世界中,便逐步形成了「程式國際化」與「資料本土
化」的標準,讓世界各地的程式開發者得以遵循。在此標準之下,程式碼只要寫過
一遍,就可以適用於所有的語文與編碼系統,只要系統有支援該語文與編碼系統所
需的「本土資料」即可。簡而言之,這裡所採取的概念就是「程式」與「資料」分
離並分開維護的方式。
「程式國際化」簡稱 I18N,其意為 InternationalizatioN 一字中頭尾字母 "I"
與 "N" 中間夾 18 個英文字母,故名。它是在系統底層的函式庫 (即 libc 函式
庫) 中實作一組標準的函式介面,可以讓程式存取該地區語系的種種資訊。有了這
些資訊,程式本身不僅可以不需要修改,就足以處理各國的語文,同時程式本身甚
至連各地區語文的各項細節 (如編碼方式 .... 等) 都不需要知道,因為這些全部
都是由系統函式庫提供的。
「資料本土化」簡稱 L10N,其意為 LocalizatioN 一字中頭尾字母 "L" 與 "N"
中間夾 10 個英文字母,故名。它是將地區語文的各項細節資料分門別類,安裝在
系統底層的資料庫中,以便讓系統函式庫存取,以提供給上頭的應用程式使用。這
些用來描述各地區語文的資料,我們稱之為「區域化資料庫 (locale)」,它們包
括以下的類別 (categories):
1. LC_COLLATE: 該地區文字排序規則,以及正規化表示式 (regular expression)
的比對依據。
2. LC_CTYPE: 該地區所使用的編碼系統、字集、與文字分類、轉換等資訊。
3. LC_MESSAGES: 各應用程式區域化的訊息顯示。
4. LC_MONETARY: 該地區所通行的貨幣格式。
5. LC_NUMERIC: 該地區所通行的數字表示格式。
6. LC_TIME: 該地區所通行的時間、日期表示格式。
對於同一個地區語文而言,除了 LC_MESSAGES 之外,其他所有的類別都是固定的,
不會隨應用程式的不同而有改變。故這些類別的資料就只需準備一份即可,它們是
由系統底層函式庫直接提供,可以讓所有的應用程式分享。至於 LC_MESSAGES 訊
息顯示的部分,由於各應用程式的訊息都不同,故這部分的資料是由應用程式提供。
在這些類別中,決定一個程式是否在該地區已「本土」化的最重要因素,一是
LC_CTYPE,二是 LC_MESSAGES。前者賦與程式處理該地區文字的能力,後者賦與程
式用該地區的語文來顯示的能力,故底下我們就針對這兩個類別作更進一步介紹。
(I) LC_CTYPE 類別:
LC_CTYPE 與文字處理有關,它讓程式可以在不知道該地區所採用的編碼細節之
下,進行文字的處理。它包括以下的資訊:
1. 該地區所使用的字集。所謂「字集」就是指一群文字符號的集合,換言之就
是該地區會用到的所有文字符號。
2. 該字集所採用的編碼方式。一般而言,一個編碼方式所能容納的字數是固定
的,故當我們談到某個編碼系統時,會自然而然將它所代表的字集聯想在一
起。但稍後我們將看到,其實二者的觀念是分開的,特別是在 GNU/Linux 的
系統中 (使用 glibc-2.2.X)。
3. 該字集中每個字的分類與轉換。這一點其實在使用拼音文字的語系中比較有
意義,而在使用象形文字的地區 (如亞洲語系) 則意義比較不那麼大。這裡
指的是拼音文字中字母大小寫的轉換、是不是數字 (阿拉伯數字或十六進位
數字)、是不是標點符號、是否可以印出、是否為電腦控制碼 .... 等等。
由於電腦的發展起源於使用拼音文字的西方世界,故當他們在制定 C 語言
等標準函式庫介面時就特別將這一點列入考慮。顯然這些分類轉換規則會隨
著地區語系的不同而不同。而在使用象形文字的地區,也許沒有所謂大小寫
的分別,但仍然可以大至依既定的規則填入分類表中。
4. 多位元組字元與寬字元的轉換。這是特別為亞洲地區,使用大量非拼音文字
的地區而制定的。因為這些地區所使用的文字符號數目,大大超越了單一一
個位元組 (即 8 位元) 所能表示的範圍,因而往往需要依一定的編碼規則,
將多個位元組合起來代表一個字。然而,在程式中往往不會有這些編碼規則
的資訊,這使得該程式無法從一個位元組串列中辨認出一個一個的文字。因
此系統就提供了這一組多位元組字元與寬字元的轉換機制。由於每個寬字元
所佔的位元組數是固定的,因而一個寬字元就代表一個文字,不管它原先在
多位元組字元形式時是由幾個位元組所組成的。因此,程式本身可以完全不
需要知道各地區的編碼方式,就能採這種方式逐字處理文字,而這些詳盡的
編碼辨識與轉換工作就全部交由系統底層函式庫來完成了。這正是程式國際
化最重要的關鍵。
(II) LC_MESSAGES 類別:
此類別與程式訊息顯示有關。由於各程式會顯示的訊息都不相同,故這些訊息必
須由程式本身提供,而不能像其他類別一樣由系統函式庫提供。在一個國際化的
程式中,程式訊息的部分是與程式碼本身分開維護。在實作上,我們將程式所有
的訊息集中放在一個文件檔中,而該文件檔的訊息開始時只會用程式原作者慣用
的語言來表示。如果我們希望該程式也能顯示其他語文的訊息時,我們只需要去
做翻譯的工作即可,而不必真的去修改程式碼本身。因此,訊息翻譯與程式維護
可以分頭進行,翻譯的工作不需要由程式原作者、或有經驗的程式設計師來做,
只需他熟悉該語文,並對該程式有一定的熟悉度即可。故基本上,任何人都可以
參與翻譯的工作。
當程式編譯安裝完成後,已翻譯成各國語文的訊息檔也會一併安裝入系統的區域
化資料庫中。當程式啟動,需要做訊息顯示時,它會呼叫系統提供的函式介面,
依目前的語系設定 (見後述) 來正確抓取該語文的訊息並顯示出來。萬一目前的
語系設定找不到相對應的訊息翻譯檔時,則程式會自動以其原始的語系來顯示。
B. 區域化資料庫名稱和語系設定:
各地區所屬的區域化資料庫名稱格式如下:
<語系名>_<地區名>[.<編碼系統名>]
其中 [.<編碼系統名>] 有時候會省略。以我們台灣地區所使用的為例:
zh_TW.Big5
其意即為「中文語系」(zh)「台灣地區」(TW)「使用 Big5 編碼系統」。如果將後頭
的 [.<編碼系統名>] 省略掉,就是這個樣子
zh_TW
那這樣的區域化資料庫是用什麼樣的編碼系統呢?就依不同的 UNIX 系統而有不同了。
在大部分早期的 UNIX 系統中,用的可能是 EUC-TW 編碼系統,即 ISO11643,而在
GNU/Linux 系統中,用的則是 Big5,原因是 Big5 是目前台灣地區使用最廣泛的編碼。
如果我們不特別做語系的設定,則程式在啟動時,會以系統預設的語系來運作,一般
而言其區域化資料庫名就是 "C" 或 "POSIX",也就是原始 C 語言所採用的編碼系統
(ASCII) 與英文訊息等等。如果我們希望改變程式運作的語系,則我們必須在程式啟
動前先做好環境語系的設定,也就是設好各類別的環境變數。例如:
LC_CTYPE=zh_TW.Big5; export LC_CTYPE
LC_MESSAGES=zh_TW.Big5; export LC_MESSAGES
經過如上的設定後,則之後我們在此 shell 下啟動程式時,程式就可以處理台灣地區
的 Big5 文字,同時訊息顯示也會是台灣地區 Big5 中文。萬一我們希望程式可以處
理 EUC-TW 的文字,但仍以 Big5 中文顯示訊息時,就這樣設定:
LC_CTYPE=zh_TW.euctw; export LC_CTYPE
LC_MESSAGES=zh_TW.Big5; export LC_MESSAGES
而其他類別的設定方式依此類推。
在很多情況下,我們通常會希望一口氣將所有的類別一口氣設定成相同的語系,也就是
讓我們的整體環境全部處於同一個語系下。當然我們可以用上述的方式一個個類別分別
設定,但除此之外系統還提供了另外兩個環境變數,以方便我們的作業。一是 LANG,
另一個是 LC_ALL。例如我們這樣設:
LC_ALL=zh_TW.Big5; export LC_ALL
其效果就完全等價於將所有的類別全部設定了。而 LANG 的用法也是一樣,所達到的效
果也類似,但意義稍有不同,這裡要留意優先順序的差別。一般系統對這些環境變數的
優先順序是:
LC_ALL > LC_* > LANG
也就是說,任何一個 LC_ 類的變數設定後,會使 LANG 的設定的相對應類別失效。如
果我們完全不設任何的 LC_ 類的環境變數,只單單這麼設
LANG=zh_TW.Big5; export LANG
則所有的類別都會以 LANG 的設定來運作,除非我們特別去設了某個 LC_ 的環境變數,
如此這個類別就會以新的設定來運作 (但其他的類別不變)。相似的道理,如果我們設
了 LC_ALL 的環境變數,則所有的類別設定,包括 LANG 的設定全部會失效,而改以
LC_ALL 的設定來運作。
C. GNU/Linux 下的實作方式:
長久以來,中文化的問題一直是兩岸三地 GNU/Linux 使用者所面臨的一大挑戰。在過
去我們所謂「中文化」的方式,就是將需要處理中文的程式,如終端機、文字編輯器、
郵件程式等的原始碼拿來,一行行改,改成可以處理我們的 Big5 碼。然而之前我們也
提過,這樣的方式相當費時費力,而且每個程式都要修改,也難以跟上國外程式發展團
隊的腳步。
直到近兩三年來,GNU/Linux 的 I18N 與 L10N 環境發展開始起步,我們也才能依循這
些標準,逐步建構我們的中文環境。我們認為,遵循這樣的標準來建構中文環境,才是
徹底的解決之道,我們不能自己關起門來,自行實作一套獨特中文環境,因為這不僅事
倍功半,同時國外的程式開發團體也無法直接支援我們。只要按照 I18N 的標準來發展
程式,不僅可以馬上使用我們的中文,而且也可以在別的語系下使用。
GNU/Linux 的 I18N 環境的發展,到了去年中 glibc-2.2 系列正式問世後,才算完全
成熟。它不僅完全支援 Unicode 環境,同時在 I18N 與 L10N 方面還擁有許多先進的
特色,茲簡述如後:
1. glibc 內部的編碼轉換系統 (iconv) 擁有共同的「基底字集」,該基底字集採用
UCS4 編碼,目前仍持續擴編當中,理論上將可以函蓋世界上所有已知的編碼系統
的轉換對應。透過基底字集,可以達到完善的轉碼機制。同時,由於在各語系下
其多位元組編碼轉成寬字元時,其實就是轉成 UCS4,這在將來 Unicode 通行時,
將有利於資料的處理。
2. glibc 內部擁有數量龐大的編碼轉換表,大部分是各編碼系統與 UCS4 的轉換用,
也有一些是用於不同編碼間直接轉換。所有的轉換表皆採動態模組載入的方式供應
用程式使用,故只有在需要時系統才會自動載入所需的轉換表來執行,使用完畢後
可以卸下,不會浪費記憶體空間。
3. glibc 擁有完整的 I18N 程式呼叫介面,以及完整的 Unicode 輸出入與處理的呼
叫介面。
4. 在區域化資料庫方面,glibc 將「字集」與「編碼」的概念分開。一個區域化資料
庫有一個明確的字集,代表這個地區語文可能會使用到的所有文字符號,而這個字
集可以自由選擇一個編碼系統來套用。例如我們台灣地區的字集包含所有的中文字
(簡繁體都有),如果我們選用 Big5 編碼,則區域化資料庫名稱就是 zh_TW.Big5;
若我們選用 EUC-TW 編碼,則名稱就是 zh_TW.euctw;當然我們甚至可以選用
GB2312 或 Unicode 等編碼來套用。
然而,要注意的是,由於各編碼所能包含的字數是固定的,同時它們也只能包含特
定的字集,因此,僅管我們的中文字集包含了所有的中文字,但一旦選定編碼系統
後,其所能使用的中文字自然僅限於該編碼系統所包含的範圍。因此,假如我們選
用了 Big5,就表示在此環境下我們無法使用簡體字;反之若選用 GB2312 亦然。
5. glibc 中各區域化資料庫預設的編碼系統與傳統的 UNIX 系統稍有不同。所謂的
「預設編碼系統」指的是在 "<語文名>_<地區名>" 這樣的區域化資料庫名稱中,
所採用的編碼系統。傳統的 UNIX 系統中所採用的預設編碼系統多半是依照官方
或業界的標準,例如在台灣地區就是 EUC-TW。而在 glibc 中,則是以當地最廣
泛流通的編碼系統做為預設,故在台灣地區就是 Big5。
更進一步地,glibc 會自動將未登錄的編碼系統名稱,改以其區域化資料庫的預
設編碼系統來取代。例如我們將語系環境設定為 zh_TW.euctw,由於 "euctw" 在
glibc 內部已有登錄,故它就會採用 EUC-TW 做為此環境下的編碼系統。萬一我
們將語系環境設定為 zh_TW.unknown,由於 "unknown" 一字沒有登錄,故 glibc
會自動以 zh_TW 預設的編碼系統 Big5 來取代。
6. 在訊息顯示方面,各應用程式的訊息翻譯只需依各語文地區分別保留一份即可,不
需要分別為不同的編碼系統都保留一份。以台灣地區中文為例,我們只需要一份
zh_TW 的訊息即可,至於它是用 Big5 寫成的,或 EUC-TW 寫成的都沒關係。假如
它原來是以 Big5 寫成的,但我們卻希望以 EUC-TW 來顯示時,glibc 會自動為我
們做好轉碼的工作。
也許有人會問,那我們只需要保留一份 zh (即中文) 的訊息就好了嘛,這樣豈不
是兩岸三地都可使用了?然而這麼做並不恰當,原因是兩岸三地因歷史背景與文化
的隔閡,使得它們各自發展成獨特的語文,或稱「地區性方言」,對同一個句子的
翻譯,可能兩岸三地的翻法都各自不同。故我們不能單純用轉碼的方式,直接將台
灣地區的翻譯轉成簡體字供對岸使用,而必須為他們分別保留一份他們自己的訊息
翻譯。
7. 在訊息翻譯的維護工作,以及系統函式的呼叫介面上,各 UNIX 系統的實作方式都
不盡相同。而在 glibc (或所有的 GNU 系統、程式) 中,則統一使用 GNU gettext,
以方便程式撰寫以及後續的翻譯維護。
8. 在區域化資料庫中,除了上述那幾個傳統的類別之外,新一代的 glibc-2.2 還擴
增了以下的類別:
LC_PAPER, LC_NAME, LC_ADDRESS, LC_TELEPHONE, LC_MEASUREMENT,
LC_IDENTIFICATION
這些是根據 ISO/IEC JTC1/SC22/WG20 N690 (1999/06) 的新規範而來,用以描述
更多的地區性慣例,如住址格式、電話號碼格式、抬頭稱位 .... 等等。
D. X Window 的國際化環境 (Xi18n):
Libc 的國際化與本土化是整個 UNIX 系統的語文處理的基礎,其他應用程式都是以此
基礎出發往上延伸發展,圖形介面的 X Window 系統亦是如此。同樣的,X Window 系
統所面臨的課題也是在於文字處理與訊息顯示這兩大方面。就訊息顯示方面而言,過
去在 libc 的 I18N 與 L10N 標準尚未確立前,曾一度使用資源檔 (X Resources) 來
存放訊息的翻譯,唯此方式普及率不高。現在有了 I18N 與 L10N,只要使用 LC_MESSAGES
類別,就能達到所需的目標。
但在文字處理方面就複雜許多,主要是文字的輸出與輸入兩方面。在圖形介面中,不
同的文字需要不同的字形來顯示,故為了要完整顯示一個地區所有的文字符號,往往
需要同時使用許多不同的字型才能達成。故在 X Window 的區域化資料庫定義中就多
了一個字型集 (FontSet) 的定義。以我們的 zh_TW.Big5 區域化資料庫為例,在使用
XFree86 的 X Window 系統中 (包括 GNU/Linux),我們的字型集就包括了一個
ISO8859-1 的字型,以及一個 BIG5-0 的字型,分別用於顯示英文與 Big5 中文。但
在其他使用非 XFree86 的 UNIX 系統中,其字型集的定義則各有不同。
有了字型集的定義,X Window 系統就會依據 libc 的 LC_CTYPE 編碼辨識與分析機
制,自動從字型集中挑選適當的字型來顯示各個文字。因此,X Window 的應用程式
同樣也不需要知道各語文的編碼細節,使用字型集就能自動完整地顯示出該語文中所
有的文字符號。因而程式可以國際化。
至於文字的輸入方面比起文字顯示還要更複雜些,這裡牽涉到各語系的輸入法問題,
故在此 X Window 系統特別定義了一個 XIM (X Input Method) 的協定來做處理,它
同樣是以 libc 的 LC_CTYPE 類別為基礎運作的。有關這方面的細節,請參閱下一節
「中文輸入」的說明。
參考資料:
1. man setlocale, man locale
2. info libc
3. ISO/IEC JTC1/SC22/WG20 N690:
http://wwwold.dkuug.dk/JTC1/SC22/WG20/docs/n690.pdf
4. Unicode:
http://www.unicode.org/
5. Xlib Programming Manual, 作者: Adrian Nye, 出版者: O'Reilly,
ISBN 1-56592-002-3