技术杂谈:在网页中加入正简 (繁简) 中文自动转换

PUBLISHED ON NOV 29, 2018 — WEB

    由于历史因素,中文的写法分为正体中文 (traditional Chinese) 和简体中文 (simplified Chinese) 两种。对于华人来说,由于只是同一种语言的不同写法,稍加学习后,两种文字都能阅读。不过,如果能根据不同网站访客的习惯给予相对应的中文文字,对于网站来说算是加分项目。本文以原有内容为正体中文的网站为前提,说明如何转成相对应的简体中文网站;若原本网站是简体,反过来操作也是可以的。

    正简转换

    基本原理

    比起文字翻译,正简 (或繁简) 转换会来得简单一些,因为正体字和简体字算是同一种语言的变体,文字顺序上几乎不会有变动。正简转换注重的是字词间的转换,依照转换的粒度 (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) 的功能,为了使用者偏好去实现会员子系统似乎也太费工了。

    这个程序只做到字和字对转,如果能加上词和词对转的话,文字读起来会更道地。若在前端放入整个词和词对转的词语表和程序,可能读取时间会过长,不利于使用者经验;不过,只进行有限度的转换应该还是可以的。

    虽然港澳地区也是使用正体中文,但港澳和台湾在许多词语上相异,如果可以的话,最好也能写一份词对词转换的程序。

    TAGS: WEB