Nessun oggetto della modifica
Etichetta: Ripristino manuale
Nessun oggetto della modifica
 
(18 versioni intermedie di uno stesso utente non sono mostrate)
Riga 1: Riga 1:
/* ======================= CommonTranslateUpdate.js (INTEGRAZIONE COMPLETA) ======================= */
/* ======================= CommonTranslateUpdate.js (proxy + sezioni protette) ======================= */


console.log("🔄 Caricamento CommonTranslateUpdate.js...");
console.log("🔄 Caricamento CommonTranslateUpdate.js...");


if (window.CommonTranslateUpdateLoaded) {
if (window.CommonTranslateUpdateLoaded) {
    console.warn("⚠️ CommonTranslateUpdate.js è già stato caricato.");
  console.warn("⚠️ CommonTranslateUpdate.js è già stato caricato.");
} else {
} else {
    window.CommonTranslateUpdateLoaded = true;
  window.CommonTranslateUpdateLoaded = true;
    window.linguaOrigine = localStorage.getItem("linguaOrigine") || document.documentElement.lang || "en";
  window.linguaOrigine = localStorage.getItem("linguaOrigine") || document.documentElement.lang || "en";
  window.OPENAI_PROXY_URL = "/dashboard/api/openai_project_gpt.php";
  console.log("✅ CommonTranslateUpdate.js caricato correttamente!");
}


    if (!window.apiKey) {
/* ======================= Utility ======================= */
        window.apiKey = "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"; // Inserisci la tua API key qui
    }


     console.log("✅ CommonTranslateUpdate.js caricato correttamente!");
function stripMarkers(t) {
  return (t || "")
     .replace(/^\s*[-–—]*\s*(TESTO\s*INIZIO|TEXT\s*START)\s*[-–—]*\s*$/gmi, "")
    .replace(/^\s*[-–—]*\s*(TESTO\s*FINE|TEXT\s*END)\s*[-–—]*\s*$/gmi, "")
    .replace(/---\s*(TESTO\s*INIZIO|TEXT\s*START)\s*---/gi, "")
    .replace(/---\s*(TESTO\s*FINE|TEXT\s*END)\s*---/gi, "")
    .trim();
}
}


/* ======================= Nuova segmentazione avanzata ======================= */
const HEADER_RE = /^={2,5}\s*([^=]+?)\s*={2,5}\s*$/m;
function segmentaBloccoMediaWiki(wikitesto) {
const isBiblioHeaderText = (s) => /^\s*(bibliography|references|riferimenti)\b/i.test((s||"").trim());
    const blocchiProtetti = [
const isListItem = (s) => /^\s*(?:\d+\.\s+|[#*;:]+\s*)/.test(s||"");
        /<math>[\s\S]*?<\/math>/g,
        /<ref[^>]*>[\s\S]*?<\/ref>/g,
        /<gallery[\s\S]*?<\/gallery>/g,
        /{{(?:[^{}]|{{[^{}]*}})*}}/g
    ];


    let placeholderMap = {};
/* ======================= Protezioni / Segnaposti ======================= */
    let placeholderIndex = 0;
/* Mettiamo in cassaforte: bibliografia (intera sezione), tabelle, ref, math, gallery, templates, code/pre, references */
function buildPlaceholder(keyPrefix, idx) { return `%%${keyPrefix}_${idx}%%`; }


    blocchiProtetti.forEach((regex) => {
function protectBlocks(originalText) {
        wikitesto = wikitesto.replace(regex, (match) => {
  let text = originalText;
            const key = `%%SEGMENTO_${placeholderIndex++}%%`;
  const map = {};
            placeholderMap[key] = match;
  let i = 0;
            return key;
 
        });
  // 1) Bibliografia/References/Riferimenti: sezione intatta (header + corpo fino al prossimo header)
    });
  //  - Sostituiamo OGNI sezione biblio con un placeholder unico
  text = text.replace(
    /(^={2,5}\s*(?:Bibliography|References|Riferimenti)\s*={2,5}\s*$[\s\S]*?)(?=^={2,5}|\Z)/gmi,
    (m) => {
      const key = buildPlaceholder("BIBLIO", i++);
      map[key] = m;
      return key;
    }
  );


    const segmenti = wikitesto
  // 2) Tabelle wiki e HTML
        .split(/\n(?==|===|{{CD1}})/)
  const protectRegexes = [
         .map(seg => seg.trim())
    /\{\|[\s\S]*?\|\}/g,                    // wiki table
        .filter(seg => seg.length > 0);
    /<table[\s\S]*?<\/table>/gi,            // html table
    /<syntaxhighlight[\s\S]*?<\/syntaxhighlight>/gi,
    /<pre[\s\S]*?<\/pre>/gi,
    /<code[\s\S]*?<\/code>/gi,
    /<gallery[\s\S]*?<\/gallery>/gi,
    /<math>[\s\S]*?<\/math>/gi,
    /<ref[^>]*>[\s\S]*?<\/ref>/gi,
    /<references\s*\/>/gi,
    /{{(?:[^{}]|\{\{[^{}]*\}\})*}}/g         // templates (greedy-safe)
  ];


    const segmentiFinali = segmenti.map(seg => {
  protectRegexes.forEach((rx) => {
        Object.entries(placeholderMap).forEach(([key, valore]) => {
    text = text.replace(rx, (m) => {
            seg = seg.replaceAll(key, valore);
      const key = buildPlaceholder("SAFE", i++);
        });
      map[key] = m;
        return seg;
      return key;
     });
     });
  });


    return segmentiFinali;
  return { text, map };
}
}
/* ======================= Ripristina segmenti salvati ======================= */
 
function ripristinaSegmenti(text) {
function restoreBlocks(text, map) {
    if (!window.segmentiSalvati) return text;
  if (!map) return text;
     return text.replace(/%%SEGMENTO_(\d+)%%/g, function(match, index) {
  // Ripeti fino a che non ci sono più placeholder da sostituire
        const segmento = window.segmentiSalvati[parseInt(index)];
  let changed = true;
        return segmento ? segmento : match;
  while (changed) {
     changed = false;
    text = text.replace(/%%(BIBLIO|SAFE)_(\d+)%%/g, (m) => {
      if (map[m] !== undefined) { changed = true; return map[m]; }
      return m;
     });
     });
  }
  return text;
}
/* ======================= Segmentazione (per sezioni) ======================= */
function segmentaInSezioni(wikitestoProtetto) {
  // split per header di livello 2..5. Manteniamo anche eventuale intro prima del primo header.
  const parts = wikitestoProtetto.split(/\n(?=^={2,5}[^=\n]+={2,5}\s*$)/m);
  return parts.map(s => s.trim()).filter(Boolean);
}
}


/* ======================= UI: avvio, popup, ecc. ======================= */


/* ======================= AVVIO TRADUZIONE ======================= */
async function traduciTestoUpdate() {
async function traduciTestoUpdate() {
    const conferma = confirm("⚡ Vuoi tradurre il capitolo?");
  const conferma = confirm("⚡ Vuoi tradurre il capitolo?");
    if (!conferma) return;
  if (!conferma) return;


    const linguaScelta = prompt("🌍 Scegli la lingua:\nit = Italiano\nen = Inglese\nfr = Francese\nes = Spagnolo\nde = Tedesco\ntutte = Tutte le lingue", "en");
  const linguaScelta = prompt(
    if (!linguaScelta) return;
    "🌍 Scegli la lingua:\n\nit = Italiano\nen = Inglese\nfr = Francese\nes = Spagnolo\nde = Tedesco\ntutte = Tutte le lingue",
    "en"
  );
  if (!linguaScelta) return;


    const lingue = ["it", "en", "fr", "es", "de"];
  const lingue = ["it", "en", "fr", "es", "de"];
    const lingueDaTradurre = linguaScelta === "tutte" ? lingue : [linguaScelta];
  const lingueDaTradurre = linguaScelta === "tutte" ? lingue : [linguaScelta];
    localStorage.setItem("lingueDaTradurre", JSON.stringify(lingueDaTradurre));
  localStorage.setItem("lingueDaTradurre", JSON.stringify(lingueDaTradurre));


    verificaURLCapitoli(lingueDaTradurre);
  verificaURLCapitoli(lingueDaTradurre);
}
}


function verificaURLCapitoli(lingueDaTradurre) {
function verificaURLCapitoli(lingueDaTradurre) {
    let formHtml = `<form id="urlForm">`;
  let formHtml = `<form id="urlForm">`;
    for (let lang of lingueDaTradurre) {
  for (let lang of lingueDaTradurre) {
        formHtml += `
    formHtml += `
        <label for="url_${lang}">${lang.toUpperCase()}:</label>
      <label for="url_${lang}">${lang.toUpperCase()}:</label>
        <input type="text" id="url_${lang}" value="${localStorage.getItem(`urlCapitolo_${lang}`) || ""}" style="width:100%;margin-bottom:5px;"><br>`;
      <input type="text" id="url_${lang}" value="${localStorage.getItem(`urlCapitolo_${lang}`) || ""}" style="width:100%;margin-bottom:5px;"><br>`;
    }
  }
    formHtml += `</form>`;
  formHtml += `</form>`;


    let popup = document.createElement("div");
  let popup = document.createElement("div");
    popup.innerHTML = `
  popup.innerHTML = `
     <div id="popup-url-traduzione" style="position:fixed; top:50%; left:50%; transform:translate(-50%, -50%);
     <div id="popup-url-traduzione" style="position:fixed; top:50%; left:50%; transform:translate(-50%, -50%);
        background:white; padding:20px; border-radius:10px; box-shadow:0px 4px 6px rgba(0,0,0,0.2); z-index:2000; font-family:Arial, sans-serif;">
      background:white; padding:20px; border-radius:10px; box-shadow:0px 4px 6px rgba(0,0,0,0.2); z-index:2000; font-family:Arial, sans-serif;">
      <h3>🔍 Imposta gli URL per la traduzione</h3>
      <p>
        🧠 <b>Parole:</b> <span id="conteggio-parole">0</span><br>
        🔢 <b>Token stimati:</b> <span id="conteggio-token">0</span><br>
        💰 <b>Costo stimato:</b> <span id="costo-stimato">$0.0000</span>
      </p>
      ${formHtml}
      <div style="margin-top:10px; text-align:right;">
        <button id="salvaUrlBtn" type="button">✅ Salva URL</button>
        <button id="chiudiPopup" type="button">❌ Annulla</button>
      </div>
    </div>`;
  document.body.appendChild(popup);


        <h3>🔍 Imposta gli URL per la traduzione</h3>
  const testo = ottieniTestoDaEditor();
  const parole = testo.trim().split(/\s+/).filter(Boolean).length;
  const tokenStimati = Math.ceil(parole / 0.75);
  const costo = (tokenStimati * 0.00002).toFixed(4);


        <p>
  try {
          🧠 <b>Parole:</b> <span id="conteggio-parole">0</span><br>
    document.getElementById("conteggio-parole").innerText = parole;
          🔢 <b>Token stimati:</b> <span id="conteggio-token">0</span><br>
    document.getElementById("conteggio-token").innerText = tokenStimati;
          💰 <b>Costo stimato:</b> <span id="costo-stimato">$0.0000</span>
    document.getElementById("costo-stimato").innerText = `$${costo}`;
        </p>
  } catch {}


        ${formHtml}
  setTimeout(() => {
 
     const salvaBtn = document.getElementById("salvaUrlBtn");
        <div style="margin-top:10px; text-align:right;">
     const chiudiBtn = document.getElementById("chiudiPopup");
          <button id="salvaUrlBtn" type="button">✅ Salva URL</button>
     if (salvaBtn) {
          <button id="chiudiPopup" type="button">❌ Annulla</button>
      salvaBtn.onclick = function () {
        </div>
         lingueDaTradurre.forEach(lang => {
    </div>
          const newUrl = document.getElementById(`url_${lang}`).value.trim();
    `;
          localStorage.setItem(`urlCapitolo_${lang}`, newUrl);
 
        });
    document.body.appendChild(popup);
        document.body.removeChild(popup);
 
         avviaTraduzioneConSegmentazione(lingueDaTradurre);
    const testo = ottieniTestoDaEditor();
      };
     const parole = testo.trim().split(/\s+/).length;
     const tokenStimati = Math.ceil(parole / 0.75);
     const costo = (tokenStimati * 0.00002).toFixed(4);
 
    try {
         document.getElementById("conteggio-parole").innerText = parole;
        document.getElementById("conteggio-token").innerText = tokenStimati;
        document.getElementById("costo-stimato").innerText = `$${costo}`;
    } catch (e) {
         console.warn("⚠️ Conteggio parole/token non aggiornato:", e);
     }
     }
    if (chiudiBtn) chiudiBtn.onclick = () => document.body.removeChild(popup);
  }, 100);
}


    setTimeout(() => {
function avviaTraduzioneConSegmentazione(lingueDaTradurre) {
        const salvaBtn = document.getElementById("salvaUrlBtn");
  const testo = ottieniTestoDaEditor();
        const chiudiBtn = document.getElementById("chiudiPopup");


        if (salvaBtn) {
  // Proteggi prima, poi segmenta
            salvaBtn.onclick = function () {
  const { text: protetto, map: PHmap } = protectBlocks(testo);
                lingueDaTradurre.forEach(lang => {
  window._CTU_PHMAP = PHmap; // metto in globale per ripristino finale
                    const newUrl = document.getElementById(`url_${lang}`).value.trim();
                    localStorage.setItem(`urlCapitolo_${lang}`, newUrl);
                });
                document.body.removeChild(popup);
                avviaTraduzioneConSegmentazione(lingueDaTradurre);
            };
        }


        if (chiudiBtn) {
  const sezioni = segmentaInSezioni(protetto);
            chiudiBtn.onclick = function () {
  const errori = analizzaErroriSegmentazione(sezioni);
                document.body.removeChild(popup);
  sostituisciTestoSegmentato(sezioni, errori);
            };
  mostraPopupConferma(sezioni, errori, lingueDaTradurre);
        }
    }, 100);
}
}


function avviaTraduzioneConSegmentazione(lingueDaTradurre) {
/* ======================= Supporto UI ======================= */
    const testo = ottieniTestoDaEditor();
    const blocchi = segmentaBloccoMediaWiki(testo); // NUOVA FUNZIONE USATA QUI
    const errori = analizzaErroriSegmentazione(blocchi);
    sostituisciTestoSegmentato(blocchi, errori);
    mostraPopupConferma(blocchi, errori, lingueDaTradurre);
}
 
/*========= Errori ed invio ==========*/


function ottieniTestoDaEditor() {
function ottieniTestoDaEditor() {
    const area = document.getElementById("wpTextbox1");
  const area = document.getElementById("wpTextbox1");
    return area ? area.value.trim() : "";
  return area ? area.value.trim() : "";
}
}


function analizzaErroriSegmentazione(blocchi) {
function analizzaErroriSegmentazione(blocchi) {
    let errori = [];
  let errori = [];
    blocchi.forEach((b, i) => {
  blocchi.forEach((b, i) => {
        let err = [];
    let err = [];
        [["[", "]"], ["{", "}"], ["<", ">"]].forEach(([o, c]) => {
    [["[", "]"], ["{", "}"], ["<", ">"]].forEach(([o, c]) => {
            const countO = (b.match(new RegExp(`\\${o}`, "g")) || []).length;
      const countO = (b.match(new RegExp(`\\${o}`, "g")) || []).length;
            const countC = (b.match(new RegExp(`\\${c}`, "g")) || []).length;
      const countC = (b.match(new RegExp(`\\${c}`, "g")) || []).length;
            if (countO !== countC) err.push(`${o}${c} non bilanciate`);
      if (countO !== countC) err.push(`${o}${c} non bilanciate`);
        });
        if (err.length) errori.push({ index: i + 1, dettagli: err });
     });
     });
     return errori;
     if (err.length) errori.push({ index: i + 1, dettagli: err });
  });
  return errori;
}
}


function sostituisciTestoSegmentato(blocchi, errori) {
function sostituisciTestoSegmentato(blocchi, errori) {
    const area = document.getElementById("wpTextbox1");
  const area = document.getElementById("wpTextbox1");
    const finale = blocchi.map((b, i) => {
  const finale = blocchi.map((b, i) => {
        const e = errori.find(x => x.index === i + 1);
    const e = errori.find(x => x.index === i + 1);
        const evidenza = e ? `⚠️ ERRORI:\n- ${e.dettagli.join("\n- ")}\n\n` : "";
    const evidenza = e ? `⚠️ ERRORI:\n- ${e.dettagli.join("\n- ")}\n\n` : "";
        return `${evidenza}🔹 BLOCCO ${i + 1}\n${b}`;
    return `${evidenza}🔹 BLOCCO ${i + 1}\n${b}`;
    });
  });
    area.value = finale.join("\n\n==============================\n\n");
  area.value = finale.join("\n\n==============================\n\n");
}
}


function mostraPopupConferma(blocchi, errori, lingueDaTradurre) {
function mostraPopupConferma(blocchi, errori, lingueDaTradurre) {
    const popup = document.createElement("div");
  const popup = document.createElement("div");
    popup.style = `
  popup.style = `
        position: fixed;
    position: fixed; bottom: 20px; right: 20px; background: white;
        bottom: 20px;
    padding: 15px; border: 2px solid #444; box-shadow: 4px 4px 10px rgba(0,0,0,0.3);
        right: 20px;
    z-index: 9999; resize: both; overflow: auto; cursor: move;`;
        background: white;
  popup.innerHTML = `
        padding: 15px;
    <b>✅ Segmentazione completata</b><br>
        border: 2px solid #444;
    🧠 Blocchi: ${blocchi.length}<br>
        box-shadow: 4px 4px 10px rgba(0,0,0,0.3);
    ${errori.length > 0
        z-index: 9999;
      ? `❗ <span style='color:red;'>${errori.length} errori rilevati. Procedere?</span><br>`
        resize: both;
      : `<span style='color:green;'>✅ Nessun errore rilevato</span><br>`}
        overflow: auto;
    <button id="btn-invia-traduzioni">✅ Invia Traduzioni</button>
        cursor: move;`;
    <button id="btn-salva-segmentato">💾 Salva localmente</button>
    popup.innerHTML = `
    <button onclick="this.parentNode.remove()">❌ Annulla</button>`;
        <b>✅ Segmentazione completata</b><br>
  document.body.appendChild(popup);
        🧠 Token stimati: ~${blocchi.length * 3000}<br>
  dragElement(popup);
        💰 Costo approssimativo: ~$${(blocchi.length * 3000 * 0.00002).toFixed(2)}<br>
        ${errori.length > 0
            ? `❗ <span style='color:red;'>${errori.length} errori rilevati. Procedere?</span><br>`
            : `<span style='color:green;'>✅ Nessun errore rilevato</span><br>`}
        <button id="btn-invia-traduzioni">✅ Invia Traduzioni</button>
        <button id="btn-salva-segmentato">💾 Salva localmente</button>
        <button onclick="this.parentNode.remove()">❌ Annulla</button>`;


    document.body.appendChild(popup);
  document.getElementById("btn-invia-traduzioni").onclick = () => {
     dragElement(popup);
    popup.remove();
 
     lingueDaTradurre.forEach(lang => inviaTraduzione(lang, blocchi));
    document.getElementById("btn-invia-traduzioni").onclick = () => {
  };
        popup.remove();
  document.getElementById("btn-salva-segmentato").onclick = () => {
        lingueDaTradurre.forEach(lang => inviaTraduzione(lang, blocchi));
    const testoSegmentato = document.getElementById("wpTextbox1").value.trim();
    };
    localStorage.setItem("segmentatoDaInviare", testoSegmentato);
    alert("✅ Testo segmentato salvato localmente!");
  };
}


    document.getElementById("btn-salva-segmentato").onclick = () => {
function ricostruisciTesto(blocchi) {
        const testoSegmentato = document.getElementById("wpTextbox1").value.trim();
  return blocchi.map(b => (HEADER_RE.test(b.trim()) ? `\n\n${b.trim()}\n\n` : b.trim()))
        localStorage.setItem("segmentatoDaInviare", testoSegmentato);
                .join("\n\n");
        alert("✅ Testo segmentato salvato localmente!");
    };
}
}


function ricostruisciTestoSegmentato(blocchi) {
/* ======================= Invio a MediaWiki (con ripristino segnaposti) ======================= */
    return blocchi.map(b => {
        return b.trim().match(/^={2,5}[^=]+={2,5}$/)
            ? `\n\n${b.trim()}\n\n`
            : b.trim();
    }).join("\n\n");
}


async function inviaTraduzione(lingua, blocchi) {
async function inviaTraduzione(lingua, blocchi) {
   
  const urlAPI = "https://staging.masticationpedia.org/api.php";
    const urlAPI = "http://188.213.170.162/mediawiki/api.php";
  const urlDest = localStorage.getItem(`urlCapitolo_${lingua}`);
  if (!urlDest) return;


    const urlDestinazione = localStorage.getItem(`urlCapitolo_${lingua}`);
  // CSRF token
    if (!urlDestinazione) return;
  const tokenRes = await fetch(`${urlAPI}?action=query&meta=tokens&type=csrf&format=json`, { credentials: "include" });
  const tokenData = await tokenRes.json();
  const csrfToken = tokenData?.query?.tokens?.csrftoken;


    const tokenRes = await fetch(`${urlAPI}?action=query&meta=tokens&type=csrf&format=json`, { credentials: "include" });
  // Traduci SOLO ciò che NON è biblio o segnaposto
    const tokenData = await tokenRes.json();
  const tradotti = [];
    const csrfToken = tokenData.query.tokens.csrftoken;
  for (const b of blocchi) {
    const raw = (b || "").trim();


     let testoTradotto = ricostruisciTestoSegmentato(
     // Se il blocco è un placeholder di biblio/safe → NON TRADURRE
        await Promise.all(blocchi.map(b => traduciBlocco(b, lingua)))
     if (/^%%(BIBLIO|SAFE)_(\d+)%%$/.test(raw)) {
    );
      tradotti.push(raw);
     testoTradotto = ripristinaSegmenti(testoTradotto); // ✅ AGGIUNTA QUESTA RIGA
      continue;
    const formData = new URLSearchParams();
     }
    formData.append("action", "edit");
    formData.append("title", urlDestinazione.split("/").pop());
    formData.append("text", testoTradotto);
    formData.append("format", "json");
     formData.append("token", csrfToken);


     const response = await fetch(urlAPI, {
    // Se è un header di biblio → potrebbe essere stato già protetto come BIBLIO; ma se non lo fosse, lo lasciamo com’è.
        method: "POST",
     const headerMatch = raw.match(HEADER_RE);
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
    if (headerMatch && isBiblioHeaderText(headerMatch[1])) {
        body: formData,
      tradotti.push(raw); // non tradurre
        credentials: "include"
      continue;
    });
    }
 
    // Altrimenti traduci
    tradotti.push(await traduciBloccoProxy(raw, lingua));
  }
 
  // Ricompone, poi ripristina segnaposti in blocco
  let testoTradotto = ricostruisciTesto(tradotti);
  testoTradotto = restoreBlocks(testoTradotto, window._CTU_PHMAP);
  testoTradotto = stripMarkers(testoTradotto);
 
  // Invio edit
  const form = new URLSearchParams();
  form.append("action", "edit");
  form.append("title", urlDest.split("/").pop());
  form.append("text", testoTradotto);
  form.append("format", "json");
  form.append("token", csrfToken);


     const data = await response.json();
  const resp = await fetch(urlAPI, {
    if (data.edit && data.edit.result === "Success") {
    method: "POST",
        alert(`✅ Traduzione inviata con successo a: ${urlDestinazione}`);
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    } else {
    body: form,
        console.error("❌ Errore invio:", data);
     credentials: "include"
    }
  });
  const data = await resp.json();
  if (data?.edit?.result === "Success") {
    alert(`✅ Traduzione inviata con successo a: ${urlDest}`);
  } else {
    console.error("❌ Errore invio:", data);
  }
}
}


async function traduciBlocco(blocco, lingua) {
/* ======================= Traduzione singolo blocco via proxy (no markers) ======================= */
    try {
async function traduciBloccoProxy(blocco, lingua) {
        const res = await fetch("https://api.openai.com/v1/chat/completions", {
  try {
            method: "POST",
    const res = await fetch(window.OPENAI_PROXY_URL, {
            headers: {
      method: "POST",
                "Content-Type": "application/json",
      headers: { "Content-Type": "application/json" },
                "Authorization": `Bearer ${window.apiKey}`
      body: JSON.stringify({
            },
        model: "gpt-4o",
            body: JSON.stringify({
        prompt: [
                model: "gpt-4o-2024-05-13",
          "You are a professional MediaWiki translator.",
                messages: [
          "Translate the text into " + lingua + ".",
                    { role: "system", content: "You are a professional MediaWiki translator. Preserve all formatting, templates, and syntax. Do not translate the content inside <math>, <ref>, {{...}}, or [[...]]." },
          "Preserve ALL MediaWiki syntax exactly: templates {{...}}, links [[...]], headings == ==, lists (*, #, ;, :), tables {| |}, <math>, <ref>, <gallery>, etc.",
                    { role: "user", content: `Translate this text into ${lingua}:\n\n${blocco}` }
          "Do NOT add or remove items; do NOT summarize; do NOT explain.",
                ],
          "Return ONLY the translated text, no comments, no wrappers."
                temperature: 0.2
        ].join(" ") + "\n\n" + blocco
            })
      })
        });
    });
        const data = await res.json();
    const data = await res.json();
        return data.choices[0].message.content;
    if (data.status !== "ok") throw new Error(data.error || ("HTTP " + res.status));
     } catch (e) {
     return stripMarkers(data.result);
        console.error("❌ Traduzione fallita:", e);
  } catch (e) {
        return "[Errore Traduzione]";
    console.error("❌ Traduzione fallita:", e);
    }
    return "[Errore Traduzione]";
  }
}
}


/* ======================= Drag helper ======================= */
function dragElement(elmnt) {
function dragElement(elmnt) {
    let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  let pos1=0,pos2=0,pos3=0,pos4=0;
    elmnt.onmousedown = dragMouseDown;
  elmnt.onmousedown = dragMouseDown;
    function dragMouseDown(e) {
  function dragMouseDown(e){ e = e || window.event; e.preventDefault(); pos3=e.clientX; pos4=e.clientY; document.onmouseup=closeDragElement; document.onmousemove=elementDrag; }
        e = e || window.event; e.preventDefault();
  function elementDrag(e){ e = e || window.event; e.preventDefault(); pos1=pos3-e.clientX; pos2=pos4-e.clientY; pos3=e.clientX; pos4=e.clientY; elmnt.style.top=(elmnt.offsetTop-pos2)+"px"; elmnt.style.left=(elmnt.offsetLeft-pos1)+"px"; }
        pos3 = e.clientX; pos4 = e.clientY;
  function closeDragElement(){ document.onmouseup=null; document.onmousemove=null; }
        document.onmouseup = closeDragElement;
        document.onmousemove = elementDrag;
    }
    function elementDrag(e) {
        e = e || window.event; e.preventDefault();
        pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY;
        pos3 = e.clientX; pos4 = e.clientY;
        elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
        elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
    }
    function closeDragElement() {
        document.onmouseup = null;
        document.onmousemove = null;
    }
}
}

Versione attuale delle 15:18, 6 set 2025

/* ======================= CommonTranslateUpdate.js (proxy + sezioni protette) ======================= */

console.log("🔄 Caricamento CommonTranslateUpdate.js...");

if (window.CommonTranslateUpdateLoaded) {
  console.warn("⚠️ CommonTranslateUpdate.js è già stato caricato.");
} else {
  window.CommonTranslateUpdateLoaded = true;
  window.linguaOrigine = localStorage.getItem("linguaOrigine") || document.documentElement.lang || "en";
  window.OPENAI_PROXY_URL = "/dashboard/api/openai_project_gpt.php";
  console.log("✅ CommonTranslateUpdate.js caricato correttamente!");
}

/* ======================= Utility ======================= */

function stripMarkers(t) {
  return (t || "")
    .replace(/^\s*[-–—]*\s*(TESTO\s*INIZIO|TEXT\s*START)\s*[-–—]*\s*$/gmi, "")
    .replace(/^\s*[-–—]*\s*(TESTO\s*FINE|TEXT\s*END)\s*[-–—]*\s*$/gmi, "")
    .replace(/---\s*(TESTO\s*INIZIO|TEXT\s*START)\s*---/gi, "")
    .replace(/---\s*(TESTO\s*FINE|TEXT\s*END)\s*---/gi, "")
    .trim();
}

const HEADER_RE = /^={2,5}\s*([^=]+?)\s*={2,5}\s*$/m;
const isBiblioHeaderText = (s) => /^\s*(bibliography|references|riferimenti)\b/i.test((s||"").trim());
const isListItem = (s) => /^\s*(?:\d+\.\s+|[#*;:]+\s*)/.test(s||"");

/* ======================= Protezioni / Segnaposti ======================= */
/* Mettiamo in cassaforte: bibliografia (intera sezione), tabelle, ref, math, gallery, templates, code/pre, references */
function buildPlaceholder(keyPrefix, idx) { return `%%${keyPrefix}_${idx}%%`; }

function protectBlocks(originalText) {
  let text = originalText;
  const map = {};
  let i = 0;

  // 1) Bibliografia/References/Riferimenti: sezione intatta (header + corpo fino al prossimo header)
  //   - Sostituiamo OGNI sezione biblio con un placeholder unico
  text = text.replace(
    /(^={2,5}\s*(?:Bibliography|References|Riferimenti)\s*={2,5}\s*$[\s\S]*?)(?=^={2,5}|\Z)/gmi,
    (m) => {
      const key = buildPlaceholder("BIBLIO", i++);
      map[key] = m;
      return key;
    }
  );

  // 2) Tabelle wiki e HTML
  const protectRegexes = [
    /\{\|[\s\S]*?\|\}/g,                     // wiki table
    /<table[\s\S]*?<\/table>/gi,             // html table
    /<syntaxhighlight[\s\S]*?<\/syntaxhighlight>/gi,
    /<pre[\s\S]*?<\/pre>/gi,
    /<code[\s\S]*?<\/code>/gi,
    /<gallery[\s\S]*?<\/gallery>/gi,
    /<math>[\s\S]*?<\/math>/gi,
    /<ref[^>]*>[\s\S]*?<\/ref>/gi,
    /<references\s*\/>/gi,
    /{{(?:[^{}]|\{\{[^{}]*\}\})*}}/g         // templates (greedy-safe)
  ];

  protectRegexes.forEach((rx) => {
    text = text.replace(rx, (m) => {
      const key = buildPlaceholder("SAFE", i++);
      map[key] = m;
      return key;
    });
  });

  return { text, map };
}

function restoreBlocks(text, map) {
  if (!map) return text;
  // Ripeti fino a che non ci sono più placeholder da sostituire
  let changed = true;
  while (changed) {
    changed = false;
    text = text.replace(/%%(BIBLIO|SAFE)_(\d+)%%/g, (m) => {
      if (map[m] !== undefined) { changed = true; return map[m]; }
      return m;
    });
  }
  return text;
}

/* ======================= Segmentazione (per sezioni) ======================= */
function segmentaInSezioni(wikitestoProtetto) {
  // split per header di livello 2..5. Manteniamo anche eventuale intro prima del primo header.
  const parts = wikitestoProtetto.split(/\n(?=^={2,5}[^=\n]+={2,5}\s*$)/m);
  return parts.map(s => s.trim()).filter(Boolean);
}

/* ======================= UI: avvio, popup, ecc. ======================= */

async function traduciTestoUpdate() {
  const conferma = confirm("⚡ Vuoi tradurre il capitolo?");
  if (!conferma) return;

  const linguaScelta = prompt(
    "🌍 Scegli la lingua:\n\nit = Italiano\nen = Inglese\nfr = Francese\nes = Spagnolo\nde = Tedesco\ntutte = Tutte le lingue",
    "en"
  );
  if (!linguaScelta) return;

  const lingue = ["it", "en", "fr", "es", "de"];
  const lingueDaTradurre = linguaScelta === "tutte" ? lingue : [linguaScelta];
  localStorage.setItem("lingueDaTradurre", JSON.stringify(lingueDaTradurre));

  verificaURLCapitoli(lingueDaTradurre);
}

function verificaURLCapitoli(lingueDaTradurre) {
  let formHtml = `<form id="urlForm">`;
  for (let lang of lingueDaTradurre) {
    formHtml += `
      <label for="url_${lang}">${lang.toUpperCase()}:</label>
      <input type="text" id="url_${lang}" value="${localStorage.getItem(`urlCapitolo_${lang}`) || ""}" style="width:100%;margin-bottom:5px;"><br>`;
  }
  formHtml += `</form>`;

  let popup = document.createElement("div");
  popup.innerHTML = `
    <div id="popup-url-traduzione" style="position:fixed; top:50%; left:50%; transform:translate(-50%, -50%);
      background:white; padding:20px; border-radius:10px; box-shadow:0px 4px 6px rgba(0,0,0,0.2); z-index:2000; font-family:Arial, sans-serif;">
      <h3>🔍 Imposta gli URL per la traduzione</h3>
      <p>
        🧠 <b>Parole:</b> <span id="conteggio-parole">0</span><br>
        🔢 <b>Token stimati:</b> <span id="conteggio-token">0</span><br>
        💰 <b>Costo stimato:</b> <span id="costo-stimato">$0.0000</span>
      </p>
      ${formHtml}
      <div style="margin-top:10px; text-align:right;">
        <button id="salvaUrlBtn" type="button">✅ Salva URL</button>
        <button id="chiudiPopup" type="button">❌ Annulla</button>
      </div>
    </div>`;
  document.body.appendChild(popup);

  const testo = ottieniTestoDaEditor();
  const parole = testo.trim().split(/\s+/).filter(Boolean).length;
  const tokenStimati = Math.ceil(parole / 0.75);
  const costo = (tokenStimati * 0.00002).toFixed(4);

  try {
    document.getElementById("conteggio-parole").innerText = parole;
    document.getElementById("conteggio-token").innerText = tokenStimati;
    document.getElementById("costo-stimato").innerText = `$${costo}`;
  } catch {}

  setTimeout(() => {
    const salvaBtn = document.getElementById("salvaUrlBtn");
    const chiudiBtn = document.getElementById("chiudiPopup");
    if (salvaBtn) {
      salvaBtn.onclick = function () {
        lingueDaTradurre.forEach(lang => {
          const newUrl = document.getElementById(`url_${lang}`).value.trim();
          localStorage.setItem(`urlCapitolo_${lang}`, newUrl);
        });
        document.body.removeChild(popup);
        avviaTraduzioneConSegmentazione(lingueDaTradurre);
      };
    }
    if (chiudiBtn) chiudiBtn.onclick = () => document.body.removeChild(popup);
  }, 100);
}

function avviaTraduzioneConSegmentazione(lingueDaTradurre) {
  const testo = ottieniTestoDaEditor();

  // Proteggi prima, poi segmenta
  const { text: protetto, map: PHmap } = protectBlocks(testo);
  window._CTU_PHMAP = PHmap; // metto in globale per ripristino finale

  const sezioni = segmentaInSezioni(protetto);
  const errori = analizzaErroriSegmentazione(sezioni);
  sostituisciTestoSegmentato(sezioni, errori);
  mostraPopupConferma(sezioni, errori, lingueDaTradurre);
}

/* ======================= Supporto UI ======================= */

function ottieniTestoDaEditor() {
  const area = document.getElementById("wpTextbox1");
  return area ? area.value.trim() : "";
}

function analizzaErroriSegmentazione(blocchi) {
  let errori = [];
  blocchi.forEach((b, i) => {
    let err = [];
    [["[", "]"], ["{", "}"], ["<", ">"]].forEach(([o, c]) => {
      const countO = (b.match(new RegExp(`\\${o}`, "g")) || []).length;
      const countC = (b.match(new RegExp(`\\${c}`, "g")) || []).length;
      if (countO !== countC) err.push(`${o}${c} non bilanciate`);
    });
    if (err.length) errori.push({ index: i + 1, dettagli: err });
  });
  return errori;
}

function sostituisciTestoSegmentato(blocchi, errori) {
  const area = document.getElementById("wpTextbox1");
  const finale = blocchi.map((b, i) => {
    const e = errori.find(x => x.index === i + 1);
    const evidenza = e ? `⚠️ ERRORI:\n- ${e.dettagli.join("\n- ")}\n\n` : "";
    return `${evidenza}🔹 BLOCCO ${i + 1}\n${b}`;
  });
  area.value = finale.join("\n\n==============================\n\n");
}

function mostraPopupConferma(blocchi, errori, lingueDaTradurre) {
  const popup = document.createElement("div");
  popup.style = `
    position: fixed; bottom: 20px; right: 20px; background: white;
    padding: 15px; border: 2px solid #444; box-shadow: 4px 4px 10px rgba(0,0,0,0.3);
    z-index: 9999; resize: both; overflow: auto; cursor: move;`;
  popup.innerHTML = `
    <b>✅ Segmentazione completata</b><br>
    🧠 Blocchi: ${blocchi.length}<br>
    ${errori.length > 0
      ? `❗ <span style='color:red;'>${errori.length} errori rilevati. Procedere?</span><br>`
      : `<span style='color:green;'>✅ Nessun errore rilevato</span><br>`}
    <button id="btn-invia-traduzioni">✅ Invia Traduzioni</button>
    <button id="btn-salva-segmentato">💾 Salva localmente</button>
    <button onclick="this.parentNode.remove()">❌ Annulla</button>`;
  document.body.appendChild(popup);
  dragElement(popup);

  document.getElementById("btn-invia-traduzioni").onclick = () => {
    popup.remove();
    lingueDaTradurre.forEach(lang => inviaTraduzione(lang, blocchi));
  };
  document.getElementById("btn-salva-segmentato").onclick = () => {
    const testoSegmentato = document.getElementById("wpTextbox1").value.trim();
    localStorage.setItem("segmentatoDaInviare", testoSegmentato);
    alert("✅ Testo segmentato salvato localmente!");
  };
}

function ricostruisciTesto(blocchi) {
  return blocchi.map(b => (HEADER_RE.test(b.trim()) ? `\n\n${b.trim()}\n\n` : b.trim()))
                .join("\n\n");
}

/* ======================= Invio a MediaWiki (con ripristino segnaposti) ======================= */

async function inviaTraduzione(lingua, blocchi) {
  const urlAPI = "https://staging.masticationpedia.org/api.php";
  const urlDest = localStorage.getItem(`urlCapitolo_${lingua}`);
  if (!urlDest) return;

  // CSRF token
  const tokenRes = await fetch(`${urlAPI}?action=query&meta=tokens&type=csrf&format=json`, { credentials: "include" });
  const tokenData = await tokenRes.json();
  const csrfToken = tokenData?.query?.tokens?.csrftoken;

  // Traduci SOLO ciò che NON è biblio o segnaposto
  const tradotti = [];
  for (const b of blocchi) {
    const raw = (b || "").trim();

    // Se il blocco è un placeholder di biblio/safe → NON TRADURRE
    if (/^%%(BIBLIO|SAFE)_(\d+)%%$/.test(raw)) {
      tradotti.push(raw);
      continue;
    }

    // Se è un header di biblio → potrebbe essere stato già protetto come BIBLIO; ma se non lo fosse, lo lasciamo com’è.
    const headerMatch = raw.match(HEADER_RE);
    if (headerMatch && isBiblioHeaderText(headerMatch[1])) {
      tradotti.push(raw); // non tradurre
      continue;
    }

    // Altrimenti traduci
    tradotti.push(await traduciBloccoProxy(raw, lingua));
  }

  // Ricompone, poi ripristina segnaposti in blocco
  let testoTradotto = ricostruisciTesto(tradotti);
  testoTradotto = restoreBlocks(testoTradotto, window._CTU_PHMAP);
  testoTradotto = stripMarkers(testoTradotto);

  // Invio edit
  const form = new URLSearchParams();
  form.append("action", "edit");
  form.append("title", urlDest.split("/").pop());
  form.append("text", testoTradotto);
  form.append("format", "json");
  form.append("token", csrfToken);

  const resp = await fetch(urlAPI, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: form,
    credentials: "include"
  });
  const data = await resp.json();
  if (data?.edit?.result === "Success") {
    alert(`✅ Traduzione inviata con successo a: ${urlDest}`);
  } else {
    console.error("❌ Errore invio:", data);
  }
}

/* ======================= Traduzione singolo blocco via proxy (no markers) ======================= */
async function traduciBloccoProxy(blocco, lingua) {
  try {
    const res = await fetch(window.OPENAI_PROXY_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        model: "gpt-4o",
        prompt: [
          "You are a professional MediaWiki translator.",
          "Translate the text into " + lingua + ".",
          "Preserve ALL MediaWiki syntax exactly: templates {{...}}, links [[...]], headings == ==, lists (*, #, ;, :), tables {| |}, <math>, <ref>, <gallery>, etc.",
          "Do NOT add or remove items; do NOT summarize; do NOT explain.",
          "Return ONLY the translated text, no comments, no wrappers."
        ].join(" ") + "\n\n" + blocco
      })
    });
    const data = await res.json();
    if (data.status !== "ok") throw new Error(data.error || ("HTTP " + res.status));
    return stripMarkers(data.result);
  } catch (e) {
    console.error("❌ Traduzione fallita:", e);
    return "[Errore Traduzione]";
  }
}

/* ======================= Drag helper ======================= */
function dragElement(elmnt) {
  let pos1=0,pos2=0,pos3=0,pos4=0;
  elmnt.onmousedown = dragMouseDown;
  function dragMouseDown(e){ e = e || window.event; e.preventDefault(); pos3=e.clientX; pos4=e.clientY; document.onmouseup=closeDragElement; document.onmousemove=elementDrag; }
  function elementDrag(e){ e = e || window.event; e.preventDefault(); pos1=pos3-e.clientX; pos2=pos4-e.clientY; pos3=e.clientX; pos4=e.clientY; elmnt.style.top=(elmnt.offsetTop-pos2)+"px"; elmnt.style.left=(elmnt.offsetLeft-pos1)+"px"; }
  function closeDragElement(){ document.onmouseup=null; document.onmousemove=null; }
}