您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
[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,
// ==UserScript== // @name [SNOLAB] [Mulango] Search // @name:zh [SNOLAB] [Mulango] 多语言搜索 // @namespace https://userscript.snomiao.com/ // @author [email protected] // @version 1.0.2 // @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, // @description:zh [snolab] Mulango - 双语学习者的学步车,以并列多语言视角浏览谷歌搜索结果 现支持 Bing & Google, // @match https://*.google.com/search?* // @match https://*.bing.com/search?* // @match https://*/search* // @grant none // @run-at document-start // @license GPL-3.0+ // @supportURL https://github.com/snomiao/userscript.js/issues // @contributionURL https://snomiao.com/donate // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (async function () { if (!location.hostname.match(/google|bing/)) return; const isIframe = parent !== window; if (isIframe) return iframeSetup(); iframeHeightReceiverSetup(); const searchLinks = await mulangoSearchLinksFetch(); searchLinks.length && mulangoPageReplace(searchLinks); })(); function mulangoPageReplace(searchLinks) { const iframes = searchLinks.map((src) => `<iframe src="${src}"></iframe>`); const style = ` <style> body{margin: 0; display: flex; flex-direction: row; } iframe{flex: auto; height: 100vh; overflow: hidden; border: none; } </style> `; document.body.innerHTML = `${style}${iframes}`; } function iframeHeightReceiverSetup() { const setHeight = (height = 0) => height && [...document.querySelectorAll("iframe[src]")].map( (e) => (e.style.height = Math.max( Number(String(e.style.height).replace(/\D+/g, "") || 0), height ) + "px") ); window.addEventListener("message", (e) => setHeight(e.data?.height), false); } function iframeSetup() { iframeScrollbarRemove(); const sendHeight = () => parent.postMessage?.({ height: document.body.scrollHeight }, "*"); window.addEventListener("resize", sendHeight, false); window.addEventListener("load", sendHeight, false); sendHeight(); window.addEventListener("load", iframeLinksSetup, false); } function iframeLinksSetup() { return [...document.querySelectorAll("a[href]")] .filter(({ href }) => new URL(href).origin === location.origin) .map((e) => (e.target = "_parent")); } function iframeScrollbarRemove() { document.body.style.margin = "-18px auto 0"; } async function mulangoSearchLinksFetch() { const url = new URL(location.href); const query = url.searchParams.get("q") || ""; if (!query) return []; const langs = [ ...new Set(navigator.languages.map((e) => e.replace(/-.*/, ""))), ].slice(0, 2); const translate = await useTranslator(); return await Promise.all( langs.map(async (lang) => { const u2 = new URL(url.href); const transcript = (await translate(query, { to: lang })).text; if (transcript === query) return location.href; // try use cache u2.searchParams.set("q", transcript); u2.searchParams.set("lr", "lang_" + lang); // for google search return u2.href; }) ); } async function useTranslator() { return ( await import( "https://cdn.skypack.dev/@snomiao/google-translate-api-browser" ) ).setCORS("https://google-translate-cors.vercel.app/api?url=", { encode: true, }); } async function amap(fn, a) { const r = []; let i = 0; for await (const v of a) r.push(await fn(v, i++, a)); return r; }