// ==UserScript== // @name 网页文本翻译工具 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 选中文本后右键翻译成中文,支持多个翻译API // @author Your name // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // API 配置 const API_CONFIG = { BAIDU: { name: '百度翻译', url: 'https://fanyi-api.baidu.com/api/trans/vip/translate', needKey: true }, YOUDAO: { name: '有道翻译', url: 'https://openapi.youdao.com/api', needKey: true }, MYMEMORY: { name: 'MyMemory', url: 'https://api.mymemory.translated.net/get', needKey: false } }; // 获取当前使用的API let currentAPI = GM_getValue('currentAPI', 'MYMEMORY'); let baiduAppId = GM_getValue('baiduAppId', ''); let baiduKey = GM_getValue('baiduKey', ''); let youdaoAppKey = GM_getValue('youdaoAppKey', ''); let youdaoSecretKey = GM_getValue('youdaoSecretKey', ''); // 添加样式 GM_addStyle(` .translation-popup { position: fixed; top: 10px; right: 10px; padding: 10px; background: white; border: 1px solid #ccc; border-radius: 4px; z-index: 10000000; max-width: 300px; box-shadow: 0 2px 4px rgba(0,0,0,0.2); font-family: Arial, sans-serif; font-size: 14px; line-height: 1.4; } .translation-close { position: absolute; right: 5px; top: 5px; border: none; background: none; cursor: pointer; font-size: 16px; color: #666; padding: 0 5px; } .translation-close:hover { color: #333; } .context-menu { position: fixed; background: white; border: 1px solid #ccc; border-radius: 4px; padding: 5px 0; box-shadow: 0 2px 5px rgba(0,0,0,0.2); z-index: 10000000; } .translation-menu-item { padding: 8px 20px; cursor: pointer; background: none; border: none; width: 100%; text-align: left; font-size: 14px; } .translation-menu-item:hover { background-color: #f0f0f0; } .api-settings { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); z-index: 10000001; } .api-settings input { margin: 5px 0; padding: 5px; width: 100%; } .api-settings button { margin: 5px; padding: 5px 10px; } `); // 注册设置菜单 GM_registerMenuCommand('设置翻译API', showSettings); function showSettings() { const div = document.createElement('div'); div.className = 'api-settings'; div.innerHTML = `

翻译API设置

`; document.body.appendChild(div); // API选择切换事件 const apiSelect = div.querySelector('#apiSelect'); apiSelect.addEventListener('change', function() { div.querySelector('#baiduSettings').style.display = this.value === 'BAIDU' ? 'block' : 'none'; div.querySelector('#youdaoSettings').style.display = this.value === 'YOUDAO' ? 'block' : 'none'; }); // 保存设置 window.saveAPISettings = function(button) { const parent = button.parentElement; currentAPI = parent.querySelector('#apiSelect').value; baiduAppId = parent.querySelector('#baiduAppId').value; baiduKey = parent.querySelector('#baiduKey').value; youdaoAppKey = parent.querySelector('#youdaoAppKey').value; youdaoSecretKey = parent.querySelector('#youdaoSecretKey').value; GM_setValue('currentAPI', currentAPI); GM_setValue('baiduAppId', baiduAppId); GM_setValue('baiduKey', baiduKey); GM_setValue('youdaoAppKey', youdaoAppKey); GM_setValue('youdaoSecretKey', youdaoSecretKey); parent.remove(); showTranslation('设置已保存'); }; } // 生成百度翻译签名 function generateBaiduSign(text, salt) { const str = baiduAppId + text + salt + baiduKey; return CryptoJS.MD5(str).toString(); } // 生成有道翻译签名 function generateYoudaoSign(text, salt, curtime) { const str = youdaoAppKey + truncate(text) + salt + curtime + youdaoSecretKey; return CryptoJS.SHA256(str).toString(); } // 截取文本 function truncate(text) { if (text.length <= 20) return text; return text.substring(0, 10) + text.length + text.substring(text.length - 10); } let contextMenu = null; // 创建右键菜单 document.addEventListener('contextmenu', function(event) { const selectedText = window.getSelection().toString().trim(); if (selectedText) { event.preventDefault(); if (contextMenu) { contextMenu.remove(); } contextMenu = document.createElement('div'); contextMenu.className = 'context-menu'; let x = event.pageX; let y = event.pageY; contextMenu.style.top = y + 'px'; contextMenu.style.left = x + 'px'; const menuItem = document.createElement('button'); menuItem.className = 'translation-menu-item'; menuItem.textContent = '翻译选中文本'; menuItem.onclick = () => { translateText(selectedText); contextMenu.remove(); contextMenu = null; }; contextMenu.appendChild(menuItem); document.body.appendChild(contextMenu); const menuRect = contextMenu.getBoundingClientRect(); if (menuRect.right > window.innerWidth) { contextMenu.style.left = (x - menuRect.width) + 'px'; } if (menuRect.bottom > window.innerHeight) { contextMenu.style.top = (y - menuRect.height) + 'px'; } } }); document.addEventListener('click', function(event) { if (contextMenu && !contextMenu.contains(event.target)) { contextMenu.remove(); contextMenu = null; } }); document.addEventListener('keydown', function(event) { if (event.key === 'Escape' && contextMenu) { contextMenu.remove(); contextMenu = null; } }); function showTranslation(translatedText) { const existingPopup = document.querySelector('.translation-popup'); if (existingPopup) { existingPopup.remove(); } const div = document.createElement('div'); div.className = 'translation-popup'; const content = document.createElement('div'); content.style.marginRight = '20px'; content.textContent = translatedText; div.appendChild(content); const closeButton = document.createElement('button'); closeButton.className = 'translation-close'; closeButton.textContent = '×'; closeButton.onclick = () => div.remove(); div.appendChild(closeButton); document.body.appendChild(div); div.style.opacity = '0'; div.style.transition = 'opacity 0.3s ease-in-out'; setTimeout(() => div.style.opacity = '1', 10); setTimeout(() => { div.style.opacity = '0'; setTimeout(() => div.remove(), 300); }, 5000); } async function translateText(text) { showTranslation('正在翻译...'); switch(currentAPI) { case 'BAIDU': if (!baiduAppId || !baiduKey) { showTranslation('请先设置百度翻译API密钥'); return; } translateWithBaidu(text); break; case 'YOUDAO': if (!youdaoAppKey || !youdaoSecretKey) { showTranslation('请先设置有道翻译API密钥'); return; } translateWithYoudao(text); break; case 'MYMEMORY': translateWithMyMemory(text); break; } } function translateWithBaidu(text) { const salt = Date.now(); const sign = generateBaiduSign(text, salt); const url = `${API_CONFIG.BAIDU.url}?q=${encodeURIComponent(text)}&from=auto&to=zh&appid=${baiduAppId}&salt=${salt}&sign=${sign}`; GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.trans_result) { showTranslation(data.trans_result[0].dst); } else { showTranslation('翻译失败:' + (data.error_msg || '未知错误')); } } catch (error) { console.error('Translation error:', error); showTranslation('翻译出错,请稍后重试'); } }, onerror: function(error) { console.error('Request error:', error); showTranslation('网络错误,请稍后重试'); } }); } function translateWithYoudao(text) { const salt = Date.now(); const curtime = Math.round(new Date().getTime() / 1000); const sign = generateYoudaoSign(text, salt, curtime); const url = `${API_CONFIG.YOUDAO.url}?q=${encodeURIComponent(text)}&from=auto&to=zh-CHS&appKey=${youdaoAppKey}&salt=${salt}&sign=${sign}&signType=v3&curtime=${curtime}`; GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.translation) { showTranslation(data.translation[0]); } else { showTranslation('翻译失败:' + (data.errorMessage || '未知错误')); } } catch (error) { console.error('Translation error:', error); showTranslation('翻译出错,请稍后重试'); } }, onerror: function(error) { console.error('Request error:', error); showTranslation('网络错误,请稍后重试'); } }); } function translateWithMyMemory(text) { GM_xmlhttpRequest({ method: 'GET', url: `${API_CONFIG.MYMEMORY.url}?q=${encodeURIComponent(text)}&langpair=auto|zh`, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.responseStatus === 200) { showTranslation(data.responseData.translatedText); } else { showTranslation('翻译失败,请稍后重试'); } } catch (error) { console.error('Translation error:', error); showTranslation('翻译出错,请稍后重试'); } }, onerror: function(error) { console.error('Request error:', error); showTranslation('网络错误,请稍后重试'); } }); } })();