Skip to content

写一个 JavaScript 脚本实现网页简繁体转换

Published: at 10:43编辑

很早之前就有想要给全站加上简繁体转换的功能,但一直因为各种原因拖来拖去没完成,这次偶然看见了某网站的便民繁简转换效果,便扒了下代码优化了一番塞进了本站。

0x00 实现思路

我们可以通过 Map 创建一个简繁体一一对应的字符映射,通过遍历获得所有的简体中文汉字,通过映射转换为繁体即可。除此之外我们还可以保存当前的翻译状态,以便下一次来访时不用手动切换语言。

0x01 获取中文汉字

我们来实现第一步,获取页面上所有的汉字。显然,我们当然可以使用正则表达式匹配所有中文字符,但这样的代价就是太占用 CPU 资源了,对于大型网站会导致页面变卡甚至崩溃(是的,我的 Github 今天早上页面崩了好几次),有没有什么好的解决方案呢?答案当然是有的,我们可以通过 document.body.childNodes 作为入口循环遍历其子节点,同时结合 document.body.nodeType 分离文本节点(开始翻译)和 DOM 元素节点(继续遍历)。

实际实现逻辑如下:

// 使用传入的节点 `node` 或者默认使用 `document.body` 作为根节点开始遍历
(node || document.body).childNodes.forEach(child => {
  // 检查当前节点是否为文本节点
  if (child.nodeType === Node.TEXT_NODE && translate.isChineseText(child.textContent)) {
    // 如果是文本节点并且包含中文字符,则调用 `convertTextEncoding` 方法转换文本内容
    child.textContent = translate.convertTextEncoding(child.textContent);
  } 
  // 如果当前节点不是文本节点而是元素节点,并且该元素不在不被翻译的列表中
  else if (child.nodeType === Node.ELEMENT_NODE && !translate.isStopDOMElement(child.tagName)) {
    // 递归调用自身方法,继续处理子节点
    translate.translatePageContent(child);
  }
});

这样,我们便得到需要翻译的文本内容。下面,我们将进行映射部分的编写。

0x02 字符 Map 映射

通过 Map 我们可以很简单的实现一个字符映射关系,这部分很简单,通过现有的对照表很轻松就能完成,这样我们就可以写出实际的代码了:

translate.createEncodingMap = function() {
    // 简体中文字符集
    const sourceChars = '这里填充简体中文汉字';
    // 对应的繁体中文字符集
    const targetChars = '这里填充繁体中文汉字';

    // 初始化一个空的字符映射对象
    const map = {};

    // 遍历简体中文字符集
    for (let i = 0; i < sourceChars.length; i++) {
        // 将简体中文字符与对应的繁体中文字符建立映射关系
        map[sourceChars[i]] = targetChars[i];
    }

    // 返回构建好的字符映射对象
    return map;
};

为了更好的完成开发,我们需要定义一些辅助函数,这些函数封装在 translate 对象中:

// 判断是否是中文
translate.isChineseText = function(text) {
  return /[\u4e00-\u9fa5]/.test(text);
};

// 判断是否是需要跳过的DOM元素
translate.isStopDOMElement = function(tagName) {
  return translate.stopDOMElements.includes(tagName.toUpperCase());
};

接下来让我们进入保存字符状态编码吧~

0x03 保存翻译状态

出于一些原因,这里我更推荐使用 Local Storage 替代源码写的 Cookie,因为 localStorage 可以直接调用增删查改,不必再次封装 Cookie 操作函数。

使用 localStorage.getItem(key) 方法获得对应 key 的值,这里我们使用 targetEncoding 作为示例,实际可以随意指定。在参考 SegmentFault 上的文章后我决定使用更加现代的三元操作符来完成这个操作:

/* 获取翻译状态决定要不要翻译 */
translate.targetEncoding = localStorage.getItem("targetEncoding") === "false" ? false : true; // 三元操作符获取 targetEncoding 的 value(String) 并转换成 Boolean 类型
  if (!translate.targetEncoding) {
    translate.translatePageContent(); // 那就翻译!
}

/* 保存翻译状态以供后续使用 */
localStorage.setItem("targetEncoding", String(translate.targetEncoding)); // 这里保存为 String 类型

0x04 其他优化

除了这些简单的内容,我还做了如下改动:

  1. 替换现有的对照表:我找了很多教程,但他们的对照表都是不太全的,为此我优化了一个新的简繁体对照表
  2. 避免命名空间污染:我是知道很多人写代码可能会通过添加变量前缀的方式避免重复定义,但我这里推荐封装所有逻辑在一个立即执行的函数表达式中,以避免污染全局命名空间
  3. 暴露接口方便调用:你可能注意到了我的代码都处于 translate 对象下,这意味着我可以在页面加载完成后将该对象挂载到 window 对象上实现接口暴露方便页面或其他脚本调用

除此之外,这里还有一些仍可以做的优化,就当作课后作业罢:

  1. 缓存现有的结果
  2. 懒翻译(类似懒加载)以实现性能的优化
  3. 使用迭代代替现有的递归
  4. 错误处理,如果不能保存状态,不妨通过备用的 Cookie 保存,不过不是很推荐这样做

0x05 参考

好了,这就是本文的全部内容了,不出意外本月会发第二期的网站优化建议,敬请关注!您可以订阅我的 RSS 以获取及时更新!


下一篇
纯 CSS 实现自定义浏览器滚动条