Completa i18n IT/EN/FR/DE/ES su sito pubblico, area autenticata e admin.

Tutte le view marketing, legali, viewer, dashboard e admin usano t(); mailer utente-facing e flash localizzati; admin con LocaleResolver e selettore lingua.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-24 00:38:50 +02:00
co-authored by Cursor
parent fb7068f86c
commit b88f44ab1c
158 changed files with 9444 additions and 1536 deletions
+108 -43
View File
@@ -2,6 +2,73 @@
const root = document.getElementById("regia-app");
if (!root) return;
const DEFAULT_I18N = {
periodUpdated: "Periodo aggiornato",
nowInPeriodTemplate: "Ora in %{period}",
setWonTitle: "Set vinto",
setWonBodyTemplate: "%{winner} ha vinto il set. Chiudere il set?",
matchWonTitle: "Partita vinta",
matchWonBodyTemplate: "%{winner} ha vinto la partita. Chiudere la diretta?",
closeSetTitle: "Chiudi set",
closeSetConfirm: "Confermi la chiusura del set corrente?",
scoreUpdateError: "Errore aggiornamento punteggio",
genericError: "Errore",
setsLineTemplate: "Set %{current} · Set vinti %{home}-%{away}",
setPartialLabelTemplate: "Set %{number}",
freeScore: "Punteggio libero",
stopwatch: "Cronometro",
quarterLabelTemplate: "Q%{n}",
halfLabelTemplate: "%{n}° tempo",
overtimeBasketTemplate: "OT%{n}",
overtimeOther: "Suppl.",
unknownPeriod: "—",
partialsPrefix: "Parziali:",
resumed: "Diretta ripresa",
pausedCover: "Diretta in pausa — copertina in onda",
closed: "Diretta chiusa",
resumeError: "Errore ripresa diretta",
pauseError: "Errore pausa diretta",
closeError: "Errore chiusura",
closeConfirm: "Chiudere definitivamente la diretta?",
linkUnavailable: "Link non disponibile",
linkCopied: "Link copiato",
copyPrompt: "Copia il link:",
shareRegiaText: "Apri questo link per gestire il punteggio della diretta:",
shareLiveText: "Guarda la diretta su Match Live TV:",
previewWaiting: "Anteprima in attesa del segnale…",
streamEnded: "Diretta terminata",
resumeLabel: "Riprendi diretta",
pauseLabel: "Metti in pausa",
endedBadge: "Terminata",
pausedBadge: "In pausa",
liveBadge: "In onda",
waitingBadge: "In attesa",
subtitlePrefix: "Regia ·"
};
function loadI18n() {
const raw = root.dataset.i18n;
if (!raw) return DEFAULT_I18N;
try {
const parsed = JSON.parse(raw);
const merged = {};
for (const key in DEFAULT_I18N) {
merged[key] = parsed[key] || DEFAULT_I18N[key];
}
return merged;
} catch (err) {
return DEFAULT_I18N;
}
}
const I18N = loadI18n();
function fillTemplate(template, values) {
return template.replace(/%\{(\w+)\}/g, (match, key) =>
Object.prototype.hasOwnProperty.call(values, key) ? values[key] : match
);
}
const cfg = {
token: root.dataset.token,
board: root.dataset.board || "volley",
@@ -88,7 +155,7 @@
function formatPartials(partials) {
if (!partials || !partials.length) return "";
return partials
.map((p) => `Set ${p.set || p["set"]} ${p.home || p["home"]}-${p.away || p["away"]}`)
.map((p) => `${fillTemplate(I18N.setPartialLabelTemplate, { number: p.set || p["set"] })} ${p.home || p["home"]}-${p.away || p["away"]}`)
.join(" · ");
}
@@ -104,24 +171,30 @@
const raw = score.period;
if (typeof raw === "string" && raw.trim()) return raw.trim();
if (typeof raw === "number" && raw > 0) {
return board === "basket" ? `Q${raw}` : `${raw}° tempo`;
return board === "basket"
? fillTemplate(I18N.quarterLabelTemplate, { n: raw })
: fillTemplate(I18N.halfLabelTemplate, { n: raw });
}
const nested = score.data || {};
const dataPeriod = nested.period ?? nested["period"];
const periodNum = parseInt(dataPeriod, 10);
if (periodNum > 0) {
if (nested.overtime || nested["overtime"]) {
return board === "basket" ? `OT${Math.max(periodNum - 4, 1)}` : "Suppl.";
return board === "basket"
? fillTemplate(I18N.overtimeBasketTemplate, { n: Math.max(periodNum - 4, 1) })
: I18N.overtimeOther;
}
return board === "basket" ? `Q${periodNum}` : `${periodNum}° tempo`;
return board === "basket"
? fillTemplate(I18N.quarterLabelTemplate, { n: periodNum })
: fillTemplate(I18N.halfLabelTemplate, { n: periodNum });
}
return "—";
return I18N.unknownPeriod;
}
function updateRegiaSubtitle(periodLabel, board) {
if (!els.subtitle) return;
if (board === "basket" || board === "timed") {
els.subtitle.textContent = `Regia · ${periodLabel}`;
els.subtitle.textContent = `${I18N.subtitlePrefix} ${periodLabel}`;
}
}
@@ -150,17 +223,17 @@
updateRegiaSubtitle(periodLabel, board);
} else if (board === "timer") {
const clockSecs = s.clock_secs ?? s.clockSecs ?? s.data?.clock_secs;
if (els.setsLine) els.setsLine.textContent = "Cronometro";
if (els.setsLine) els.setsLine.textContent = I18N.stopwatch;
if (clockLine) clockLine.textContent = formatClock(clockSecs, true);
} else if (board === "generic") {
if (els.setsLine) els.setsLine.textContent = "Punteggio libero";
if (els.setsLine) els.setsLine.textContent = I18N.freeScore;
if (els.scoreLine) els.scoreLine.textContent = `${cfg.homeName} ${homePts} - ${awayPts} ${cfg.awayName}`;
} else {
if (els.setsLine) els.setsLine.textContent = `Set ${currentSet} · Set vinti ${homeSets}-${awaySets}`;
if (els.setsLine) els.setsLine.textContent = fillTemplate(I18N.setsLineTemplate, { current: currentSet, home: homeSets, away: awaySets });
if (els.scoreLine) els.scoreLine.textContent = `${cfg.homeName} ${homePts} - ${awayPts} ${cfg.awayName}`;
const partialsText = formatPartials(s.set_partials || s.setPartials);
if (els.partialsLine && partialsText) {
els.partialsLine.textContent = `Parziali: ${partialsText}`;
els.partialsLine.textContent = `${I18N.partialsPrefix} ${partialsText}`;
els.partialsLine.hidden = false;
}
}
@@ -170,26 +243,26 @@
if (!els.btnPause) return;
const paused = !!(data.paused || data.status === "paused");
streamPaused = paused;
els.btnPause.textContent = paused ? "Riprendi diretta" : "Metti in pausa";
els.btnPause.textContent = paused ? I18N.resumeLabel : I18N.pauseLabel;
}
function setBadge(data) {
syncPauseButton(data);
if (data.stream_closed) {
els.badge.textContent = "Terminata";
els.badge.textContent = I18N.endedBadge;
els.badge.className = "regia-badge regia-badge--ended";
return;
}
if (data.status === "paused" || data.paused) {
els.badge.textContent = "In pausa";
els.badge.textContent = I18N.pausedBadge;
els.badge.className = "regia-badge regia-badge--wait";
return;
}
if (data.on_air) {
els.badge.textContent = "In onda";
els.badge.textContent = I18N.liveBadge;
els.badge.className = "regia-badge regia-badge--live";
} else {
els.badge.textContent = "In attesa";
els.badge.textContent = I18N.waitingBadge;
els.badge.className = "regia-badge regia-badge--wait";
}
}
@@ -202,7 +275,7 @@
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error || "Errore aggiornamento punteggio");
throw new Error(body.error || I18N.scoreUpdateError);
}
return res.json();
}
@@ -219,24 +292,24 @@
if (action === "advance_period") {
const periodLabel = resolvePeriodLabel(data.score || {}, cfg.board);
toast(periodLabel === "—" ? "Periodo aggiornato" : `Ora in ${periodLabel}`);
toast(periodLabel === I18N.unknownPeriod ? I18N.periodUpdated : fillTemplate(I18N.nowInPeriodTemplate, { period: periodLabel }));
}
if (cfg.board === "volley" || cfg.board === "racket") {
if (data.set_won && action !== "close_set") {
const winner = data.winner === "home" ? cfg.homeName : cfg.awayName;
openModal("Set vinto", `${winner} ha vinto il set. Chiudere il set?`, () => postScore("close_set"));
openModal(I18N.setWonTitle, fillTemplate(I18N.setWonBodyTemplate, { winner }), () => postScore("close_set"));
}
}
if (data.match_won) {
const winner = data.winner === "home" ? cfg.homeName : cfg.awayName;
openModal("Partita vinta", `${winner} ha vinto la partita. Chiudere la diretta?`, () => stopStream());
openModal(I18N.matchWonTitle, fillTemplate(I18N.matchWonBodyTemplate, { winner }), () => stopStream());
}
} catch (e) {
if (prevHome != null && els.homePoints) els.homePoints.textContent = prevHome;
const awayEl = document.getElementById("away-points");
if (prevAway != null && awayEl) awayEl.textContent = prevAway;
toast(e.message || "Errore");
toast(e.message || I18N.genericError);
} finally {
if (trigger) trigger.disabled = false;
}
@@ -265,7 +338,7 @@
setBadge(data);
}
} catch (e) {
toast(e.message || "Errore");
toast(e.message || I18N.genericError);
}
}
});
@@ -276,7 +349,7 @@
btn.addEventListener("click", () => {
const action = btn.dataset.action;
if (action === "close_set") {
openModal("Chiudi set", "Confermi la chiusura del set corrente?", () => postScore("close_set"));
openModal(I18N.closeSetTitle, I18N.closeSetConfirm, () => postScore("close_set"));
} else {
handleAction(action);
}
@@ -304,7 +377,7 @@
}
} else {
destroyPreview();
showPreviewPlaceholder("Anteprima in attesa del segnale…");
showPreviewPlaceholder(I18N.previewWaiting);
}
wasPausedPreview = paused;
}
@@ -367,7 +440,7 @@
function stopPreview() {
destroyPreview();
if (els.preview) els.preview.style.display = "none";
showPreviewPlaceholder("Diretta terminata");
showPreviewPlaceholder(I18N.streamEnded);
}
async function togglePauseStream() {
@@ -375,25 +448,25 @@
const res = await fetch(url, { method: "POST", headers: { Accept: "application/json" } });
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error || (streamPaused ? "Errore ripresa diretta" : "Errore pausa diretta"));
throw new Error(body.error || (streamPaused ? I18N.resumeError : I18N.pauseError));
}
const data = await res.json();
const wasPaused = streamPaused;
applyScorePayload(data);
setBadge(data);
syncPreviewFromStatus(data);
toast(wasPaused ? "Diretta ripresa" : "Diretta in pausa — copertina in onda");
toast(wasPaused ? I18N.resumed : I18N.pausedCover);
}
async function stopStream() {
const ok = confirm("Chiudere definitivamente la diretta?");
const ok = confirm(I18N.closeConfirm);
if (!ok) return null;
const res = await fetch(cfg.stopUrl, { method: "POST", headers: { Accept: "application/json" } });
if (!res.ok) throw new Error("Errore chiusura");
if (!res.ok) throw new Error(I18N.closeError);
const data = await res.json();
cfg.streamClosed = true;
stopPreview();
toast("Diretta chiusa");
toast(I18N.closed);
return data;
}
@@ -402,7 +475,7 @@
async function shareLink(url, title, text) {
if (!url) {
toast("Link non disponibile");
toast(I18N.linkUnavailable);
return;
}
const payload = { title, text, url };
@@ -419,31 +492,23 @@
async function copyLink(url) {
if (!url) {
toast("Link non disponibile");
toast(I18N.linkUnavailable);
return;
}
try {
await navigator.clipboard.writeText(url);
toast("Link copiato");
toast(I18N.linkCopied);
} catch (_) {
prompt("Copia il link:", url);
prompt(I18N.copyPrompt, url);
}
}
document.getElementById("btn-share-regia")?.addEventListener("click", () =>
shareLink(
cfg.regiaShareUrl,
cfg.regiaShareTitle,
"Apri questo link per gestire il punteggio della diretta:"
)
shareLink(cfg.regiaShareUrl, cfg.regiaShareTitle, I18N.shareRegiaText)
);
document.getElementById("btn-copy-regia")?.addEventListener("click", () => copyLink(cfg.regiaShareUrl));
document.getElementById("btn-share-live")?.addEventListener("click", () =>
shareLink(
cfg.liveShareUrl,
cfg.liveShareTitle,
"Guarda la diretta su Match Live TV:"
)
shareLink(cfg.liveShareUrl, cfg.liveShareTitle, I18N.shareLiveText)
);
document.getElementById("btn-copy-live")?.addEventListener("click", () => copyLink(cfg.liveShareUrl));