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:
+108
-43
@@ -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));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user