translator

划词翻译

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name translator
// @namespace https://lufei.so
// @supportURL https://github.com/intellilab/translator.user.js
// @description 划词翻译
// @version 1.6.8
// @run-at document-start
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @require https://cdn.jsdelivr.net/combine/npm/@violentmonkey/dom@1,npm/@violentmonkey/[email protected]
// @include *
// ==/UserScript==

(function () {
'use strict';

function dumpQuery(query) {
  return Object.entries(query).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join('&');
}
function request({
  method = 'GET',
  url,
  params,
  responseType,
  data,
  headers
}) {
  return new Promise((resolve, reject) => {
    if (params) {
      const sep = url.includes('?') ? '&' : '?';
      url += sep + dumpQuery(params);
    }

    GM_xmlhttpRequest({
      method,
      url,
      responseType,
      data,
      headers,

      onload(res) {
        if (res.status >= 300) return reject();
        resolve(res.response);
      },

      onerror: reject
    });
  });
}

const provider = {
  name: 'youdao',
  handle: async text => {
    const payload = {
      type: 'data',
      doctype: 'json',
      version: '1.1',
      relatedUrl: 'http://fanyi.youdao.com/',
      keyfrom: 'fanyiweb',
      key: null,
      translate: 'on',
      q: text,
      ts: Date.now()
    };
    const result = await request({
      url: 'https://fanyi.youdao.com/openapi.do',
      params: payload,
      responseType: 'json'
    });
    if (result.errorCode) throw result;
    const {
      basic,
      query,
      translation
    } = result;

    if (basic) {
      const noPhonetic = '♥';
      const {
        explains,
        'us-phonetic': us,
        'uk-phonetic': uk
      } = basic;
      return {
        query,
        phonetic: [{
          html: `UK: [${uk || noPhonetic}]`,
          url: `https://dict.youdao.com/dictvoice?audio=${encodeURIComponent(query)}&type=1`
        }, {
          html: `US: [${us || noPhonetic}]`,
          url: `https://dict.youdao.com/dictvoice?audio=${encodeURIComponent(query)}&type=2`
        }],
        explains,
        detailUrl: `http://dict.youdao.com/search?q=${encodeURIComponent(query)}`
      };
    }

    if (translation != null && translation[0]) {
      return {
        translations: translation
      };
    }
  }
};

const LANG_EN = 'en';
const LANG_ZH_HANS = 'zh-Hans';

async function translate(text, to) {
  const [data] = await request({
    method: 'POST',
    url: 'https://cn.bing.com/ttranslatev3',
    responseType: 'json',
    data: dumpQuery({
      fromLang: 'auto-detect',
      to,
      text
    }),
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  });
  const {
    detectedLanguage,
    translations
  } = data;
  return {
    language: {
      from: detectedLanguage.language,
      to
    },
    translations: translations.map(item => item.text)
  };
}

const provider$1 = {
  name: 'bing',
  handle: async source => {
    let data = await translate(source, LANG_ZH_HANS);
    if (data.language.from === LANG_ZH_HANS) data = await translate(source, LANG_EN);
    return data;
  }
};

const LANG_EN$1 = 'en';
const LANG_ZH_CN = 'zh-CN';

async function translate$1(text, to) {
  var _data$;

  const data = await request({
    url: 'https://translate.google.cn/translate_a/single',
    params: {
      q: text,
      client: 'gtx',
      sl: 'auto',
      tl: to,
      dt: 'at'
    },
    responseType: 'json'
  });
  const language = {
    from: data[8][0][0],
    to
  };
  const translations = (_data$ = data[5]) == null ? void 0 : _data$.map(item => {
    var _item$, _item$$;

    return (_item$ = item[2]) == null ? void 0 : (_item$$ = _item$[0]) == null ? void 0 : _item$$[0];
  }).filter(Boolean);
  return {
    language,
    translations
  };
}

const provider$2 = {
  name: 'google',
  handle: async source => {
    let data = await translate$1(source, LANG_ZH_CN);
    if (data.language.from === LANG_ZH_CN) data = await translate$1(source, LANG_EN$1);
    return data;
  }
};

var styles = {"link":"style-module_link__YVV7m","section":"style-module_section__1Eiq1","block-top":"style-module_block__1NZsy","block-bottom":"style-module_block__1NZsy","label":"style-module_label__JD9KX","content":"style-module_content__1MvKK","phonetic":"style-module_phonetic__2SIsx","item":"style-module_item__2QPXK"};
var stylesheet=":host .style-module_link__YVV7m{position:relative;color:#7cbef0;cursor:pointer}:host .style-module_link__YVV7m:hover{text-decoration:underline}:host .style-module_section__1Eiq1{display:flex;align-items:flex-start;font-size:12px;line-height:1.2}:host .style-module_section__1Eiq1:not(:first-child){border-top:1px solid #eee}:host .style-module_block__1NZsy{display:block}:host .style-module_label__JD9KX{display:block;margin:8px 8px 8px 0;padding:2px 0;color:#fff;background:#bbb;border-radius:4px;font-size:12px;line-height:1.4;text-transform:uppercase;writing-mode:vertical-rl}:host .style-module_content__1MvKK{flex:1;min-width:0;padding:8px 0}:host .style-module_content__1MvKK>*{display:block}:host .style-module_content__1MvKK>:not(:first-child){margin-top:8px}:host .style-module_phonetic__2SIsx{display:inline-block;margin-left:8px}:host .style-module_item__2QPXK{display:block}:host .style-module_item__2QPXK~.style-module_item__2QPXK{margin-top:8px}";

const React = VM;
let audio;

function play(url) {
  if (!audio) audio = /*#__PURE__*/React.createElement("audio", {
    autoPlay: true
  });
  audio.src = url;
}

function getPlayer(url) {
  return () => {
    play(url);
  };
}

function handleOpenUrl(e) {
  const {
    href
  } = e.target.dataset;
  const a = /*#__PURE__*/React.createElement("a", {
    href: href,
    target: "_blank",
    rel: "noopener noreferrer"
  });
  a.click();
}

function render(results, {
  event,
  panel
}) {
  panel.clear();

  for (const [name, result] of Object.entries(results)) {
    const {
      query,
      phonetic,
      detailUrl,
      explains,
      translations
    } = result;
    panel.append( /*#__PURE__*/React.createElement(panel.id, {
      className: styles.section
    }, /*#__PURE__*/React.createElement(panel.id, {
      className: styles.label
    }, name), /*#__PURE__*/React.createElement(panel.id, {
      className: styles.content
    }, !!(query || phonetic != null && phonetic.length) && /*#__PURE__*/React.createElement(panel.id, {
      className: styles.block
    }, query && /*#__PURE__*/React.createElement(panel.id, null, query), phonetic == null ? void 0 : phonetic.map(({
      html,
      url
    }) => /*#__PURE__*/React.createElement(panel.id, {
      className: `${styles.phonetic} ${styles.link}`,
      dangerouslySetInnerHTML: {
        __html: html
      },
      onClick: getPlayer(url)
    }))), explains && /*#__PURE__*/React.createElement(panel.id, {
      className: styles.block
    }, explains.map(item => /*#__PURE__*/React.createElement(panel.id, {
      className: styles.item,
      dangerouslySetInnerHTML: {
        __html: item
      }
    }))), detailUrl && /*#__PURE__*/React.createElement(panel.id, {
      className: styles.block
    }, /*#__PURE__*/React.createElement(panel.id, {
      className: styles.link,
      "data-href": detailUrl,
      onClick: handleOpenUrl
    }, "\u66F4\u591A...")), translations && /*#__PURE__*/React.createElement(panel.id, {
      className: styles.block
    }, translations.map(item => /*#__PURE__*/React.createElement(panel.id, {
      className: styles.item,
      dangerouslySetInnerHTML: {
        __html: item
      }
    }))))));
  }

  const {
    wrapper
  } = panel;
  const {
    innerWidth,
    innerHeight
  } = window;
  const {
    clientX,
    clientY
  } = event;

  if (clientY > innerHeight * 0.5) {
    wrapper.style.top = 'auto';
    wrapper.style.bottom = `${innerHeight - clientY + 10}px`;
  } else {
    wrapper.style.top = `${clientY + 10}px`;
    wrapper.style.bottom = 'auto';
  }

  if (clientX > innerWidth * 0.5) {
    wrapper.style.left = 'auto';
    wrapper.style.right = `${innerWidth - clientX}px`;
  } else {
    wrapper.style.left = `${clientX}px`;
    wrapper.style.right = 'auto';
  }

  panel.show();
}

const providers = [provider, provider$1, provider$2];
let session;

function translate$2(context) {
  const sel = window.getSelection();
  const text = sel.toString().trim();
  if (/^\s*$/.test(text)) return;
  const {
    activeElement
  } = document;
  if (['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) < 0 && !activeElement.contains(sel.getRangeAt(0).startContainer)) return;
  context.source = text;
  const results = {};
  session = results;
  providers.forEach(async provider => {
    const result = await provider.handle(text);
    if (!result || session !== results) return;
    results[provider.name] = result;
    render(results, context);
  });
}

function debounce(func, delay) {
  let timer;

  function exec(...args) {
    timer = null;
    func(...args);
  }

  return (...args) => {
    if (timer) clearTimeout(timer);
    timer = setTimeout(exec, delay, ...args);
  };
}

function initialize() {
  const panel = VM.getPanel({
    css: stylesheet,
    shadow: false
  });
  const panelStyle = panel.body.style;
  panelStyle.maxHeight = '50vh';
  panelStyle.padding = '0 8px';
  panelStyle.overflow = 'auto';
  panelStyle.overscrollBehavior = 'contain';
  const debouncedTranslate = debounce(event => translate$2({
    event,
    panel
  }));
  let isSelecting;
  document.addEventListener('mousedown', e => {
    isSelecting = false;
    if (panel.body.contains(e.target)) return;
    panel.hide();
    session = null;
  }, true);
  document.addEventListener('mousemove', () => {
    isSelecting = true;
  }, true);
  document.addEventListener('mouseup', e => {
    if (panel.body.contains(e.target) || !isSelecting) return;
    debouncedTranslate(e);
  }, true);
  document.addEventListener('dblclick', e => {
    if (panel.body.contains(e.target)) return;
    debouncedTranslate(e);
  }, true);
}

initialize();

}());