🌐 Telegram Translator Pro — Matrix Edition v4.5

Advanced Telegram Web translator with Matrix-style UI, instant language switching & smart cache. Developed by Fer3on_Mod

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         🌐 Telegram Translator Pro — Matrix Edition v4.5
// @namespace    Fer3on_Mod
// @version      4.5
// @description  Advanced Telegram Web translator with Matrix-style UI, instant language switching & smart cache. Developed by Fer3on_Mod
// @author       Fer3on_Mod
// @match        https://web.telegram.org/k/*
// @license MIT
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      translate.googleapis.com
// ==/UserScript==

(function () {
  "use strict";

  // 💠 Styles
  GM_addStyle(`
    #tg-translator-panel {
      position: fixed;
      right: 18px;
      bottom: 18px;
      z-index: 2147483647;
      font-family: "Segoe UI", system-ui, sans-serif;
      user-select: none;
      color: #dfffe6;
    }

    #tg-panel-toggle {
      width: 60px;
      height: 60px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 26px;
      background: radial-gradient(circle at 30% 30%, #00ff99 0%, #0066ff 100%);
      color: white;
      box-shadow: 0 0 25px rgba(0, 255, 136, 0.7);
      cursor: pointer;
      transition: all 0.2s ease;
    }
    #tg-panel-toggle:hover { transform: scale(1.1); filter: brightness(1.2); }

    #tg-panel {
      position: fixed;
      right: 18px;
      bottom: 18px;
      width: 280px;
      background: linear-gradient(180deg, #060a0d, #0a1418);
      border-radius: 14px;
      color: #dfffe6;
      box-shadow: 0 0 25px rgba(0, 255, 136, 0.2);
      padding: 16px;
      display: none;
      backdrop-filter: blur(8px);
      border: 1px solid rgba(0, 255, 136, 0.08);
      animation: slideIn 0.25s ease forwards;
    }

    @keyframes slideIn {
      from { opacity: 0; transform: translateY(10px); }
      to { opacity: 1; transform: translateY(0); }
    }

    #tg-close-btn {
      position: absolute;
      top: -10px;
      left: -10px;
      width: 28px;
      height: 28px;
      border-radius: 50%;
      background: radial-gradient(circle at 30% 30%, #00ff99, #0066ff);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 14px;
      color: #000;
      cursor: pointer;
      box-shadow: 0 0 15px rgba(0,255,136,0.6);
      transition: transform 0.15s ease;
    }
    #tg-close-btn:hover { transform: scale(1.1); }

    #tg-panel h3 {
      margin-top: 0;
      text-align: center;
      color: #00ff88;
      font-weight: 600;
      letter-spacing: 0.5px;
      margin-bottom: 10px;
    }

    .tg-row { display: flex; flex-direction: column; gap: 5px; margin: 10px 0; }

    .tg-select, .tg-input {
      width: 100%;
      padding: 8px;
      border-radius: 8px;
      background: rgba(0,255,136,0.05);
      border: 1px solid rgba(0,255,136,0.15);
      color: #eaffea;
      font-size: 13px;
      outline: none;
    }
    .tg-select option { background: #071018; color: #eaffea; }

    .tg-btn {
      width: 100%;
      padding: 8px;
      margin-top: 8px;
      border-radius: 10px;
      background: linear-gradient(90deg,#0a1f1f,#002244);
      border: 1px solid rgba(0,255,136,0.2);
      cursor: pointer;
      color: #dfffe6;
      font-size: 13px;
      transition: all 0.15s ease;
    }
    .tg-btn:hover { filter: brightness(1.3); }

    .tg-btn.positive { background: linear-gradient(90deg,#00c853,#0091ea); box-shadow: 0 0 10px rgba(0,255,136,0.3); }

    /* Switch style */
    .switch {
      position: relative;
      display: inline-block;
      width: 50px;
      height: 26px;
    }
    .switch input { opacity: 0; width: 0; height: 0; }
    .slider {
      position: absolute;
      cursor: pointer;
      top: 0; left: 0; right: 0; bottom: 0;
      background-color: #555;
      transition: .3s;
      border-radius: 26px;
    }
    .slider:before {
      position: absolute;
      content: "";
      height: 20px; width: 20px;
      left: 3px; bottom: 3px;
      background-color: white;
      transition: .3s;
      border-radius: 50%;
    }
    input:checked + .slider {
      background-color: #00ff88;
      box-shadow: 0 0 10px rgba(0,255,136,0.4);
    }
    input:checked + .slider:before {
      transform: translateX(24px);
    }

    .tg-footer {
      margin-top: 12px;
      text-align: center;
      font-size: 12px;
      opacity: 0.8;
    }
    .tg-footer a { color: #00ff88; text-decoration: none; font-weight: 600; }
  `);

  // 🌐 Panel HTML
  const panelWrap = document.createElement("div");
  panelWrap.id = "tg-translator-panel";
  panelWrap.innerHTML = `
    <div id="tg-panel-toggle" title="Open Translator Menu">🌐</div>
    <div id="tg-panel" role="dialog" aria-label="Translator Pro Settings">
      <div id="tg-close-btn">✕</div>
      <h3>Translator Pro Settings</h3>

      <div class="tg-row">
        <label>Language:</label>
        <select id="tg-lang-select" class="tg-select">
          <option value="ar">Arabic</option>
          <option value="en">English</option>
          <option value="fr">French</option>
          <option value="es">Spanish</option>
          <option value="de">German</option>
          <option value="ru">Russian</option>
          <option value="zh-CN">Chinese (Simplified)</option>
          <option value="ja">Japanese</option>
          <option value="it">Italian</option>
          <option value="pt">Portuguese</option>
          <option value="tr">Turkish</option>
          <option value="ko">Korean</option>
          <option value="hi">Hindi</option>
          <option value="nl">Dutch</option>
          <option value="sv">Swedish</option>
          <option value="pl">Polish</option>
          <option value="uk">Ukrainian</option>
          <option value="id">Indonesian</option>
          <option value="th">Thai</option>
          <option value="fa">Persian</option>
          <option value="he">Hebrew</option>
          <option value="vi">Vietnamese</option>
          <option value="ro">Romanian</option>
          <option value="cs">Czech</option>
          <option value="fi">Finnish</option>
          <option value="el">Greek</option>
        </select>
      </div>

      <div class="tg-row">
        <label><input type="checkbox" id="tg-auto-detect"> Auto-detect Source Language</label>
        <label>Enable Translation:
          <label class="switch">
            <input type="checkbox" id="tg-enabled">
            <span class="slider"></span>
          </label>
        </label>
      </div>

      <button id="tg-clear-btn" class="tg-btn">🧹 Clear Cache</button>

      <div class="tg-footer">
        <a href="https://t.me/Fer3on_Mod" target="_blank">Developer: Fer3on_Mod</a>
        <div>v4.4 — Matrix Edition</div>
      </div>
    </div>
  `;
  document.body.appendChild(panelWrap);

  // 🎛️ DOM refs
  const toggleBtn = document.getElementById("tg-panel-toggle");
  const panel = document.getElementById("tg-panel");
  const closeBtn = document.getElementById("tg-close-btn");
  const langSelect = document.getElementById("tg-lang-select");
  const autoDetectInput = document.getElementById("tg-auto-detect");
  const enableInput = document.getElementById("tg-enabled");
  const clearBtn = document.getElementById("tg-clear-btn");

  // ⚙️ Load Settings
  let state = {
    targetLang: localStorage.getItem("tg_target_lang") || "ar",
    autoDetect: localStorage.getItem("tg_auto_detect") === "true",
    enabled: localStorage.getItem("tg_enabled") !== "false"
  };

  langSelect.value = state.targetLang;
  autoDetectInput.checked = state.autoDetect;
  enableInput.checked = state.enabled;

  // 🧩 Panel toggle behavior
  toggleBtn.onclick = () => {
    panel.style.display = "block";
    toggleBtn.style.display = "none";
  };
  closeBtn.onclick = () => {
    panel.style.display = "none";
    toggleBtn.style.display = "flex";
  };

  // ⚡ Instant apply language change
  langSelect.onchange = () => {
    state.targetLang = langSelect.value;
    localStorage.setItem("tg_target_lang", state.targetLang);
  };

  // ⚡ Instant toggle
  enableInput.onchange = () => {
    state.enabled = enableInput.checked;
    localStorage.setItem("tg_enabled", state.enabled);
    if (!state.enabled) removeAllTranslations();
  };

  autoDetectInput.onchange = () => {
    state.autoDetect = autoDetectInput.checked;
    localStorage.setItem("tg_auto_detect", state.autoDetect);
  };

  clearBtn.onclick = () => {
    translateCache.clear();
    alert("🧹 Cache cleared");
  };

  // 💾 Cache
  const translateCache = new Map();

  // 🧠 Translation Engine
  function translateText(text) {
    return new Promise((resolve) => {
      if (!state.enabled) return resolve(null);
      if (!text.trim() || text.length < 3) return resolve(null);
      if (/^[0-9:\s]+$/.test(text)) return resolve(null);
      if (translateCache.has(text)) return resolve(translateCache.get(text));

      const sl = state.autoDetect ? "auto" : "en";
      const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${sl}&tl=${state.targetLang}&dt=t&q=${encodeURIComponent(text)}`;

      GM_xmlhttpRequest({
        method: "GET",
        url,
        onload: (res) => {
          try {
            const result = JSON.parse(res.responseText);
            const translated = result[0].map(t => t[0]).join("");
            translateCache.set(text, translated);
            resolve(translated);
          } catch {
            resolve(null);
          }
        },
        onerror: () => resolve(null)
      });
    });
  }

  // 🧩 Render translation
  function renderTranslation(el, translated) {
    if (!el || !translated) return;
    if (el.querySelector(".tg-translated-text")) return;
    const div = document.createElement("div");
    div.className = "tg-translated-text";
    div.textContent = translated;
    div.style.marginTop = "6px";
    div.style.color = "#00ff88";
    div.style.fontSize = "0.88em";
    el.appendChild(div);
  }

  // ❌ Remove all translations
  function removeAllTranslations() {
    document.querySelectorAll(".tg-translated-text").forEach(el => el.remove());
  }

  // 🔁 Observe messages
  const observer = new MutationObserver((mutations) => {
    if (!state.enabled) return;
    mutations.forEach((m) => {
      m.addedNodes.forEach(async (node) => {
        if (node.nodeType === 1 && node.querySelector) {
          const msg = node.querySelector(".text-content, .message, .message_text, .text-content-inner");
          if (msg && !msg.querySelector(".tg-translated-text")) {
            const text = msg.innerText.trim();
            const translated = await translateText(text);
            if (translated && translated !== text) renderTranslation(msg, translated);
          }
        }
      });
    });
  });

  const container = document.querySelector("#column-center, .messages-container, body");
  if (container) observer.observe(container, { childList: true, subtree: true });
})();