google multilang search view enja

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

  1. // ==UserScript==
  2. // @name google multilang search view enja
  3. // @name:zh 谷歌多语言搜索 enja
  4. // @namespace snomiao@gmail.com
  5. // @author snomiao@gmail.com
  6. // @version 1.0.1
  7. // @description [snolab] Mulango - en/ja 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 - en/ja 双语学习者的学步车,以并列多语言视角浏览谷歌搜索结果 现支持 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://gist.github.com/snomiao/ecee0271ac7599d526aaa210293e3f43
  16. // @contributionURL https://snomiao.com/donate
  17. // @grant GM_getValue
  18. // @grant GM_setValue
  19. // ==/UserScript==
  20.  
  21. (async function main() {
  22. if (!location.hostname.match(/google|bing/)) return;
  23. if (parent !== window) return iframeSetup();
  24. iframeHeightReceiverSetup();
  25. const searchLinks = await mulangoSearchLinksFetch();
  26. searchLinks.length && mulangoPageReplace(searchLinks);
  27. })();
  28.  
  29. function mulangoPageReplace(searchLinks) {
  30. const iframes = searchLinks.map((src) => `<iframe src="${src}"></iframe>`);
  31. const style = `<style>
  32. body{margin: 0; display: flex; flex-direction: row; }
  33. iframe{flex: auto; height: 100vh; overflow: hidden;border: none; }
  34. </style>`;
  35. document.body.innerHTML = `${style}${iframes}`;
  36. }
  37.  
  38. function iframeHeightReceiverSetup() {
  39. const setHeight = (height = 0) =>
  40. height &&
  41. [...document.querySelectorAll("iframe[src]")].map(
  42. (e) =>
  43. (e.style.height =
  44. Math.max(
  45. Number(String(e.style.height).replace(/\D+/g, "") || 0),
  46. height
  47. ) + "px")
  48. );
  49. window.addEventListener("message", (e) => setHeight(e.data?.height), false);
  50. }
  51. function iframeSetup() {
  52. iframeScrollbarRemove();
  53. const sendHeight = () =>
  54. parent.postMessage?.({ height: document.body.scrollHeight }, "*");
  55. window.addEventListener("resize", sendHeight, false);
  56. window.addEventListener("load", sendHeight, false);
  57. sendHeight();
  58. window.addEventListener("load", iframeLinksSetup, false);
  59. }
  60.  
  61. function iframeLinksSetup() {
  62. return [...document.querySelectorAll("a[href]")]
  63. .filter(({ href }) => new URL(href).origin === location.origin)
  64. .map((e) => (e.target = "_parent"));
  65. }
  66.  
  67. function iframeScrollbarRemove() {
  68. document.body.style.margin = "-18px auto 0";
  69. }
  70.  
  71. async function mulangoSearchLinksFetch() {
  72. const url = new URL(location.href);
  73. const query = url.searchParams.get("q") || "";
  74. if (!query) return [];
  75. const result = await bilangTranslate(query);
  76. const searchLinks = result.map((t) => {
  77. const u2 = new URL(url.href);
  78. u2.searchParams.set("q", t);
  79. return u2.href;
  80. });
  81. return searchLinks;
  82. }
  83. async function bilangTranslate(s) {
  84. const translate = (
  85. await import(
  86. "https://cdn.skypack.dev/@snomiao/google-translate-api-browser"
  87. )
  88. ).setCORS("https://google-translate-cors.vercel.app/api?url=", {
  89. encode: true,
  90. });
  91. return [
  92. await translate(s, { to: "ja" })
  93. .then((e) => e.text)
  94. .catch(console.error),
  95. await translate(s, { to: "en" })
  96. .then((e) => e.text)
  97. .catch(console.error),
  98. ].filter((e) => e);
  99. }