您需要先安装一个扩展,例如 篡改猴、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 snomiao@gmail.com
- // @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
- );
- }