Han Simplify

将页面上的汉字转换为简体字,需要手动添加包含的网站以启用

目前为 2022-03-19 提交的版本。查看 最新版本

// ==UserScript==
// @name Han Simplify
// @name:zh 汉字转换为简体字
// @description 将页面上的汉字转换为简体字,需要手动添加包含的网站以启用
// @namespace https://github.com/tiansh
// @version 1.1
// @resource t2s https://tiansh.github.io/reader/data/han/t2s.json
// @include *
// @exclude *
// @grant GM_getResourceURL
// @grant GM.getResourceUrl
// @run-at document-start
// @license MIT
// @supportURL https://github.com/tiansh/us-han-simplify/issues
// ==/UserScript==

/* eslint-env browser, greasemonkey */

; (async function () {
  /** @type {'t2s'|'s2t'} */
  const RULE = 't2s';

  const fetchTable = async function (url) {
    return (await fetch(url)).json();
  };
  const loadTable = async function () {
    try {
      return fetchTable(GM_getResourceURL(RULE));
    } catch {
      return fetchTable(await GM.getResourceUrl(RULE));
    }
  };

  /** @type {{ [ch: string]: [string, number] }[]} */
  const table = await loadTable();
  /** @param {string} text */
  const translate = function (text) {
    let output = '';
    let state = 0;
    const hasOwnProperty = Object.prototype.hasOwnProperty;
    for (let char of text) {
      while (true) {
        const current = table[state];
        const hasMatch = hasOwnProperty.call(current, char);
        if (!hasMatch && state === 0) {
          output += char;
          break;
        }
        if (hasMatch) {
          const [adding, next] = current[char];
          if (adding) output += adding;
          state = next;
          break;
        }
        const [adding, next] = current[''];
        if (adding) output += adding;
        state = next;
      }
    }
    while (state !== 0) {
      const current = table[state];
      const [adding, next] = current[''];
      if (adding) output += adding;
      state = next;
    }
    return output;
  };

  const correctLangTags = function () {
    [...document.querySelectorAll('[lang]:not([hanconv-lang])')].forEach(element => {
      const lang = element.getAttribute('lang');
      element.setAttribute('hanconv-lang', lang);
      if (RULE === 't2s' && /^zh\b(?:(?!.*-Hans)-(?:TW|HK|MO)|.*-Hant|$)/i.test(lang)) {
        element.setAttribute('lang', 'zh-Hans');
      }
      if (RULE === 's2t' && /^zh\b(?:(?!.*-Hant)-(?:CN|SG|MY)|.*-Hans|$)/i.test(lang)) {
        element.setAttribute('lang', 'zh-Hant');
      }
      if (!/^(?:ja|ko|vi)\b/i.test(lang)) {
        element.setAttribute('hanconv-apply', 'apply');
      }
    });
  };

  /** @type {WeakMap<Text|Attr, string>} */
  const translated = new WeakMap();
  /** @param {Element} element */
  const needTranslateElement = function (element) {
    if (element.matches('script, style')) return false;
    if (element.closest('svg, math, .notranslate, [translate="no"], code:not([translate="yes"]), var:not([translate="yes"])')) return false;
    const lang = element.closest('[lang]');
    return lang == null || lang.hasAttribute('hanconv-apply');
  };
  /** @param {Text|Attr} node */
  const needTranslate = function (node) {
    if (translated.has(node) && translated.get(node) === node.nodeValue) return false;
    if (/^\s*$/.test(node.nodeValue)) return false;
    const element = node instanceof Text ? node.parentElement : node.ownerElement;
    if (!element) return true;
    return needTranslateElement(element);
  };
  /** @param {Text|Attr} node */
  const translateNode = function (node) {
    if (!needTranslate(node)) return;
    const result = translate(node.nodeValue);
    translated.set(node, result);
    node.nodeValue = result;
  };
  const translateContainer = function (container) {
    if (container instanceof Text || container instanceof Attr) {
      translateNode(container);
    } else if (container instanceof Element) {
      const nodes = document.evaluate([
        // Translate text
        '//text()',
        // Translate attributes
        '(//applet|//area|//img|//input)/@alt',
        '(//a|//area)/@download',
        '//@title',
        '//@aria-label', '//@aria-description',
      ].join('|'), container, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
      for (let node; (node = nodes.iterateNext());) {
        translateNode(node);
      }
    }
  };

  let timeout = null;
  const updateInterval = 200;
  const translateTargets = new Set();
  const observer = new MutationObserver(function onMutate(records) {
    correctLangTags();
    records.forEach(record => {
      if (record.type === 'childList') {
        [...record.addedNodes].forEach(node => translateTargets.add(node));
      } else {
        translateTargets.add(record.target);
      }
    });
    if (!timeout) {
      const targets = [...translateTargets].filter((node, _, arr) => !arr.some(other => other !== node && other.contains(node)));
      if (!targets.length) return;
      translateTargets.clear();
      targets.forEach(translateContainer);
      timeout = setTimeout(() => { timeout = null; onMutate([]); }, updateInterval);
    }
  });
  observer.observe(document, { subtree: true, childList: true, characterData: true, attributes: true });

}());

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址