COC2 翻译分段修复

修复 Google 翻译导致段落合并的问题

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         COC2 翻译分段修复
// @namespace    http://tampermonkey.net/
// @version      2024-10-33
// @description  修复 Google 翻译导致段落合并的问题
// @author       LinHQ
// @match        *://*/*
// @grant        GM_addStyle
// @run-at       context-menu
// @license      AGPLv3
// ==/UserScript==

(function () {
  'use strict';

  // 选择器必须指向最内层元素,否则导致虚拟 dom 报错
  // 大段落主要文本
  const target = '.mainText'
  // 零碎段落
  const targetMulti = '.combatCard span,.itemText,span.message'
  // 不翻译
  const noTranslate = 'span.listItemQuantity,div.totalCard .value,.sidebarDetailsContainer,.sidebarControlContainer,.keybindDisplay.trayButtonBind,.hotkeyIndicator'

  GM_addStyle(`
    .mainText .event_log_entry > span:has(font){
      white-space: inherit !important;
    }
    `)

  new MutationObserver((li, ob) => {
    try {
      const operateNodes = new Map([
        ['translate', []],
        ['brfix', []],
      ])

      li.filter(mu => mu.type === 'childList' && !!mu.addedNodes)
        .forEach(mu =>
          mu.addedNodes.forEach(node => {
            if (node.nodeType === 3) {
              // #text 节点这里可以不区分,非 #text 可能是 fragment
              operateNodes.get('brfix').push(node.parentElement?.matches(`${target},${targetMulti}`) ? node.parentNode : undefined)
              operateNodes.get('translate').push(node.parentElement?.matches(noTranslate) ? node.parentElement : undefined)
              return
            }

            // 放下面,因为 #text 没有 querySelectorAll
            const mainText = node?.querySelectorAll(target)
            operateNodes.get('brfix').push(mainText[Math.max(0, mainText.length - 1)], ...node?.querySelectorAll(targetMulti))

            operateNodes.get('translate').push(...node?.querySelectorAll(noTranslate))
          })
        )

      ob.disconnect()
      for (const [k, v] of operateNodes) {
        switch (k) {
          case 'translate':
            v.forEach(node => node?.setAttribute('translate', 'no'))
            break
          case 'brfix':
            v.forEach(node => {
              if (!node?.innerHTML) return

              // 直接换行和空一行需要区分
              // 看看是不是 div 效果比 br 更好,插入   利用默认 line-height 撑起 div
              node.innerHTML = node.innerHTML.replaceAll('\n\n', '<div style="display: block; line-height: inherit;">&nbsp;</div>').replaceAll('\n', '<div style="display: block; line-height: inherit;"></div>')
            })
            break
        }
      }
      ob.observe(document.body, {
        subtree: true,
        childList: true
      })
    } catch (e) {
    }
  }).observe(document, {
    subtree: true,
    childList: true
  })
})()