translator

划词翻译

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==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();

}());