MediaWiki:CommonTranslate.js: differenze tra le versioni
Nessun oggetto della modifica Etichetta: Annullato |
Nessun oggetto della modifica Etichetta: Annullato |
||
| Riga 1: | Riga 1: | ||
/* ======================= CommonTranslate.js ======================= */ | /* ======================= CommonTranslate.js (server-proxy) ======================= */ | ||
console.log("🔄 Caricamento CommonTranslate. | console.log("🔄 Caricamento CommonTranslate.js…"); | ||
// | /* Evita doppi caricamenti */ | ||
if (window.CommonTranslateLoaded) { | if (window.CommonTranslateLoaded) { | ||
console.warn("⚠️ CommonTranslate.js è già stato caricato."); | |||
} else { | } else { | ||
window.CommonTranslateLoaded = true; | |||
/* === Config di base (senza API key nel client) === */ | |||
window.linguaOrigine = document.documentElement.lang || "en"; | |||
const API_PROXY = "/dashboard/api/openai_project_gpt.php"; // il “cameriere” | |||
const MODEL_DEFAULT = "gpt-4o"; // il server userà questo | |||
console.log("✅ CommonTranslate.js pronto (via proxy)."); | |||
} | } | ||
/* ======================= Azione principale ======================= */ | |||
/* ======================= | |||
async function traduciTesto() { | async function traduciTesto() { | ||
const area = document.getElementById("wpTextbox1"); | const area = document.getElementById("wpTextbox1"); | ||
if (!area) { | if (!area) { alert("Campo editor non trovato."); return; } | ||
const start = area.selectionStart; | const start = area.selectionStart; | ||
const end = area.selectionEnd; | const end = area.selectionEnd; | ||
const | const sel = area.value.slice(start, end).trim(); | ||
const tutto = ! | const tutto = !sel; | ||
// | // 1) cosa tradurre | ||
const | const onlySel = confirm("📝 Vuoi tradurre SOLO il testo selezionato?\n\n✅ OK = solo selezione\n❌ Annulla = tutto il testo"); | ||
if ( | if (onlySel && !sel) { | ||
alert("⚠️ Nessuna | alert("⚠️ Nessuna selezione. Premi Annulla per tradurre tutto."); | ||
return; | return; | ||
} | } | ||
const testo = tutto ? area.value : sel; | |||
// 2) lingua di destinazione | |||
const lingua = prompt( | |||
// | "🌍 Scegli la lingua di traduzione (it, en, fr, es, de):", | ||
const | |||
"🌍 Scegli la lingua di traduzione: | |||
"it" | "it" | ||
); | ); | ||
if (! | if (!lingua) { console.warn("⚠️ Lingua non indicata."); return; } | ||
// | // 3) segmentazione e (se tutto) pre-check | ||
const blocchi = segmentaTesto(testo); | const blocchi = segmentaTesto(testo); | ||
console.log(`🧩 Segmentazione: ${blocchi.length} blocchi | console.log(`🧩 Segmentazione: ${blocchi.length} blocchi`); | ||
if (tutto) { | if (tutto) { | ||
const errori = analizzaErroriSegmentazione(blocchi); | const errori = analizzaErroriSegmentazione(blocchi); | ||
sostituisciTestoSegmentato(blocchi, errori); | sostituisciTestoSegmentato(blocchi, errori); | ||
mostraPopupConferma(blocchi, errori, | mostraPopupConferma(blocchi, errori, lingua, tutto); | ||
return; // | return; // la traduzione parte dal popup | ||
} | } | ||
// | // 4) traduzione diretta (solo selezione) | ||
const tradotto = await inviaTraduzione(blocchi, | const tradotto = await inviaTraduzione(blocchi, lingua, tutto); | ||
// | // 5) inserimento nel campo editor | ||
if (tutto) { | if (tutto) { | ||
area.value = tradotto; | area.value = tradotto; | ||
} else { | } else { | ||
const prima = area.value.slice(0, start); | const prima = area.value.slice(0, start); | ||
const dopo = area.value.slice(end); | const dopo = area.value.slice(end); | ||
area.value = prima + tradotto + dopo; | area.value = prima + tradotto + dopo; | ||
area.selectionStart = start; | area.selectionStart = start; | ||
area.selectionEnd = start + tradotto.length; | area.selectionEnd = start + tradotto.length; | ||
} | } | ||
// | // 6) notifica a MW | ||
area.dispatchEvent(new Event("input", { bubbles: true })); | area.dispatchEvent(new Event("input", { bubbles: true })); | ||
area.dispatchEvent(new Event("change", { bubbles: true })); | area.dispatchEvent(new Event("change", { bubbles: true })); | ||
console.log("✅ Traduzione completata e inserita | console.log("✅ Traduzione completata e inserita."); | ||
} | } | ||
/* ======================= Utility ======================= */ | |||
function segmentaTesto(testo, maxChars = 6000) { | |||
// segmentazione semplice per blocchi “morbidi” su spazi | |||
const re = new RegExp(`.{1,${maxChars}}(?=\\s|$)`, "gs"); | |||
return testo.match(re) || [testo]; | |||
function segmentaTesto(testo, | |||
} | } | ||
function analizzaErroriSegmentazione(blocchi) { | function analizzaErroriSegmentazione(blocchi) { | ||
const errors = []; | |||
const count = (s, ch) => (s.match(new RegExp(`\\${ch}`, "g")) || []).length; | |||
const braces = [["[", "]"], ["{", "}"], ["<", ">"]]; | |||
blocchi.forEach((b, i) => { | |||
const e = []; | |||
braces.forEach(([o, c]) => { | |||
const co = count(b, o), cc = count(b, c); | |||
if (co !== cc) e.push(`Parentesi ${o}${c} non bilanciate: ${co} ${o}, ${cc} ${c}`); | |||
}); | }); | ||
return | if (e.length) errors.push({ index: i + 1, dettagli: e }); | ||
}); | |||
return errors; | |||
} | } | ||
function sostituisciTestoSegmentato(blocchi, errori) { | function sostituisciTestoSegmentato(blocchi, errori) { | ||
const area = document.getElementById("wpTextbox1"); | |||
if (!area) return; | |||
const text = blocchi.map((b, i) => { | |||
const err = errori.find(x => x.index === i + 1); | |||
const head = err ? `⚠️ ERRORI:\n- ${err.dettagli.join("\n- ")}\n\n` : ""; | |||
return `${head}🔹 BLOCCO ${i + 1}\n` + b; | |||
}).join("\n\n==============================\n\n"); | |||
area.value = text; | |||
console.log("✍️ Testo segmentato inserito nel campo."); | |||
} | } | ||
function mostraPopupConferma(blocchi, errori, | function mostraPopupConferma(blocchi, errori, lingua, tutto) { | ||
const popup = document.createElement("div"); | |||
popup.id = "popup-conferma-traduzione"; | |||
popup.style = ` | |||
position: fixed; bottom: 20px; right: 20px; | |||
background: #fff; padding: 15px; border: 2px solid #444; | |||
box-shadow: 4px 4px 10px rgba(0,0,0,.3); z-index: 9999; | |||
`; | |||
popup.innerHTML = ` | |||
<b>✅ Segmentazione completata</b><br> | |||
Blocchi: ${blocchi.length}<br> | |||
${errori.length ? `<span style="color:#c00;">❗ ${errori.length} blocchi con parentesi non bilanciate</span><br>` : `✅ Nessun errore strutturale<br>`} | |||
<button id="btn-invia-traduzione">✅ Invia traduzione</button> | |||
<button id="btn-close-popup">❌ Annulla</button> | |||
`; | |||
document.body.appendChild(popup); | |||
document.getElementById("btn-close-popup").onclick = () => popup.remove(); | |||
document.getElementById("btn-invia-traduzione").onclick = async () => { | |||
popup.remove(); | |||
try { await inviaTraduzione(blocchi, lingua, tutto); } | |||
catch (e) { | |||
console.error("❌ Errore invio traduzione:", e); | |||
alert("Errore durante l’invio:\n" + (e?.message || e)); | |||
} | } | ||
}; | |||
} | } | ||
// | /* ======================= Traduzione via “cameriere” ======================= */ | ||
async function inviaTraduzione(blocchi, lingua, tutto = true) { | async function inviaTraduzione(blocchi, lingua, tutto = true) { | ||
const | const area = document.getElementById("wpTextbox1"); | ||
if (!area) return ""; | |||
let risultato = ""; | |||
console.log(" | console.log("🧪 inviaTraduzione() → blocchi:", blocchi.length, "lingua:", lingua); | ||
for (let i = 0; i < blocchi.length; i++) { | for (let i = 0; i < blocchi.length; i++) { | ||
console.log(`🚀 Invio blocco ${i + 1}/${blocchi.length} | console.log(`🚀 Invio blocco ${i + 1}/${blocchi.length}…`); | ||
const trad = await traduciBloccoProxy(blocchi[i], lingua); | |||
risultato += (trad || "[Errore Traduzione]") + "\n\n"; | |||
} | } | ||
const | const tradotto = risultato.trim(); | ||
if (tutto) { | if (tutto) { | ||
area.value = | area.value = tradotto; | ||
} else { | } else { | ||
const start = area.selectionStart; | |||
const end = area.selectionEnd; | |||
const prima = area.value.slice(0, start); | const prima = area.value.slice(0, start); | ||
const dopo = area.value.slice(end); | const dopo = area.value.slice(end); | ||
area.value = prima + | area.value = prima + tradotto + dopo; | ||
area.selectionStart = start; | area.selectionStart = start; | ||
area.selectionEnd = start + | area.selectionEnd = start + tradotto.length; | ||
} | } | ||
area.dispatchEvent(new Event("input", { bubbles: true })); | area.dispatchEvent(new Event("input", { bubbles: true })); | ||
area.dispatchEvent(new Event("change", { bubbles: true })); | area.dispatchEvent(new Event("change", { bubbles: true })); | ||
console.log("✅ Traduzione completata."); | console.log("✅ Traduzione completata."); | ||
return | return tradotto; | ||
} | } | ||
async function traduciBloccoProxy(blocco, lingua) { | |||
async function | // Prompt “sicuro”: il server riceve solo il prompt, non la key | ||
try { | const system = "You are a professional MediaWiki translator. Preserve all templates, links, tags and wiki syntax exactly; translate only human-readable text. Keep headings and parameters unchanged."; | ||
const user = `Translate the following MediaWiki wikitext into ${lingua}. Keep formatting/templates/links untouched:\n\n${blocco}`; | |||
const prompt = [ | |||
{ role: "system", content: system }, | |||
{ role: "user", content: user } | |||
]; | |||
try { | |||
const res = await fetch(API_PROXY, { | |||
method: "POST", | |||
headers: { "Content-Type": "application/json" }, | |||
body: JSON.stringify({ | |||
prompt: JSON.stringify(prompt), // il “cameriere” inoltra come chat | |||
model: MODEL_DEFAULT | |||
}) | |||
}); | |||
const data = await res.json().catch(() => ({})); | |||
if (!res.ok || data.status !== "ok") { | |||
const msg = data?.error || `HTTP ${res.status}`; | |||
throw new Error(msg); | |||
} | } | ||
// Il cameriere risponde con {status:"ok", result:"…"} | |||
return (data.result || "").trim(); | |||
} catch (err) { | |||
console.error("❌ Errore da proxy:", err); | |||
return "[Errore Traduzione]"; | |||
} | |||
} | } | ||
// | /* ======================= UI: pulsante in editor ======================= */ | ||
$(function () { | $(function () { | ||
const area = $("#wpTextbox1"); | |||
if (!area.length) return; | |||
if (!$("#pulsanteTraduci").length) { | |||
area.after('<button id="pulsanteTraduci" class="btn">🧠 Traduci contenuto</button>'); | |||
$("#pulsanteTraduci").on("click", traduciTesto); | $("#pulsanteTraduci").on("click", traduciTesto); | ||
} | |||
}); | }); | ||
Versione delle 14:00, 18 ago 2025
/* ======================= CommonTranslate.js (server-proxy) ======================= */
console.log("🔄 Caricamento CommonTranslate.js…");
/* Evita doppi caricamenti */
if (window.CommonTranslateLoaded) {
console.warn("⚠️ CommonTranslate.js è già stato caricato.");
} else {
window.CommonTranslateLoaded = true;
/* === Config di base (senza API key nel client) === */
window.linguaOrigine = document.documentElement.lang || "en";
const API_PROXY = "/dashboard/api/openai_project_gpt.php"; // il “cameriere”
const MODEL_DEFAULT = "gpt-4o"; // il server userà questo
console.log("✅ CommonTranslate.js pronto (via proxy).");
}
/* ======================= Azione principale ======================= */
async function traduciTesto() {
const area = document.getElementById("wpTextbox1");
if (!area) { alert("Campo editor non trovato."); return; }
const start = area.selectionStart;
const end = area.selectionEnd;
const sel = area.value.slice(start, end).trim();
const tutto = !sel;
// 1) cosa tradurre
const onlySel = confirm("📝 Vuoi tradurre SOLO il testo selezionato?\n\n✅ OK = solo selezione\n❌ Annulla = tutto il testo");
if (onlySel && !sel) {
alert("⚠️ Nessuna selezione. Premi Annulla per tradurre tutto.");
return;
}
const testo = tutto ? area.value : sel;
// 2) lingua di destinazione
const lingua = prompt(
"🌍 Scegli la lingua di traduzione (it, en, fr, es, de):",
"it"
);
if (!lingua) { console.warn("⚠️ Lingua non indicata."); return; }
// 3) segmentazione e (se tutto) pre-check
const blocchi = segmentaTesto(testo);
console.log(`🧩 Segmentazione: ${blocchi.length} blocchi`);
if (tutto) {
const errori = analizzaErroriSegmentazione(blocchi);
sostituisciTestoSegmentato(blocchi, errori);
mostraPopupConferma(blocchi, errori, lingua, tutto);
return; // la traduzione parte dal popup
}
// 4) traduzione diretta (solo selezione)
const tradotto = await inviaTraduzione(blocchi, lingua, tutto);
// 5) inserimento nel campo editor
if (tutto) {
area.value = tradotto;
} else {
const prima = area.value.slice(0, start);
const dopo = area.value.slice(end);
area.value = prima + tradotto + dopo;
area.selectionStart = start;
area.selectionEnd = start + tradotto.length;
}
// 6) notifica a MW
area.dispatchEvent(new Event("input", { bubbles: true }));
area.dispatchEvent(new Event("change", { bubbles: true }));
console.log("✅ Traduzione completata e inserita.");
}
/* ======================= Utility ======================= */
function segmentaTesto(testo, maxChars = 6000) {
// segmentazione semplice per blocchi “morbidi” su spazi
const re = new RegExp(`.{1,${maxChars}}(?=\\s|$)`, "gs");
return testo.match(re) || [testo];
}
function analizzaErroriSegmentazione(blocchi) {
const errors = [];
const count = (s, ch) => (s.match(new RegExp(`\\${ch}`, "g")) || []).length;
const braces = [["[", "]"], ["{", "}"], ["<", ">"]];
blocchi.forEach((b, i) => {
const e = [];
braces.forEach(([o, c]) => {
const co = count(b, o), cc = count(b, c);
if (co !== cc) e.push(`Parentesi ${o}${c} non bilanciate: ${co} ${o}, ${cc} ${c}`);
});
if (e.length) errors.push({ index: i + 1, dettagli: e });
});
return errors;
}
function sostituisciTestoSegmentato(blocchi, errori) {
const area = document.getElementById("wpTextbox1");
if (!area) return;
const text = blocchi.map((b, i) => {
const err = errori.find(x => x.index === i + 1);
const head = err ? `⚠️ ERRORI:\n- ${err.dettagli.join("\n- ")}\n\n` : "";
return `${head}🔹 BLOCCO ${i + 1}\n` + b;
}).join("\n\n==============================\n\n");
area.value = text;
console.log("✍️ Testo segmentato inserito nel campo.");
}
function mostraPopupConferma(blocchi, errori, lingua, tutto) {
const popup = document.createElement("div");
popup.id = "popup-conferma-traduzione";
popup.style = `
position: fixed; bottom: 20px; right: 20px;
background: #fff; padding: 15px; border: 2px solid #444;
box-shadow: 4px 4px 10px rgba(0,0,0,.3); z-index: 9999;
`;
popup.innerHTML = `
<b>✅ Segmentazione completata</b><br>
Blocchi: ${blocchi.length}<br>
${errori.length ? `<span style="color:#c00;">❗ ${errori.length} blocchi con parentesi non bilanciate</span><br>` : `✅ Nessun errore strutturale<br>`}
<button id="btn-invia-traduzione">✅ Invia traduzione</button>
<button id="btn-close-popup">❌ Annulla</button>
`;
document.body.appendChild(popup);
document.getElementById("btn-close-popup").onclick = () => popup.remove();
document.getElementById("btn-invia-traduzione").onclick = async () => {
popup.remove();
try { await inviaTraduzione(blocchi, lingua, tutto); }
catch (e) {
console.error("❌ Errore invio traduzione:", e);
alert("Errore durante l’invio:\n" + (e?.message || e));
}
};
}
/* ======================= Traduzione via “cameriere” ======================= */
async function inviaTraduzione(blocchi, lingua, tutto = true) {
const area = document.getElementById("wpTextbox1");
if (!area) return "";
let risultato = "";
console.log("🧪 inviaTraduzione() → blocchi:", blocchi.length, "lingua:", lingua);
for (let i = 0; i < blocchi.length; i++) {
console.log(`🚀 Invio blocco ${i + 1}/${blocchi.length}…`);
const trad = await traduciBloccoProxy(blocchi[i], lingua);
risultato += (trad || "[Errore Traduzione]") + "\n\n";
}
const tradotto = risultato.trim();
if (tutto) {
area.value = tradotto;
} else {
const start = area.selectionStart;
const end = area.selectionEnd;
const prima = area.value.slice(0, start);
const dopo = area.value.slice(end);
area.value = prima + tradotto + dopo;
area.selectionStart = start;
area.selectionEnd = start + tradotto.length;
}
area.dispatchEvent(new Event("input", { bubbles: true }));
area.dispatchEvent(new Event("change", { bubbles: true }));
console.log("✅ Traduzione completata.");
return tradotto;
}
async function traduciBloccoProxy(blocco, lingua) {
// Prompt “sicuro”: il server riceve solo il prompt, non la key
const system = "You are a professional MediaWiki translator. Preserve all templates, links, tags and wiki syntax exactly; translate only human-readable text. Keep headings and parameters unchanged.";
const user = `Translate the following MediaWiki wikitext into ${lingua}. Keep formatting/templates/links untouched:\n\n${blocco}`;
const prompt = [
{ role: "system", content: system },
{ role: "user", content: user }
];
try {
const res = await fetch(API_PROXY, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
prompt: JSON.stringify(prompt), // il “cameriere” inoltra come chat
model: MODEL_DEFAULT
})
});
const data = await res.json().catch(() => ({}));
if (!res.ok || data.status !== "ok") {
const msg = data?.error || `HTTP ${res.status}`;
throw new Error(msg);
}
// Il cameriere risponde con {status:"ok", result:"…"}
return (data.result || "").trim();
} catch (err) {
console.error("❌ Errore da proxy:", err);
return "[Errore Traduzione]";
}
}
/* ======================= UI: pulsante in editor ======================= */
$(function () {
const area = $("#wpTextbox1");
if (!area.length) return;
if (!$("#pulsanteTraduci").length) {
area.after('<button id="pulsanteTraduci" class="btn">🧠 Traduci contenuto</button>');
$("#pulsanteTraduci").on("click", traduciTesto);
}
});