notranslate-enhanced

为不应该被翻译的网页内容添加不允许翻译标识.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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()
  }
})()