Han Simplify

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

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name Han Simplify
// @name:zh 汉字转换为简体字
// @description 将页面上的汉字转换为简体字,需要手动添加包含的网站以启用
// @namespace https://github.com/tiansh
// @version 1.7
// @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==

/** @type {'t2s'|'s2t'} */
const RULE = 't2s';

/* global RULE */
/**
 * @name RULE
 * @type {'t2s'|'s2t'}
 */
/* eslint-env browser, greasemonkey */

; (async function () {

  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();
  const hasOwnProperty = Object.prototype.hasOwnProperty;
  /** @param {string} text */
  const translate = function (text) {
    let output = '';
    let state = 0;
    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;
  };

  // Do not characters marked as following languages
  const skipLang = /^(?:ja|ko|vi)\b/i;
  // Change lang attribute so correct fonts may be available
  const fromLang = {
    t2s: /^zh\b(?:(?!.*-Hans)-(?:TW|HK|MO)|.*-Hant|$)/i,
    s2t: /^zh\b(?:(?!.*-Hant)-(?:CN|SG|MY)|.*-Hans|$)/i,
  }[RULE];
  // Overwrite language attribute with
  const destLang = { t2s: 'zh-Hans', s2t: 'zh-Hant' }[RULE];

  /** @type {WeakMap<Text|Attr, string>} */
  const translated = new WeakMap();
  /** @param {Text|Attr} node */
  const translateNode = function (node) {
    if (!node) return;
    if (translated.has(node) && translated.get(node) === node.nodeValue) return;
    if (/^\s*$/.test(node.nodeValue)) return;
    const result = translate(node.nodeValue);
    translated.set(node, result);
    node.nodeValue = result;
  };

  /** @enum {number} */
  const filterResult = {
    TRANSLATE: 0, // Translate this node
    SKIP_CHILD: 1, // Translate this node but not its children
    SKIP_LANG: 2, // Do not translate nodes in certain language
    SKIP_NODE: 3, // Do not translate this node and its children
  };
  /**
   * @param {Document|Element|Text} node
   * @param {filterResult} context
   */
  const nodeFilter = function (node, context = null) {
    // DOM Root
    if (node instanceof Document) return filterResult.TRANSLATE;
    // Inherit skip
    if (context === filterResult.SKIP_NODE || context === filterResult.SKIP_CHILD) return filterResult.SKIP_NODE;
    // Text Node
    if (node instanceof Text) return context === filterResult.TRANSLATE ? filterResult.TRANSLATE : filterResult.SKIP_NODE;
    // Skip other unknown nodes
    if (!(node instanceof Element)) return filterResult.SKIP_NODE;
    // Do not translate nodes which marked no translate
    if (node.classList.contains('notranslate')) return filterResult.SKIP_NODE;
    const translate = node.getAttribute('translate');
    if (translate === 'no') return filterResult.SKIP_NODE;
    // Do not translate content of certain type elements
    const tagName = node.tagName;
    let child = true;
    if (['CODE', 'VAR'].includes(tagName) && translate !== 'yes') child = false;
    else if (['SVG', 'MATH', 'SCRIPT', 'STYLE', 'TEXTAREA'].includes(tagName)) child = false;
    else if (node.getAttribute('contenteditable') === 'true') child = false;
    const lang = node.getAttribute('lang');
    // If no language is specified
    if (!lang) {
      if (child) return context;
      return context === filterResult.TRANSLATE ? filterResult.SKIP_CHILD : filterResult.SKIP_NODE;
    }
    // If text in languages that should be ignored
    if (skipLang.test(lang)) {
      if (child) return filterResult.SKIP_LANG;
      else return filterResult.SKIP_NODE;
    }
    if (fromLang.test(lang)) {
      node.setAttribute('ori-lang', node.getAttribute('lang'));
      node.setAttribute('lang', destLang);
    }
    if (child) return filterResult.TRANSLATE;
    else return filterResult.SKIP_CHILD;
  };
  /** @param {Text|Element} node */
  const nodeFilterParents = function (node) {
    const parents = [];
    for (let p = node; p; p = p.parentNode) parents.push(p);
    return parents.reverse().reduce((context, node) => nodeFilter(node, context), filterResult.TRANSLATE);
  };
  /**
   * @param {Node} node
   * @param {filterResult} context
   */
  const translateTree = function translateTree(node, context) {
    if (node instanceof Text) {
      if (context === filterResult.TRANSLATE) translateNode(node);
    } else if (node instanceof Element) {
      const filter = nodeFilter(node, context);
      if (filter === filterResult.SKIP_CHILD || filter === filterResult.TRANSLATE) {
        const tagName = node.tagName, attrs = node.attributes;
        if (['APPLET', 'AREA', 'IMG', 'INPUT'].includes(tagName)) translateNode(attrs.alt);
        if (['INPUT', 'TEXTAREA'].includes(tagName)) translateNode(attrs.placeholder);
        if (['A', 'AREA'].includes(tagName)) translateNode(attrs.download);
        translateNode(attrs.title);
        translateNode(attrs['aria-label']);
        translateNode(attrs['aria-description']);
      }
      if (filter === filterResult.TRANSLATE || filter === filterResult.SKIP_LANG) {
        [...node.childNodes].forEach(child => { translateTree(child, filter); });
      }
    } else if (node instanceof Document) {
      [...node.childNodes].forEach(child => { translateTree(child, filterResult.TRANSLATE); });
    }
  };
  /** @param {Text|Element} container */
  const translateContainer = function (container) {
    const filter = nodeFilterParents(container);
    if (filter !== filterResult.SKIP_NODE) translateTree(container, filter);
  };

  const observer = new MutationObserver(function onMutate(records) {
    const translateTargets = new Set();
    records.forEach(record => {
      if (record.type === 'childList') {
        [...record.addedNodes].forEach(node => translateTargets.add(node));
      } else {
        translateTargets.add(record.target);
      }
    });
    [...translateTargets].forEach(translateContainer);
  });
  observer.observe(document, { subtree: true, childList: true, characterData: true, attributes: true });

  if (document.readyState === 'complete') {
    translateContainer(document);
  } else document.addEventListener('DOMContentLoaded', () => {
    translateContainer(document);
  }, { once: true });

}());