为不应该被翻译的网页内容添加不允许翻译标识.
// ==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
// ==/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()
}
})()