您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
自動翻譯選取文字為繁體中文,整合 Google / DeepL / ChatGPT,底部浮窗,右側歷史紀錄,朗讀功能。
当前为
// ==UserScript== // @name ChatGPT + DeepL + Google 三合一選取翻譯(底部 + 右側歷史 + 朗讀) // @namespace http://tampermonkey.net/ // @version 1.1 // @description 自動翻譯選取文字為繁體中文,整合 Google / DeepL / ChatGPT,底部浮窗,右側歷史紀錄,朗讀功能。 // @author issac // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @connect translate.googleapis.com // @connect api-free.deepl.com // @connect api.openai.com // @license GPL-3.0 License // ==/UserScript== (function() { 'use strict'; const DEEPL_API_KEY = "你的_DeepL_API_KEY"; const OPENAI_API_KEY = "你的_OpenAI_API_KEY"; GM_addStyle(` #translatorBox { position: fixed; bottom: 0; left: 50%; transform: translateX(-50%); width: 100%; max-width: 900px; background: rgba(255,255,255,0.98); border-top: 2px solid #333; border-radius: 12px 12px 0 0; padding: 10px; box-shadow: 0 -4px 15px rgba(0,0,0,0.2); z-index: 999999; font-family: "Microsoft JhengHei", Arial, sans-serif; text-align: center; display: none; } #translatorResult { margin-top: 10px; font-size: 24px; font-weight: bold; color: #111; line-height: 1.6; word-break: break-word; } .engine-btn { cursor: pointer; padding: 8px 14px; margin: 6px; border: none; border-radius: 8px; font-size: 15px; color: #fff; } .google { background: #4285F4; } .deepl { background: #0F7CDA; } .chatgpt { background: #10A37F; } /* 歷史紀錄視窗 */ #historyContainer { position: fixed; top: 20%; right: 0; width: 300px; max-height: 60%; background: rgba(255,255,255,0.95); border: 1px solid #ccc; border-radius: 8px 0 0 8px; padding: 10px; box-shadow: -2px 2px 10px rgba(0,0,0,0.2); z-index: 999998; font-family: Arial, sans-serif; font-size: 14px; overflow-y: auto; display: none; } #historyHeader { cursor: pointer; font-weight: bold; text-align: center; background: #eee; padding: 5px; border-radius: 5px; margin-bottom: 5px; } #historyList div { border-bottom: 1px solid #ddd; padding: 4px; font-size: 13px; } .tts-btn { cursor: pointer; padding: 5px 10px; margin-left: 5px; border: none; border-radius: 5px; font-size: 14px; background: #ff9800; color: white; } `); const box = document.createElement("div"); box.id = "translatorBox"; box.innerHTML = ` <h3 style="margin:0;font-size:18px;">翻譯結果(繁體中文)</h3> <div id="translatorResult">請選取文字</div> <div id="translatorButtons"> <button class="engine-btn google" data-engine="google">Google</button> <button class="engine-btn deepl" data-engine="deepl">DeepL→繁體</button> <button class="engine-btn chatgpt" data-engine="chatgpt">ChatGPT</button> <button class="tts-btn" data-type="original">朗讀原文</button> <button class="tts-btn" data-type="translation">朗讀翻譯</button> </div> `; document.body.appendChild(box); const historyBox = document.createElement("div"); historyBox.id = "historyContainer"; historyBox.innerHTML = ` <div id="historyHeader">📜 歷史紀錄 ▼</div> <div id="historyList"></div> `; document.body.appendChild(historyBox); let selectedText = ""; let currentEngine = "google"; let history = []; let historyVisible = true; document.addEventListener("mouseup", () => { const sel = window.getSelection().toString().trim(); if (sel && sel !== selectedText) { selectedText = sel; box.style.display = "block"; translateText(selectedText, currentEngine); } else if (!sel) { box.style.display = "none"; } }); document.querySelectorAll(".engine-btn").forEach(btn => { btn.addEventListener("click", function() { if (selectedText) { currentEngine = this.dataset.engine; translateText(selectedText, currentEngine); } }); }); document.querySelectorAll(".tts-btn").forEach(btn => { btn.addEventListener("click", function() { const type = this.dataset.type; const text = type === "original" ? selectedText : document.getElementById("translatorResult").innerText; playTTS(text, type); }); }); document.getElementById("historyHeader").addEventListener("click", () => { historyVisible = !historyVisible; document.getElementById("historyList").style.display = historyVisible ? "block" : "none"; document.getElementById("historyHeader").innerText = historyVisible ? "📜 歷史紀錄 ▼" : "📜 歷史紀錄 ▲"; }); function addHistory(original, translation, engine) { history.unshift({ original, translation, engine }); const historyList = document.getElementById("historyList"); historyList.innerHTML = history.map(h => `<div><b>[${h.engine}]</b> ${h.original} → ${h.translation}</div>`).join(""); historyBox.style.display = "block"; } function detectLanguage(text, callback) { fetch(`https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=${encodeURIComponent(text)}`) .then(res => res.json()) .then(data => { const detectedLang = data[2] || "en"; callback(detectedLang); }) .catch(() => callback("en")); } function playTTS(text, type) { if (!text) return; if (type === "original") { detectLanguage(text, lang => { const utterance = new SpeechSynthesisUtterance(text); utterance.lang = lang; speechSynthesis.speak(utterance); }); } else { const utterance = new SpeechSynthesisUtterance(text); utterance.lang = "zh-TW"; speechSynthesis.speak(utterance); } } function translateText(text, engine) { const resultDiv = document.getElementById("translatorResult"); resultDiv.innerText = "翻譯中…"; if (engine === "google") { fetch(`https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=zh-TW&dt=t&q=${encodeURIComponent(text)}`) .then(res => res.json()) .then(data => { const translation = data[0].map(d => d[0]).join(""); resultDiv.innerText = translation; addHistory(text, translation, "Google"); }) .catch(() => resultDiv.innerText = "Google 翻譯失敗"); } else if (engine === "deepl") { GM_xmlhttpRequest({ method: "POST", url: "https://api-free.deepl.com/v2/translate", headers: { "Authorization": "DeepL-Auth-Key " + DEEPL_API_KEY, "Content-Type": "application/x-www-form-urlencoded" }, data: `text=${encodeURIComponent(text)}&target_lang=ZH`, onload: function(res) { try { const data = JSON.parse(res.responseText); const zhCN = data?.translations?.[0]?.text || ""; if (!zhCN) { resultDiv.innerText = "DeepL 翻譯失敗"; return; } fetch(`https://translate.googleapis.com/translate_a/single?client=gtx&sl=zh-CN&tl=zh-TW&dt=t&q=${encodeURIComponent(zhCN)}`) .then(r => r.json()) .then(d => { const translation = d[0].map(x => x[0]).join(""); resultDiv.innerText = translation; addHistory(text, translation, "DeepL"); }) .catch(() => resultDiv.innerText = "繁體轉換失敗"); } catch (err) { resultDiv.innerText = "DeepL 翻譯解析錯誤:" + err.message; } }, onerror: () => resultDiv.innerText = "DeepL API 錯誤" }); } else if (engine === "chatgpt") { if (!OPENAI_API_KEY) { resultDiv.innerText = "請先設定 OpenAI API Key"; return; } GM_xmlhttpRequest({ method: "POST", url: "https://api.openai.com/v1/chat/completions", headers: { "Authorization": "Bearer " + OPENAI_API_KEY, "Content-Type": "application/json" }, data: JSON.stringify({ model: "gpt-4o-mini", messages: [ { role: "system", content: "你是一位專業的翻譯員,請將以下文字翻譯成繁體中文:" }, { role: "user", content: text } ] }), onload: function(response) { try { const res = JSON.parse(response.responseText); if (res?.error?.code === "insufficient_quota") { resultDiv.innerText = "ChatGPT 今日額度已用完"; } else { const msg = res?.choices?.[0]?.message?.content || "ChatGPT 翻譯失敗"; resultDiv.innerText = msg.trim(); addHistory(text, msg.trim(), "ChatGPT"); } } catch (err) { resultDiv.innerText = "ChatGPT 翻譯解析錯誤:" + err.message; } }, onerror: e => resultDiv.innerText = "ChatGPT API 請求錯誤:" + JSON.stringify(e) }); } } })();