google multilang search view en/zh

View a google search result in two languages side by side for comparison and language learning.

当前为 2022-06-26 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name google multilang search view en/zh
  3. // @name:zh 谷歌多语言搜索 en/zh
  4. // @namespace snomiao@gmail.com
  5. // @author snomiao@gmail.com
  6. // @version 0.0.2
  7. // @description View a google search result in two languages side by side for comparison and language learning.
  8. // @description:zh 以并列多语言视角浏览谷歌搜索结果
  9. // @match https://*.google.com/search?*
  10. // @match https://*.google.com*/search?*
  11. // @match https://*.google.com.hk/search?*
  12. // @match https://*.google.com.tw/search?*
  13. // @grant none
  14. // @run-at document-start
  15. // @license GPL-3.0+
  16. // @supportURL https://github.com/snomiao/userscript.js/issues
  17. // @contributionURL https://snomiao.com/donate
  18. // @grant GM_getValue
  19. // @grant GM_setValue
  20. // ==/UserScript==
  21. const introURL =
  22. 'https://rapidapi.com/microsoft-azure-org-microsoft-cognitive-services/api/microsoft-translator-text/';
  23. const intro = `
  24. [Microsoft Translator Text API Documentation (microsoft-azure-org-microsoft-cognitive-services) | RapidAPI]( ${introURL} )
  25. 1. Open ${introURL}
  26. 2. Sign up or login
  27. 3. Click Subscribe to Test
  28. 4. Copy your API key
  29. 4. Come back and paste here
  30. 5. OK
  31. `.trim();
  32.  
  33. (async function () {
  34. if (parent !== window) return iframeHeightSenderSetup();
  35. iframeHeightReceiverSetup();
  36. //
  37. const rapidAPIKey = (globalThis.rapidAPIKey = await rapidAPIKeyLoad());
  38. if (!rapidAPIKey) throw new Error('no rapid api key');
  39. const searchLinks = await getMultiLangSearchLinks();
  40. replacePageWIthMultiLang(searchLinks);
  41. })();
  42.  
  43. function replacePageWIthMultiLang(searchLinks) {
  44. const iframes = searchLinks.map((src) => `<iframe src="${src}"></iframe>`);
  45. const style = `<style>
  46. body{margin: 0; display: flex; flex-direction: row;}
  47. iframe{flex: auto;height: 100vh;overflow: hidden;border: none;}
  48. </style>`;
  49. document.body.innerHTML = `${style}${iframes}`;
  50. }
  51.  
  52. function iframeHeightReceiverSetup() {
  53. const setHeight = (height = 0) =>
  54. height &&
  55. [...document.querySelectorAll('iframe[src]')].map(
  56. (e) =>
  57. (e.style.height =
  58. Math.max(
  59. Number(String(e.style.height).replace(/\D+/g, '') || 0),
  60. height
  61. ) + 'px')
  62. );
  63. window.addEventListener('message', (e) => setHeight(e.data?.height), false);
  64. }
  65. function iframeHeightSenderSetup() {
  66. iframeScrollbarRemove();
  67. const sendHeight = () =>
  68. parent.postMessage?.({ height: document.body.scrollHeight }, '*');
  69. window.addEventListener('resize', sendHeight, false);
  70. window.addEventListener('load', sendHeight, false);
  71. sendHeight();
  72. }
  73.  
  74. function iframeScrollbarRemove() {
  75. document.body.style.margin = '-18px auto 0';
  76. }
  77.  
  78. async function getMultiLangSearchLinks() {
  79. const url = new URL(location.href);
  80. const query = url.searchParams.get('q') || '';
  81. const result = await bingTranslate(query);
  82. const searchLinks = result.flatMap((e) =>
  83. e.translations.map((t) => {
  84. const u2 = new URL(url.href);
  85. return u2.searchParams.set('q', t.text), u2.href;
  86. })
  87. );
  88. return searchLinks;
  89. }
  90.  
  91. async function bingTranslate(text = 'hello, world') {
  92. const body = JSON.stringify([{ Text: text }]);
  93. const exampleResponse = [
  94. {
  95. detectedLanguage: {
  96. language: 'en',
  97. score: 1,
  98. },
  99. translations: [
  100. {
  101. text: '我真的很想把你的车开几个圈。',
  102. to: 'zh-Hans',
  103. },
  104. {
  105. text: 'I would really like to drive your car around the block a few times.',
  106. to: 'en',
  107. },
  108. ],
  109. },
  110. ];
  111. const ak = await rakGet();
  112. if (!ak) throw new Error('no rapid api key is found');
  113. const response = await fetch(
  114. 'https://microsoft-translator-text.p.rapidapi.com/translate?to%5B0%5D=zh%2Cen&api-version=3.0&profanityAction=NoAction&textType=plain',
  115. {
  116. method: 'POST',
  117. headers: {
  118. 'content-type': 'application/json',
  119. 'X-RapidAPI-Key': ak,
  120. 'X-RapidAPI-Host': 'microsoft-translator-text.p.rapidapi.com',
  121. },
  122. body,
  123. }
  124. )
  125. .then((r) => r.json())
  126. .catch(async (e) => await rapidAPIKeyLoadNew('error: ' + e.message));
  127. return response;
  128. }
  129.  
  130. async function rapidAPIKeyLoad() {
  131. return (await rakGet()) || (await rapidAPIKeyLoadNew());
  132. }
  133. async function rapidAPIKeyLoadNew(msg = '') {
  134. return (
  135. window.open(introURL, '_blank'),
  136. await rakSet(prompt((msg + '\n' + intro).trim(), await rakGet()) || ''),
  137. await rakGet()
  138. );
  139. }
  140.  
  141. async function rakGet() {
  142. return (
  143. (await globalThis.GM_getValue?.('rapidAPIKey', '')) ||
  144. localStorage.getItem('rapidAPIKey')
  145. )?.replace(/^null$/, '');
  146. }
  147. async function rakSet(v = '') {
  148. return (
  149. (await (globalThis.GM_setValue &&
  150. ((await globalThis.GM_setValue('rapidAPIKey', v || '')) ||
  151. true))) ||
  152. localStorage.setItem('rapidAPIKey', v || '') ||
  153. true
  154. );
  155. }