您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
[SNOLAB] [Mulango] Translate Japenese to the second language of your browser.
// ==UserScript== // @name [SNOLAB] [Mulango] myTyping Game Translator // @namespace https://userscript.snomiao.com/ // @author [email protected] // @version 0.2.1 // @description [SNOLAB] [Mulango] Translate Japenese to the second language of your browser. // @match https://typing.twi1.me/game/* // @icon https://www.google.com/s2/favicons?sz=64&domain=twi1.me // @grant none // ==/UserScript== /* Tested Pages: https://typing.twi1.me/game/79902 */ (async function () { const translate = await useTranslator(navigator.languages[1]); questionsLoop().then(); questionsLoopZh().then(); typingLoop().then(); speakingLoop().then(); async function questionsLoop() { while (1) { const cls = ".questions .kanji:not(.translated)"; const e = document.querySelector(cls); if (e) { e.classList.add("translated"); const transcript = await translate(e.textContent); e.innerHTML = e.innerHTML + "\t(" + transcript; continue; } await new Promise((r) => setTimeout(r, 32)); // TODO: upgrade this into Observer Object } } async function questionsLoopZh() { while (1) { const sel = ".questions .kanji.translated:not(.translatedzh)"; // translate users' lang first const e = document.querySelector(sel); if (e) { e.classList.add("translatedzh"); const ts2 = await translate(e.textContent, "zh"); e.setAttribute("title", ts2); continue; } await new Promise((r) => setTimeout(r, 100)); // TODO: upgrade this into Observer Object } } async function speakingLoop() { const changed = edger(""); while (1) { const e = document.querySelector(".mtjGmSc-kana"); if (e && changed(e.textContent)) { document.querySelector(".mtjGmSc-kana").style = "color: #DDD"; await speak(e.textContent); } await new Promise((r) => setTimeout(r, 100)); // TODO: upgrade this into Observer Object } } async function typingLoop() { const changed = edger(""); while (1) { const e = document.querySelector(".mtjGmSc-kanji"); if (e && !translated(e) && changed(e.textContent)) { document.querySelector(".mtjGmSc-roma").style = "display: none"; const textContent = e.textContent; await kanjiTranscriptReplace(e, textContent); const transcript = await translate(textContent); await kanjiTranscriptReplace(e, transcript); const transcriptZH = await translate(textContent, "zh"); await kanjiTranscriptReplace(e, transcript, transcriptZH); } await new Promise((r) => setTimeout(r, 100)); // TODO: upgrade this into Observer Object } function translated(e) { return e.querySelector(".kanji-transcript"); } } async function speakAndTranslate(s) { return await translate(await speaked(s)); k; } })(); async function kanjiTranscriptReplace(e, transcript, title) { const style = "width: 100%;text-align: center;background: white;position: relative;z-index: 1;"; e.querySelector(".kanji-transcript")?.remove(); const div = document.createElement("div"); div.className = "kanji-transcript"; div.innerHTML = transcript; div.style = style; title && div.setAttribute("title", title); e.appendChild(div); await new Promise((r) => setTimeout(r, 1)); } async function useTranslator(initLang = navigator.language) { const translateAPI = ( await import( "https://cdn.skypack.dev/@snomiao/google-translate-api-browser" ) ).setCORS("https://google-translate-cors.vercel.app/api?url=", { encode: true, }); const translate = async (s, lang = initLang) => { if (!s) return; return await translateAPI(s, { to: lang.replace(/-.*/, "") }) .then((e) => e.text) .catch(console.error); }; return localforageCached(limiter(translate, 1e3)); } function validPipor(fn) { // requires the first param is not undefined otherwise return the undefined return (s, ...args) => (s === undefined ? undefined : fn(s, ...args)); } function limiter(fn, wait = 1e3, last = 0) { return async (...args) => { const remain = () => last + wait - +new Date(); while (remain() > 0) await new Promise((r) => setTimeout(r, remain())); const r = await fn(...args); last = +new Date(); return r; }; } function edger(init) { return (e) => (e !== init ? (init = e) : undefined); } function watcher(fetcher, listener, interval = 16) { const changed = edger(); return async () => { while (1) { await validPipor(listener)(changed(await fetcher())); await new Promise((r) => setTimeout(r, interval)); } }; } async function localforageCached(fn) { const hash = (s) => s.slice(0, 16) + s.slice(-16); const { default: cache } = await import( "https://cdn.skypack.dev/@luudjanssen/localforage-cache" ); const in3day = 86400e3 * 3; const cacheName = hash(String(fn)); const cacheInstance = cache.createInstance({ name: cacheName, defaultExpiration: in3day, }); return validPipor(cachedFn); async function cachedFn(...args) { const result = (await cacheInstance?.getItem(JSON.stringify(args))) || (await fn(...args)); await cacheInstance?.setItem(JSON.stringify(args), result); //refresh cache return result; } } async function speaked(text) { return ( speechSynthesis.speak( Object.assign(new SpeechSynthesisUtterance(), { text, lang: "ja" }) ), text ); }