位元詩人 技術雜談:在網頁中加入正簡 (繁簡) 中文自動轉換

web
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

由於歷史因素,中文的寫法分為正體中文 (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.languagenavigator.userLanguage (IE 限定) 的值來決定網站使用者的語系 (locale)。使用者可透過調整網頁使用的偏好語系來調整這個值。

當網站使用者偏好簡體中文的語系時,我們就進行轉換的動作。要針對那些元素去轉換會依各個網站的需求有所不同,不用死記這些項目。

繼續深入

透過這個簡單的小型程式,我們的確可以得到正簡轉換的功能。不過,我們這個程式仍有許多地方可以改進:

  • 加入選擇語系的選單
  • 記住使用者的偏好
  • 實作詞和詞轉換的功能
  • 針對港澳用語轉換

在我們這個程式中,轉換的過程是自動發生的,使用者無法手動調整。但只要透過一些選單和相關的事件處理器,就可以讓使用者手動轉換。透過網站提供的小工具,使用者就不用再進瀏覽器的選項去調整語系。

目前此程式的語系會和使用者的瀏覽器設定連動,但我們可以另外儲存使用者的偏好。語系這類非機密性的資料,直接存在 cookie 也無妨;此外,網頁前端程式也可以讀取 cookie。如果網站原本沒有會員 (members) 的功能,為了使用者偏好去實作會員子系統似乎也太費工了。

這個程式只做到字和字對轉,如果能加上詞和詞對轉的話,文字讀起來會更道地。若在前端放入整個詞和詞對轉的詞語表和程式,可能讀取時間會過長,不利於使用者經驗;不過,只進行有限度的轉換應該還是可以的。

雖然港澳地區也是使用正體中文,但港澳和台灣在許多詞語上相異,如果可以的話,最好也能寫一份詞對詞轉換的程式。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。