// ==UserScript== // @name notranslate-enhanced // @name:en notranslate-enhanced // @description 为不应该被翻译的网页内容添加不允许翻译标识. // @description:en Add a "Do Not Translate" label to web page content that should not be translated. // @version 1.0 // @author ExcuseLme // @namespace https://github.com/ExcuseLme // @match *://*/* // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTEyLjg3IDE1LjA3bC0yLjU0LTIuNTEuMDMtLjAzYzEuNzQtMS45NCAyLjk4LTQuMTcgMy43MS02LjUzSDE3VjRoLTdWMkg4djJIMXYxLjk5aDExLjE3QzExLjUgNy45MiAxMC40NCA5Ljc1IDkgMTEuMzUgOC4wNyAxMC4zMiA3LjMgOS4xOSA2LjY5IDhoLTJjLjczIDEuNjMgMS43MyAzLjE3IDIuOTggNC41NmwtNS4wOSA1LjAyTDQgMTlsNS01IDMuMTEgMy4xMS43Ni0yLjA0ek0xOC41IDEwaC0yTDEyIDIyaDJsMS4xMi0zaDQuNzVMMjEgMjJoMmwtNC41LTEyeG0tMi42MiA3bDEuNjItNC4zM0wxOS4xMiAxN2gtMy4yNHoiIGZpbGw9IiM2NjYiLz48bGluZSB4MT0iMyIgeTE9IjMiIHgyPSIyMSIgeTI9IjIxIiBzdHJva2U9IiNGRjAwMDAiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBvcGFjaXR5PSIwLjgiLz48L3N2Zz4= // @grant none // @run-at document-start // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/568224/notranslate-enhanced.user.js // @updateURL https://update.greasyfork.icu/scripts/568224/notranslate-enhanced.meta.js // ==/UserScript== (function () { 'use strict' /** * @constant CONFIG * 工程化配置区域:最小化改动点集中于此 */ const CONFIG = { // --- 自定义配置区 --- CUSTOM_TAGS: [ 'cite', 'address' ], // 1. 自定义元素标签 CUSTOM_CLASSES: [ 'no-auto-trans', 'code-block', 'pre' ], // 2. 自定义Class CUSTOM_ATTRIBUTES: { // 3. 自定义属性KV 'data-no-translate': 'true', 'itemprop': 'code' }, // --- 默认强制保护规则 --- FORCE_SELECTORS: [ 'pre', 'code', 'kbd', 'var', 'samp', '.gist', '.CodeMirror', '.monaco-editor', '.syntax', '[class*="lang-"]', '[class*="language-"]' ], // --- 内部标准属性 --- PROT_CLASS: 'notranslate', PROT_ATTR : 'translate', PROT_VALUE: 'no', MANAGED_KEY: 'data-nt-managed' } /** * 动态合并自定义规则到选择器池 */ function initForceSelectors () { const customTags = CONFIG.CUSTOM_TAGS.join(',') const customClasses = CONFIG.CUSTOM_CLASSES.map(c => `.${c}`).join(',') const customAttrs = Object.entries(CONFIG.CUSTOM_ATTRIBUTES) .map(([k, v]) => `[${k}="${v}"]`).join(',') const all = [customTags, customClasses, customAttrs].filter(Boolean) if (all.length > 0) { CONFIG.FORCE_SELECTORS.push(...all) } } /** * 核心动作:双重标记增强 */ function applyDualProtection (el) { if (!el || el.nodeType !== 1) return const hasClass = el.classList.contains(CONFIG.PROT_CLASS) const hasAttr = el.getAttribute(CONFIG.PROT_ATTR) === CONFIG.PROT_VALUE // 命中规则或已具备部分特征则补全 if (hasClass || hasAttr || isForceTarget(el)) { if (!hasClass) el.classList.add(CONFIG.PROT_CLASS) if (!hasAttr) el.setAttribute(CONFIG.PROT_ATTR, CONFIG.PROT_VALUE) el.setAttribute(CONFIG.MANAGED_KEY, 'true') } } function isForceTarget (el) { return CONFIG.FORCE_SELECTORS.some(selector => { try { return el.matches(selector) } catch (e) { return false } }) } function scanAndProtect (root) { const selector = CONFIG.FORCE_SELECTORS.join(',') if (selector) { root.querySelectorAll(selector).forEach(applyDualProtection) } root.querySelectorAll(`.${CONFIG.PROT_CLASS}, [${CONFIG.PROT_ATTR}="${CONFIG.PROT_VALUE}"]`) .forEach(applyDualProtection) } function init () { initForceSelectors() const observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.type === 'attributes') { applyDualProtection(mutation.target) return } mutation.addedNodes.forEach(node => { if (node.nodeType === 1) { applyDualProtection(node) scanAndProtect(node) } }) }) }) observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', CONFIG.PROT_ATTR] }) scanAndProtect(document) } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init) } else { init() } })()