// ==UserScript== // @name translator // @namespace https://lufei.so // @supportURL https://github.com/intellilab/translator.user.js // @description 划词翻译 // @version 1.6.0 // @run-at document-start // @grant GM_xmlhttpRequest // @require https://cdn.jsdelivr.net/combine/npm/@violentmonkey/dom@1,npm/@violentmonkey/ui // @include * // @downloadURL none // ==/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(); 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://cn.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(); }());