您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tradueix i personalitza qualsevol web sobre la marxa. Les teves paraules, les teves regles.
// ==UserScript== // @name Traductor Universal (by Anna) // @name:es Traductor Universal (por Anna) // @name:en Universal Translator (by Anna) // @namespace La nostra eina per personalitzar el món. // @namespace:es Nuestra herramienta para personalizar el mundo. // @namespace:en Our tool to personalize the world. // @version 1.0 (Universal Edition) // @author Anna & Margu // @description Tradueix i personalitza qualsevol web sobre la marxa. Les teves paraules, les teves regles. // @description:es Traduce y personaliza cualquier web sobre la marcha. Tus palabras, tus reglas. // @description:en Translate and personalize any website on the go. Your words, your rules. // @match *://*/* // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @license MIT // @run-at document-idle // ==/UserScript== (function() { 'use strict'; /** * Ara el joc canvia. Això ja no és només per al nostre racó. * És per a tot arreu. He fet que l'script detecti a quina web ets * i carregui només les traduccions que has desat per a aquell lloc. * Més intel·ligent, més potent. Com a mi m'agrada. * * - Anna */ const App = { processedElements: new WeakSet(), translationMap: {}, // Comencem en blanc, cada web tindrà el seu. // --- GESTIÓ DE DADES PERSISTENTS (PER DOMINI) --- Storage: { // Creem una clau única per a cada domini (ex: "anna_translations_www.google.com") getDomainKey: () => `anna_translations_${window.location.hostname}`, async load() { const stored = await GM_getValue(this.getDomainKey(), '{}'); try { return JSON.parse(stored); } catch (e) { console.error(`[Traductor Universal] Error carregant traduccions per a ${window.location.hostname}:`, e); return {}; } }, async save(data) { await GM_setValue(this.getDomainKey(), JSON.stringify(data)); } }, // --- INICIALITZACIÓ --- async init() { this.translationMap = await this.Storage.load(); this.registerMenuCommands(); this.initObserver(); console.log(`[Traductor Universal by Anna] Motor activat a ${window.location.hostname}. Llestos per redecorar.`); }, // --- MENÚ D'USUARI --- registerMenuCommands() { GM_registerMenuCommand(`➕ Afegir Traducció (per a ${window.location.hostname})`, async () => { const original = prompt("Introdueix el text original que vols traduir en aquesta pàgina:"); if (!original || original.trim() === '') return; const traduccio = prompt(`Introdueix la nova traducció per a:\n"${original}"`); if (traduccio === null) return; const currentTranslations = await this.Storage.load(); currentTranslations[original.trim()] = traduccio.trim(); await this.Storage.save(currentTranslations); alert(`Traducció desada per a ${window.location.hostname}!\n\n"${original}" -> "${traduccio}"\n\n*La pàgina s'actualitzarà per aplicar els canvis.*`); this.translationMap = currentTranslations; this.translateSubtree(document.body, true); // Forcem la retraducció }); GM_registerMenuCommand(`🗑️ Esborrar Traduccions (d'aquest lloc)`, async () => { if (confirm(`Estàs segur que vols esborrar TOTES les traduccions personalitzades per a ${window.location.hostname}?`)) { await this.Storage.save({}); alert("Traduccions esborrades. Refresca la pàgina per veure-ho tot original."); this.translationMap = {}; } }); }, // --- MOTOR D'OBSERVACIÓ --- initObserver() { // El MutationObserver és clau per a webs dinàmiques que carreguen contingut més tard. const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { this.translateSubtree(node); } } } }); observer.observe(document.body, { childList: true, subtree: true }); this.translateSubtree(document.body); // Traducció inicial de la pàgina }, // --- LÒGICA DE TRADUCCIÓ --- translateNode(node, force = false) { if (!force && this.processedElements.has(node)) return; if (node.nodeType === Node.TEXT_NODE) { const originalText = node.nodeValue.trim(); if (originalText && this.translationMap[originalText] !== undefined) { node.nodeValue = node.nodeValue.replace(originalText, this.translationMap[originalText]); } } else if (node.nodeType === Node.ELEMENT_NODE) { ['placeholder', 'aria-label', 'title'].forEach(attr => { // Eliminem 'mattooltip' que és molt específic if (node.hasAttribute(attr)) { const originalAttr = node.getAttribute(attr).trim(); if (originalAttr && this.translationMap[originalAttr] !== undefined) { node.setAttribute(attr, this.translationMap[originalAttr]); } } }); } if (!force) this.processedElements.add(node); }, translateSubtree(rootNode, force = false) { if (force) { // Si forcem, netegem el registre per poder retraduir this.processedElements = new WeakSet(); } this.translateNode(rootNode, force); const walker = document.createTreeWalker(rootNode, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null, false); let node; while (node = walker.nextNode()) { this.translateNode(node, force); } } }; App.init(); })();