// ==UserScript==
// @name translator
// @namespace https://lufei.so
// @supportURL https://github.com/intellilab/translator.user.js
// @description 划词翻译
// @version 1.6.2
// @run-at document-start
// @grant GM_xmlhttpRequest
// @require https://cdn.jsdelivr.net/combine/npm/@violentmonkey/dom@1,npm/@violentmonkey/ui
// @include *
// ==/UserScript==
(function () {
'use strict';
var css = ".header{padding:0 0 8px;border-bottom:1px dashed #aaa}.header>a{margin-left:8px;color:#7cbef0;cursor:pointer;font-size:12px}.detail{margin:8px 0 0;padding:0;line-height:22px;list-style:none;font-size:12px}.detail>li{line-height:26px}";
function render(data, panel, audio) {
var basic = data.basic,
query = data.query,
translation = data.translation;
panel.clear();
if (basic) {
var explains = basic.explains,
us = basic['us-phonetic'],
uk = basic['uk-phonetic'];
var noPhonetic = '♥';
var handleClick = function handleClick(e) {
var type = e.target.dataset.type;
if (type) {
audio.src = `https://dict.youdao.com/dictvoice?audio=${encodeURIComponent(query)}&type=${type}`;
}
};
var header = VM.createElement("div", {
className: "header",
onClick: handleClick
}, VM.createElement("span", null, query), VM.createElement("a", {
"data-type": "1",
dangerouslySetInnerHTML: {
__html: `uk: [${uk || noPhonetic}]`
}
}), VM.createElement("a", {
"data-type": "2",
dangerouslySetInnerHTML: {
__html: `us: [${us || noPhonetic}]`
}
}), VM.createElement("a", {
target: "_blank",
rel: "noopener noreferrer",
href: `http://dict.youdao.com/search?q=${encodeURIComponent(query)}`
}, "\u8BE6\u60C5"));
panel.append(header);
if (explains) {
var lis = [];
for (var _iterator = explains, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref = _i.value;
}
var item = _ref;
lis.push(VM.createElement("li", {
dangerouslySetInnerHTML: {
__html: item
}
}));
}
var ul = VM.createElement("ul", {
className: "detail"
}, lis);
panel.append(ul);
}
} else if (translation) {
var div = VM.createElement("div", {
dangerouslySetInnerHTML: {
__html: translation[0]
}
});
panel.append(div);
}
}
function translate(e, panel, audio) {
var sel = window.getSelection();
var text = sel.toString().trim();
if (/^\s*$/.test(text)) return;
var _document = document,
activeElement = _document.activeElement;
if (['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) < 0 && !activeElement.contains(sel.getRangeAt(0).startContainer)) return;
/**
* 采用 Bing 翻译句子
* PS: 对比了一下有道发现各有千秋或许某个版本会使用有道翻译(不是词典)句子
*/
if (/\s/.test(text)) {
GM_xmlhttpRequest({
method: 'POST',
url: 'https://www.bing.com/ttranslatev3',
data: `fromLang=auto-detect&to=zh-Hans&text=${text}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
onload(res) {
if (res.status !== 200) return;
var data = JSON.parse(res.responseText); // 与有道词典结果格式保持一致
render({
translation: [data[0].translations[0].text]
}, panel, audio);
var wrapper = panel.wrapper;
var _window = window,
innerWidth = _window.innerWidth,
innerHeight = _window.innerHeight;
if (e.clientY > innerHeight * 0.5) {
wrapper.style.top = 'auto';
wrapper.style.bottom = `${innerHeight - e.clientY + 10}px`;
} else {
wrapper.style.top = `${e.clientY + 10}px`;
wrapper.style.bottom = 'auto';
}
if (e.clientX > innerWidth * 0.5) {
wrapper.style.left = 'auto';
wrapper.style.right = `${innerWidth - e.clientX}px`;
} else {
wrapper.style.left = `${e.clientX}px`;
wrapper.style.right = 'auto';
}
panel.show();
}
});
} else {
var query = {
type: 'data',
doctype: 'json',
version: '1.1',
relatedUrl: 'http://fanyi.youdao.com/',
keyfrom: 'fanyiweb',
key: null,
translate: 'on',
q: text,
ts: Date.now()
};
var qs = Object.keys(query).map(function (key) {
return `${encodeURIComponent(key)}=${encodeURIComponent(query[key])}`;
}).join('&');
GM_xmlhttpRequest({
method: 'GET',
url: `https://fanyi.youdao.com/openapi.do?${qs}`,
onload(res) {
var data = JSON.parse(res.responseText);
if (!data.errorCode) {
render(data, panel, audio);
var wrapper = panel.wrapper;
var _window2 = window,
innerWidth = _window2.innerWidth,
innerHeight = _window2.innerHeight;
if (e.clientY > innerHeight * 0.5) {
wrapper.style.top = 'auto';
wrapper.style.bottom = `${innerHeight - e.clientY + 10}px`;
} else {
wrapper.style.top = `${e.clientY + 10}px`;
wrapper.style.bottom = 'auto';
}
if (e.clientX > innerWidth * 0.5) {
wrapper.style.left = 'auto';
wrapper.style.right = `${innerWidth - e.clientX}px`;
} else {
wrapper.style.left = `${e.clientX}px`;
wrapper.style.right = 'auto';
}
panel.show();
}
}
});
}
}
function debounce(func, delay) {
var timer;
function exec() {
timer = null;
func.apply(void 0, arguments);
}
return function () {
if (timer) clearTimeout(timer);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
timer = setTimeout.apply(void 0, [exec, delay].concat(args));
};
}
function initialize() {
var audio = VM.createElement("audio", {
autoPlay: true
});
var panel = VM.getPanel({
css
});
var debouncedTranslate = debounce(function (e) {
return translate(e, panel, audio);
});
var isSelecting;
document.addEventListener('mousedown', function (e) {
isSelecting = false;
if (e.target === panel.host) return;
panel.hide();
}, true);
document.addEventListener('mousemove', function () {
isSelecting = true;
}, true);
document.addEventListener('mouseup', function (e) {
if (panel.body.contains(e.target) || !isSelecting) return;
debouncedTranslate(e);
}, true);
document.addEventListener('dblclick', function (e) {
if (panel.body.contains(e.target)) return;
debouncedTranslate(e);
}, true);
}
initialize();
}());