您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
将页面上的汉字转换为简体字,需要手动添加包含的网站以启用
- // ==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 });
- }());