划词翻译

选中文字自动翻译

  1. // ==UserScript==
  2. // @name 划词翻译
  3. // @name:ja 翻訳
  4. // @name:zh-CN 划词翻译
  5. // @namespace http://www.icycat.com
  6. // @description 选中文字自动翻译
  7. // @description:ja 選択した文字の自動翻訳
  8. // @description:zh-CN 选中文字自动翻译
  9. // @author 冻猫
  10. // @include *
  11. // @version 3.5
  12. // @grant GM_xmlhttpRequest
  13. // @grant GM_addStyle
  14. // @grant GM_getValue
  15. // @grant GM_setValue
  16. // @require https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js
  17. // @run-at document-start
  18. // ==/UserScript==
  19.  
  20. (function() {
  21.  
  22. 'use strict';
  23.  
  24. var gv = {};
  25.  
  26. if (!GM_getValue('toLanguage')) {
  27. if (/zh/i.test(navigator.language)) {
  28. GM_setValue('toLanguage', 'zh-CHS');
  29. } else if (/ja/i.test(navigator.language)) {
  30. GM_setValue('toLanguage', 'ja');
  31. } else if (/en/i.test(navigator.language)) {
  32. GM_setValue('toLanguage', 'en');
  33. }
  34. }
  35.  
  36. if (!GM_getValue('apiHost')) {
  37. GM_setValue('apiHost', 'www.bing.com');
  38. }
  39.  
  40. function init() {
  41.  
  42. document.addEventListener('mousedown', mouseStart, true);
  43. document.addEventListener('mouseup', mouseEnd, true);
  44.  
  45. function mouseStart(e) {
  46. if ($('#catTranslateBox').length == 0) {
  47. createBox();
  48. $('#catTranslateBox li').on('click', setLanguage);
  49. }
  50. if ($('#catTranslateBox').css('display') == 'block' && !checkClick(e)) {
  51. clearTranslate()
  52. }
  53. document.addEventListener('mousemove', moveCheck, true);
  54. if (e.target.className == 'catPlaySound') {
  55. $('.catPlaySound').addClass('catPlaySoundClick');
  56. getRequest(gv.soundUrl);
  57. } else if (e.target.className == 'catSet') {
  58. $('.catdropdown').css('display', 'block');
  59. }
  60. }
  61.  
  62. function moveCheck(e) {
  63. clearTimeout(gv.timer);
  64. gv.holdTime = false;
  65. gv.timer = setTimeout(function() {
  66. gv.holdTime = true;
  67. }, 300);
  68. }
  69.  
  70. function mouseEnd(e) {
  71. document.removeEventListener('mousemove', moveCheck, true);
  72. clearTimeout(gv.timer);
  73. if (gv.holdTime == true && window.getSelection().toString()) {
  74. e.preventDefault()
  75. e.stopPropagation();
  76. gv.holdTime = false;
  77. showBox(e.clientX, e.clientY);
  78. gv.apiHost = GM_getValue('apiHost');
  79. gv.selectText = window.getSelection().toString();
  80. gv.encodeText = encodeURIComponent(gv.selectText.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2'));
  81. tdetect(gv.encodeText);
  82. }
  83. if ($('.catPlaySoundClick').length > 0) {
  84. $('.catPlaySound').removeClass('catPlaySoundClick');
  85. }
  86. }
  87. }
  88.  
  89. function setLanguage(e) {
  90. GM_setValue('toLanguage', e.target.getAttribute('name'))
  91. $('.catdropdown').css('display', '');
  92. $('.catText').text(e.target.innerText + 'OK!');
  93. }
  94.  
  95. function createBox() {
  96. GM_addStyle([
  97. '#catTranslateBox * {margin:0;padding:0;box-sizing:border-box;}',
  98. '#catTranslateBox {min-height:24px;min-width:100px;max-width:360px;font:normal 12px/24px Helvetica, Tahoma, Arial, sans-serif;text-align: left;position: absolute;z-index: 2147483647;top: 22px;left: -35px;background: #fff;border: 1px solid #dcdcdc;-webkit-transition: opacity .218s;transition: opacity .218s;box-shadow: 0 1px 4px rgba(0,0,0,.2);padding: 5px 0;display: none;font-size: 12px;line-height: 20px;border-radius:3px;}',
  99. '#catTranslateBox .catContentBox {margin:0 8px;color:#333;}',
  100. '#catTranslateBox .catContentBox .catTextBox{line-height:16px;border-bottom: 1px solid #ccc;padding: 2px 18px 9px 0;height: 25px;}',
  101. '#catTranslateBox .catContentBox .catTextBox div{vertical-align: middle;margin-right: 4px;color:#333;font-weight: normal;font-size:12px;}',
  102. '#catTranslateBox .catContentBox .catTextBox .catText{display: inline-block;font-size:14px;font-weight: bold;color:#333;}',
  103. '#catTranslateBox .catContentBox .catTextBox .catPlaySound {margin-left: 1px;cursor:pointer;display: inline-block;vertical-align: middle;width: 14px;height: 11px;overflow: hidden;background: url("data:image/gif;base64,R0lGODlhDgAZAIAAABy3/f///yH5BAAAAAAALAAAAAAOABkAAAI1jA+nC7ncXmg0RlTvndnt7jlcxkmjqWzotLbui4qxqBpUmoDl2Nk5GOKRSsCfDyer7ZYMSQEAOw==") no-repeat;text-decoration: none;}',
  104. '#catTranslateBox .catContentBox .catTextBox .catPlaySound.catPlaySoundClick {background-position:0 -14px;}',
  105. '#catTranslateBox .catContentBox .catExplain{padding: 2px 0 0 0;font-weight: normal;font-size:12px;}',
  106. '#catTranslateBox .catTipArrow {width: 0;height: 0;font-size: 0;line-height: 0;display: block;position: absolute;top: -16px;left: 10px;}',
  107. '#catTranslateBox .catTipArrow em, #catTranslateBox .catTipArrow ins {width: 0;height: 0;font-size: 0;line-height: 0;display: block;position: absolute;border: 8px solid transparent;border-style: dashed dashed solid;}',
  108. '#catTranslateBox .catTipArrow em {border-bottom-color: #d8d8d8;font-style: normal;color: #c00;}',
  109. '#catTranslateBox .catTipArrow ins {border-bottom-color: #fff;top: 2px;text-decoration: underline;background:none !important}',
  110. '#catTranslateBox .catSet {position:absolute;top:9px;right:10px;cursor: pointer;width: 14px;height: 14px;background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAAdVBMVEUAAAAwi/+Zxv9urv9oq/9pq/9Elv81jf89kv8wiv8+kv8wiv8xi/8wi/9/t/9+t/9co/9Zof9Hl/9Gl/9ClP9Bk/85j//k8f/Z6v+Fu//x+P/e7f/b6//G3/+/2/+w0/+q0P+52P+42P+Lvv+IvP9wr/9vr/864/KKAAAAF3RSTlMAR/7s7OK7l5VuTyMTC/z7y8ihnYSAQ/Vmp/0AAAB9SURBVAjXVY9HDsQwDAPpVKf32E7v/3/iGtJhk7kNIIgkLH0QA3EgQHTX7AnhzWdLKo9hMWYZdkmaFFpZdJ6QRo7afH9TTmSlqcy4hmkarqMpazxq0m4GZK6e1I37rQ/q8n9cNZ9XHJRzUMFBcucaB9doTy55dSAET+gB/ABPjgqB+Q/YPgAAAABJRU5ErkJggg==") no-repeat;text-decoration: none;}',
  111. '#catTranslateBox .catSet .catdropdown {margin:0;padding:0;display:none;top:13px;right:-60px;position: absolute;background-color: #ffffff;width: 59px;overflow: auto;z-index: 1;border: 1px solid rgba(0,0,0,.2);box-shadow: 0 2px 4px rgba(0,0,0,.2);}',
  112. '#catTranslateBox .catSet .catdropdown li {list-style-type:none; color: black;padding: 6px 8px;margin:0px;text-decoration: none;display: block;text-align:center;}',
  113. '#catTranslateBox .catSet .catdropdown li:hover { background-color: #f1f1f1;}'
  114. ].join('\n'));
  115. $(
  116. '<div id="catTranslateBox">' +
  117. '<div class="catContentBox">' +
  118. '<div class="catTextBox">' +
  119. '<div class="catText"></div>' +
  120. '<div class="catPlaySound"></div>' +
  121. '</div>' +
  122. '<div class="catExplainBox">' +
  123. '<div class="catExplain"></div>' +
  124. '<div class="catPlaySound"></div>' +
  125. '</div>' +
  126. '</div>' +
  127. '<div class="catTipArrow"><em></em><ins></ins></div>' +
  128. '<div class="catSet">' +
  129. '<ul class="catdropdown">' +
  130. '<li name="zh-CHS">中文</li>' +
  131. '<li name="ja">日本語</li>' +
  132. '<li name="en">English</li>' +
  133. '</ul>' +
  134. '</div>' +
  135. '</div>'
  136. ).appendTo($(document.body));
  137. }
  138.  
  139. function showBox(mouseX, mouseY) {
  140. var catBox = document.getElementById('catTranslateBox');
  141. var selectedRect = window.getSelection().getRangeAt(0).getBoundingClientRect();
  142. var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  143. if (selectedRect.width) {
  144. if (getComputedStyle(document.body).position != 'static') {
  145. catBox.style.top = selectedRect.bottom - document.body.getBoundingClientRect().top + 8 + 'px';
  146. } else {
  147. catBox.style.top = selectedRect.bottom + scrollTop + 8 + 'px';
  148. }
  149. catBox.style.left = selectedRect.left + selectedRect.width / 2 - 18 + 'px';
  150. } else {
  151. catBox.style.top = mouseY - document.body.getBoundingClientRect().y + selectedRect.height + 8 + 'px';
  152. catBox.style.left = mouseX + selectedRect.width / 2 - 18 + 'px';
  153. }
  154.  
  155. catBox.style.display = 'block';
  156. }
  157.  
  158. function tdetect(encodeText) {
  159. var url = `https://${gv.apiHost}/tdetect`;
  160. var postData = `&text=${encodeText}`;
  161. postRequest(url, postData, 'tdetect');
  162. }
  163.  
  164. function ttranslate(fromLanguage) {
  165. var toLanguage = GM_getValue('toLanguage');
  166.  
  167. if (fromLanguage == toLanguage) {
  168. toLanguage = 'en';
  169. }
  170.  
  171. var url = `https://${gv.apiHost}/ttranslate`;
  172. var postData = `&text=${gv.encodeText}&from=${fromLanguage}&to=${toLanguage}`;
  173.  
  174. gv.soundUrl = `https://${gv.apiHost}/tspeak?&format=audio%2Fmp3&language=${fromLanguage}&options=female&text=${gv.encodeText}`;
  175.  
  176. postRequest(url, postData, 'ttranslate');
  177. }
  178.  
  179. function parseRes(jsonRes) {
  180.  
  181. var explains = '';
  182. var obj = JSON.parse(jsonRes);
  183.  
  184. $('.catExplain').text(obj.translationResponse);
  185.  
  186. }
  187.  
  188. function checkClick(e) {
  189. var path = e.path || e.composedPath();
  190. if (path.indexOf($('#catTranslateBox').get(0)) > -1) {
  191. return true;
  192. } else {
  193. return false;
  194. }
  195. }
  196.  
  197. function clearTranslate() {
  198. $('#catTranslateBox').css('display', '');
  199. $('.catdropdown').css('display', '');
  200. $('.catText').empty();
  201. $('.catExplain').empty();
  202. try {
  203. gv.catSource.stop();
  204. } catch (e) {};
  205. }
  206.  
  207. function playSound(arraybuffer) {
  208. if (!gv.audioCtx) {
  209. gv.audioCtx = new AudioContext();
  210. }
  211. gv.audioCtx.decodeAudioData(arraybuffer).then(function(buffer) {
  212. gv.catSource = gv.audioCtx.createBufferSource();
  213. gv.catSource.buffer = buffer;
  214. gv.catSource.connect(gv.audioCtx.destination);
  215. gv.catSource.start();
  216. });
  217. }
  218.  
  219. function postRequest(url, data, fn) {
  220. GM_xmlhttpRequest({
  221. method: 'POST',
  222. url: url,
  223. data: data,
  224. headers: {
  225. 'Referer': `https://${gv.apiHost}/translator/`,
  226. 'Content-type': 'application/x-www-form-urlencoded'
  227. },
  228. onload: function(res) {
  229. if (res.status == '200' && res.responseText != '') {
  230. if (fn == 'tdetect') {
  231. ttranslate(res.responseText);
  232. } else if (fn == 'ttranslate') {
  233. parseRes(res.responseText);
  234. }
  235. } else if (res.status == '200' && res.finalUrl != url) {
  236. console.log('跳转');
  237. gv.apiHost = getUrlHost(res.finalUrl);
  238. GM_setValue('apiHost', gv.apiHost);
  239. postRequest(res.finalUrl, data, fn);
  240. } else {
  241. console.log('发生错误');
  242. }
  243.  
  244. },
  245. });
  246. }
  247.  
  248. function getRequest(url) {
  249. GM_xmlhttpRequest({
  250. method: 'GET',
  251. url: url,
  252. headers: {
  253. 'Referer': `https://${gv.apiHost}/translator/`,
  254. 'Range': 'bytes=0-'
  255. },
  256. responseType: 'arraybuffer',
  257. onload: function(res) {
  258. playSound(res.response);
  259. },
  260. });
  261. }
  262.  
  263. function getUrlHost(url) {
  264. return url.split('//')[1].split('/')[0];
  265. }
  266.  
  267. function isJSON(str) {
  268. if (typeof str == 'string') {
  269. try {
  270. var obj = JSON.parse(str);
  271. if (typeof obj == 'object' && obj) {
  272. return true;
  273. } else {
  274. return false;
  275. }
  276. } catch (e) {
  277. return false;
  278. }
  279. }
  280. }
  281.  
  282. init();
  283.  
  284. })();