// ==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('网络错误,请稍后重试');
}
});
}
})();