由於歷史因素,中文的寫法分為正體中文 (traditional Chinese) 和簡體中文 (simplified Chinese) 兩種。對於華人來說,由於只是同一種語言的不同寫法,稍加學習後,兩種文字都能閱讀。不過,如果能根據不同網站訪客的習慣給予相對應的中文文字,對於網站來說算是加分項目。本文以原有內容為正體中文的網站為前提,說明如何轉成相對應的簡體中文網站;若原本網站是簡體,反過來操作也是可以的。
{{< figure src="img/blog/zh-convert.png" alt="正簡轉換" width="70%" >}}
基本原理
比起文字翻譯,正簡 (或繁簡) 轉換會來得簡單一些,因為正體字和簡體字算是同一種語言的變體,文字順序上幾乎不會有變動。正簡轉換注重的是字詞間的轉換,依照轉換的粒度 (granularity),可分為 (1) 字和字對轉和 (2) 詞和詞對轉兩種。
字和字對轉會比較簡單,我們只要備好一份正簡字元表 (character sheet),依序將文字逐一轉過去即可,不會用到什麼高深的演算法。但這種轉法的缺點在於文字不道地,例如,文字是簡體字但詞語是正體字習慣的用法,對於簡體文字的讀者來說,讀起來就沒那麼親切,反之亦然。不過要注意簡體中文一字多義的情形比正體中文顯著,轉換時可能會造成錯誤。
詞和詞對轉同樣也是要備好一份正簡詞語表 (term sheet),但是直接對轉可能會發生問題,因為可能會切到錯誤的地方。比較好的方法是對句子做解析後,將其轉為一條詞語串列,再逐一轉換詞語的部分。透過這樣的流程,會得到比較道地的文字,但在演算法上會比較複雜,而且轉換的過程仍無法達到完美的境界。
如果想做詞和詞對轉,但又想用較簡單的實作,可以進行有限度的對轉。一般來說,可以只轉換文字中和專有名詞相關的部分,這些名詞的位置切錯的機率比日常用語會來得低一些,其他的部分則只進行字和字對轉即可。
像之前小有名氣的新同文堂瀏覽器外掛,就是以字和字對轉為主,再加上少量的詞和詞對轉。相較起來,OpenCC 則加入比較多的詞和詞對轉方面的功能。
演算思維
此處假定原本網頁是正體中文,想要轉簡體中文,進行字和字對轉。其過程如下:
- 得知網站使用者所用的語系 (locale)
- 判定該使用者的語系是否為簡體中文
- 若使用者的語系是簡體中文,進行以下動作:
- 讀取頁面中的文字
- 根據正簡字元表將文字逐一代換
- 將頁面文字置換為轉換後的結果
- 若使用者的語系非簡體中文,則不進行任何動作
若頁面原本是簡體中文,想轉為正體中文,仍是用相同的邏輯來操作,只是將條件代換掉。
程式實作
我們將整個程式放在網頁前端,這樣的好處是減少伺服端的消耗。不過,在讀取的過程中,如果讀取速度較慢,會看得出來網頁文字在轉換的過程。如果不想讓使用者看到網頁文字轉換的過程,可以將這個過程放在網頁後端,但實作的方式就會和本文相異。
如果不想自己做字元表,可以找現成的,像是 chinese-conv 專案內就放了同文堂的字元對照表 (像是 Tongwen-ts)。我們在使用這個命令稿時,重點在於字元表的部分,不會用到最下方的函式,可以將該部分移除。
我們將完整的程式碼放在這裡,讀者可自行追蹤代碼。本文會拆解這個程式。
本程式假定正簡字元表已載入,這部分請讀者自行完成。
我們這個程式會在整個頁面的文字都讀取後才觸發,所以會放在以下事件處理器的 callback 中:
document.addEventListener("DOMContentLoaded", function () {
// Implement your code here.
});
撰寫判斷簡體中文語系的函式如下:
var isSimplifiedChinese = function (lang) {
var l = lang.toLowerCase();
return l === "zh-cn" || l === "zh_zn" ||
l === "zh-sg" || l === "zh_sg" ||
l === "zh-hans" || l === "zh_hans" ||
l === "zh";
};
lang
是一個表示語系的字串,我們將其轉小寫,減少因大小寫相異而造成的誤判。理論上簡體中文最常用的語系是 zh-CN
,但不一定每個瀏覽器都會使用這個語系,所以我們多用幾個相關的語系來判定,減少誤判。
實際轉換字串的函式如下:
var zhmap = TongWen_ts;
var convertZh = function (selector) {
let es = document.querySelectorAll(selector);
for (let i = 0; i < es.length; i++) {
es[i].innerHTML = es[i].innerHTML.replace(/[^\x00-\xFF]/g, function (s) {
return s in zhmap ? zhmap[s] : s;
});
}
};
selector
是代表 CSS selector 的字串,我們會根據 selector
選出所有的 HTML 元素 (elements)。接著,走訪這些元素;對於每個元素,我們以 innerHTML
取出內部的 HTML 字串,將文字的部分代換掉即可。要注意不能用 innerText
,因為使用 innerText
代換後該元素內部的子元素會消失。
實際執行程式的過程如下:
var lang = navigator.language || navigator.userLanguage;
if (isSimplifiedChinese(lang)) {
let elements = ["h1", "h2", "p", "li", "a"];
for (let i = 0; i < elements.length; i++) {
convertZh(elements[i]);
}
}
我們會根據 navigator.language
或 navigator.userLanguage
(IE 限定) 的值來決定網站使用者的語系 (locale)。使用者可透過調整網頁使用的偏好語系來調整這個值。
當網站使用者偏好簡體中文的語系時,我們就進行轉換的動作。要針對那些元素去轉換會依各個網站的需求有所不同,不用死記這些項目。
繼續深入
透過這個簡單的小型程式,我們的確可以得到正簡轉換的功能。不過,我們這個程式仍有許多地方可以改進:
- 加入選擇語系的選單
- 記住使用者的偏好
- 實作詞和詞轉換的功能
- 針對港澳用語轉換
在我們這個程式中,轉換的過程是自動發生的,使用者無法手動調整。但只要透過一些選單和相關的事件處理器,就可以讓使用者手動轉換。透過網站提供的小工具,使用者就不用再進瀏覽器的選項去調整語系。
目前此程式的語系會和使用者的瀏覽器設定連動,但我們可以另外儲存使用者的偏好。語系這類非機密性的資料,直接存在 cookie 也無妨;此外,網頁前端程式也可以讀取 cookie。如果網站原本沒有會員 (members) 的功能,為了使用者偏好去實作會員子系統似乎也太費工了。
這個程式只做到字和字對轉,如果能加上詞和詞對轉的話,文字讀起來會更道地。若在前端放入整個詞和詞對轉的詞語表和程式,可能讀取時間會過長,不利於使用者經驗;不過,只進行有限度的轉換應該還是可以的。
雖然港澳地區也是使用正體中文,但港澳和台灣在許多詞語上相異,如果可以的話,最好也能寫一份詞對詞轉換的程式。