Twitter (x) 中文汉化插件

Twitter (X) 中文汉化插件

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name                Twitter (x) 中文汉化插件
// @namespace           https://github.com/wjm13206/x-i18n-plugin/
// @version             1.0
// @description         Twitter (X) 中文汉化插件
// @author              k1995
// @author              wjm13206
// @match               https://twitter.com/*
// @match               https://x.com/*
// @grant               GM_getResourceText
// @resource            zh-CN https://raw.githubusercontent.com/wjm13206/x-i18n-plugin/master/locales/zh-CN.json
// @require             https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
// @license             MIT
// ==/UserScript==

(function() {
  'use strict';

  const lang = "zh-CN"; // 只支持中文
  const locales = JSON.parse(GM_getResourceText(lang));

  // 初始化翻译
  translatePage();
  
  // 监听DOM变化
  observeDOMChanges();

  function translateElement(el) {
    if (!el || !el.nodeValue) return;

    const txtSrc = el.nodeValue.trim();
    if (!txtSrc) return;

    const key = txtSrc.toLowerCase()
      .replace(/\xa0/g, ' ') // 替换  
      .replace(/\s{2,}/g, ' ');

    if (locales.dict[key]) {
      el.nodeValue = el.nodeValue.replace(txtSrc, locales.dict[key]);
    }
  }

  function shouldTranslateEl(el) {
    const blockTags = ["SCRIPT", "STYLE", "CODE", "PRE", "TEXTAREA"];
    if (blockTags.includes(el.tagName)) {
      return false;
    }
    
    // 跳过特定类名
    if (el.classList) {
      const blockClasses = ["emoji", "icon", "username", "handle"];
      for (let cls of blockClasses) {
        if (el.classList.contains(cls)) {
          return false;
        }
      }
    }
    
    return true;
  }

  function traverseElement(el) {
    if (!shouldTranslateEl(el)) return;

    if (el.nodeType === Node.TEXT_NODE) {
      translateElement(el);
      return;
    }

    for (const child of el.childNodes) {
      traverseElement(child);
    }
  }

  function observeDOMChanges() {
    const observer = new MutationObserver(function(mutations) {
      mutations.forEach(function(mutation) {
        mutation.addedNodes.forEach(function(node) {
          traverseElement(node);
        });
      });
    });

    observer.observe(document.body, {
      subtree: true,
      childList: true,
      characterData: true
    });
  }

  function translatePage() {
    traverseElement(document.body);
    applyCSSOverrides();
  }

  function applyCSSOverrides() {
    if (locales.css) {
      for (const css of locales.css) {
        if ($(css.selector).length > 0) {
          if (css.key === '!html') {
            $(css.selector).html(css.replacement);
          } else {
            $(css.selector).attr(css.key, css.replacement);
          }
        }
      }
    }
  }
})();