google multilang search view en/zh

[snolab] Mulango - Walkers for bilingual learners. View a google search result in two languages side by side for comparison and language learning. now supports Bing & Google,

目前为 2022-09-24 提交的版本。查看 最新版本

  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 1.0.0
  7. // @description [snolab] Mulango - Walkers for bilingual learners. View a google search result in two languages side by side for comparison and language learning. now supports Bing & Google,
  8. // @description:zh [snolab] Mulango - 双语学习者的学步车,以并列多语言视角浏览谷歌搜索结果 现支持 Bing & Google,
  9. // @match https://*.google.com/search?*
  10. // @match https://*.bing.com/search?*
  11. // @match https://*/search*
  12. // @grant none
  13. // @run-at document-start
  14. // @license GPL-3.0+
  15. // @supportURL https://github.com/snomiao/userscript.js/issues
  16. // @contributionURL https://snomiao.com/donate
  17. // @grant GM_getValue
  18. // @grant GM_setValue
  19. // ==/UserScript==
  20.  
  21. // const introURL =
  22. // 'https://rapidapi.com/microsoft-azure-org-microsoft-cognitive-services/api/microsoft-translator-text/';
  23. // // [Microsoft Translator Text API Documentation (microsoft-azure-org-microsoft-cognitive-services) | RapidAPI]( ${introURL} )
  24. // const introPrompt = `
  25. // 1. I will open ${introURL} for you.
  26. // 1. Sign up or login
  27. // 2. Go to Endpoints tab
  28. // 3. Click Subscribe to Test
  29. // 2. Copy your API key
  30. // 5. Close the page
  31. // 3. Come back
  32. // 3. paste API Key in prompt
  33. // 4. OK
  34. // `.trim();
  35.  
  36. (async function () {
  37. if (!location.hostname.match(/google|bing/)) return;
  38. if (parent !== window) return iframeSetup();
  39. iframeHeightReceiverSetup();
  40. //
  41. // const rapidAPIKey = (globalThis.rapidAPIKey = await rapidAPIKeyLoad());
  42. // if (!rapidAPIKey) throw new Error('no rapid api key');
  43. const searchLinks = await mulangoSearchLinksFetch();
  44. searchLinks.length && mulangoPageReplace(searchLinks);
  45. })();
  46.  
  47. function mulangoPageReplace(searchLinks) {
  48. const iframes = searchLinks.map((src) => `<iframe src="${src}"></iframe>`);
  49. const style = `<style>
  50. body{margin: 0; display: flex; flex-direction: row; }
  51. iframe{flex: auto; height: 100vh; overflow: hidden;border: none; }
  52. </style>`;
  53. document.body.innerHTML = `${style}${iframes}`;
  54. }
  55.  
  56. function iframeHeightReceiverSetup() {
  57. const setHeight = (height = 0) =>
  58. height &&
  59. [...document.querySelectorAll("iframe[src]")].map(
  60. (e) =>
  61. (e.style.height =
  62. Math.max(
  63. Number(String(e.style.height).replace(/\D+/g, "") || 0),
  64. height
  65. ) + "px")
  66. );
  67. window.addEventListener("message", (e) => setHeight(e.data?.height), false);
  68. }
  69. function iframeSetup() {
  70. iframeScrollbarRemove();
  71. const sendHeight = () =>
  72. parent.postMessage?.({ height: document.body.scrollHeight }, "*");
  73. window.addEventListener("resize", sendHeight, false);
  74. window.addEventListener("load", sendHeight, false);
  75. sendHeight();
  76. window.addEventListener("load", iframeLinksSetup, false);
  77. }
  78.  
  79. function iframeLinksSetup() {
  80. return [...document.querySelectorAll("a[href]")]
  81. .filter(({ href }) => new URL(href).origin === location.origin)
  82. .map((e) => (e.target = "_parent"));
  83. }
  84.  
  85. function iframeScrollbarRemove() {
  86. document.body.style.margin = "-18px auto 0";
  87. }
  88.  
  89. async function mulangoSearchLinksFetch() {
  90. const url = new URL(location.href);
  91. const query = url.searchParams.get("q") || "";
  92. if (!query) return [];
  93. const result = await bilangTranslate(query);
  94. const searchLinks = result.flatMap((e) =>
  95. e.translations.map((t) => {
  96. const u2 = new URL(url.href);
  97. return u2.searchParams.set("q", t.text), u2.href;
  98. })
  99. );
  100. return searchLinks;
  101. }
  102. async function bilangTranslate(s) {
  103. const translate = (
  104. await import(
  105. "https://cdn.skypack.dev/@snomiao/google-translate-api-browser"
  106. )
  107. ).setCORS("https://google-translate-cors.vercel.app/api?url=", {
  108. encode: true,
  109. });
  110. // const { translate } = await import("https://cdn.skypack.dev/snotran");
  111. await translate('give me a try', { to: "zh" })
  112. .then((e) => e.text)
  113. .catch(console.error)
  114. return [
  115. await translate(s, { to: "zh" })
  116. .then((e) => e.text)
  117. .catch(console.error),
  118. await translate(s, { to: "en" })
  119. .then((e) => e.text)
  120. .catch(console.error),
  121. ].filter((e) => e);
  122. }
  123.  
  124. // async function bingTranslate(text = 'hello, world') {
  125. // const body = JSON.stringify([{ Text: text }]);
  126. // const exampleResponse = [
  127. // {
  128. // detectedLanguage: {
  129. // language: 'en',
  130. // score: 1,
  131. // },
  132. // translations: [
  133. // {
  134. // text: '我真的很想把你的车开几个圈。',
  135. // to: 'zh-Hans',
  136. // },
  137. // {
  138. // text: 'I would really like to drive your car around the block a few times.',
  139. // to: 'en',
  140. // },
  141. // ],
  142. // },
  143. // ];
  144. // const rak = await rakGet();
  145. // if (!rak) throw new Error('no rapid api key is found');
  146. // const response = await fetch(
  147. // 'https://microsoft-translator-text.p.rapidapi.com/translate?to%5B0%5D=zh%2Cen&api-version=3.0&profanityAction=NoAction&textType=plain',
  148. // {
  149. // method: 'POST',
  150. // headers: {
  151. // 'content-type': 'application/json',
  152. // 'X-RapidAPI-Key': rak,
  153. // 'X-RapidAPI-Host': 'microsoft-translator-text.p.rapidapi.com',
  154. // },
  155. // body,
  156. // }
  157. // )
  158. // .then((r) => r.json())
  159. // .catch(async (e) => await rapidAPIKeyLoadNew('error: ' + e.message));
  160. // return response /* as exampleResponse */;
  161. // }
  162.  
  163. // async function rapidAPIKeyLoad() {
  164. // return (await rakGet()) || (await rapidAPIKeyLoadNew());
  165. // }
  166. // async function rapidAPIKeyLoadNew(msg = '') {
  167. // alert(introPrompt);
  168. // const w = window.open(
  169. // introURL,
  170. // 'google-multilang-user-js-rapidAPIKey',
  171. // 'width=1024,height=768'
  172. // );
  173. // await new Promise((r) => {
  174. // let id = setInterval(() => w.closed && (r(), clearInterval(id)));
  175. // });
  176. // return (
  177. // await rakSet(
  178. // prompt((msg + '\n' + introPrompt).trim(), await rakGet()) || await rakGet() || ''
  179. // ),
  180. // await rakGet()
  181. // );
  182. // }
  183.  
  184. // async function rakGet() {
  185. // return (
  186. // (await globalThis.GM_getValue?.('rapidAPIKey', '')) ||
  187. // localStorage.getItem('rapidAPIKey')
  188. // )?.replace(/^null$/, '');
  189. // }
  190. // async function rakSet(v = '') {
  191. // return (
  192. // (await (globalThis.GM_setValue &&
  193. // ((await globalThis.GM_setValue('rapidAPIKey', v || '')) ||
  194. // true))) ||
  195. // localStorage.setItem('rapidAPIKey', v || '') ||
  196. // true
  197. // );
  198. // }