Translate /report-bug into all 7 languages, require English in the report itself

The bug report form previously shipped as plain Italian only. It now shares
i18n.js with / (same TRANSLATIONS table, new bugReport.* keys in all 7
languages, own language switcher since the page has no navbar to hang one
off), so a non-Italian speaker can read the form and their own report
history in their language.

The description field itself still has to reach the admin panel in English
(operator-facing, untranslated by design), so the page states that
explicitly via a standing banner (bugReport.englishNotice) — translated
into every language rather than left in English, so the instruction to
write in English is itself understandable to whoever's reading it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 16:03:38 +02:00
co-authored by Claude Sonnet 5
parent ee4e845c89
commit a384b08044
4 changed files with 193 additions and 24 deletions
+42 -21
View File
@@ -1,37 +1,53 @@
<!DOCTYPE html>
<html lang="it">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Segnala un bug — PLM Lottery</title>
<title data-i18n="bugReport.pageTitle">Report a bug</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<div class="app-shell">
<h1>Segnala un bug</h1>
<p>Hai trovato un problema? Descrivilo qui sotto: la segnalazione arriva direttamente al pannello di amministrazione.</p>
<div class="lang-bar">
<select id="lang-switcher" class="lang-switcher" onchange="setLanguage(this.value)" aria-label="Language">
<option value="en">English</option>
<option value="it">Italiano</option>
<option value="es">Español</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
<option value="ru">Русский</option>
<option value="zh">中文</option>
</select>
</div>
<h1 data-i18n="bugReport.heading">Report a bug</h1>
<p data-i18n="bugReport.intro">Found a problem? Describe it below — your report goes straight to the admin panel.</p>
<p class="english-notice" data-i18n="bugReport.englishNotice">
Please write your bug report in English, regardless of the language you're browsing in — this helps us handle it faster.
</p>
<div class="card" id="report-form">
<label for="bug-description">Cosa è successo?</label>
<textarea id="bug-description" rows="6" maxlength="5000" placeholder="Descrivi il bug: cosa stavi facendo, cosa ti aspettavi e cosa è successo invece."></textarea>
<label for="bug-description" data-i18n="bugReport.descriptionLabel">What happened?</label>
<textarea id="bug-description" rows="6" maxlength="5000" data-i18n-placeholder="bugReport.descriptionPlaceholder"></textarea>
<label for="bug-contact">Contatto (opzionale)</label>
<input id="bug-contact" type="text" maxlength="256" placeholder="Email o altro recapito, se vuoi essere ricontattato">
<label for="bug-contact" data-i18n="bugReport.contactLabel">Contact (optional)</label>
<input id="bug-contact" type="text" maxlength="256" data-i18n-placeholder="bugReport.contactPlaceholder">
<button onclick="submitBugReport()" id="bug-submit-btn">Invia segnalazione</button>
<button onclick="submitBugReport()" id="bug-submit-btn" data-i18n="bugReport.submitBtn">Send report</button>
</div>
<div class="card hidden" id="my-reports-card">
<h2>Le tue segnalazioni</h2>
<p class="hint">Solo le segnalazioni inviate da questo account, con lo stato aggiornato dall'amministrazione.</p>
<h2 data-i18n="bugReport.myReportsTitle">Your reports</h2>
<p class="hint" data-i18n="bugReport.myReportsHint">Only reports sent from this account, with the status set by the admin team.</p>
<div id="my-reports-list"></div>
</div>
<p><a class="link" href="/">&larr; Torna alla home</a></p>
<p><a class="link" href="/" data-i18n="bugReport.backLink">&larr; Back to home</a></p>
</div>
<div id="toast-container" aria-live="polite"></div>
<script src="/i18n.js"></script>
<script>
function toast(message, type) {
const container = document.getElementById('toast-container');
@@ -46,14 +62,12 @@
return String(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
}
const BUG_REPORT_STATUS_LABELS = { open: 'Da leggere', read: 'Letta', resolved: 'Risolta' };
async function submitBugReport() {
const btn = document.getElementById('bug-submit-btn');
const description = document.getElementById('bug-description').value.trim();
const contact = document.getElementById('bug-contact').value.trim();
if (!description) {
toast('Descrivi il bug prima di inviare.', 'error');
toast(t('bugReport.blankError'), 'error');
return;
}
@@ -63,7 +77,7 @@
btn.disabled = true;
const original = btn.textContent;
btn.textContent = 'Invio…';
btn.textContent = t('bugReport.submitting');
try {
const res = await fetch('/bug-reports', {
method: 'POST',
@@ -74,13 +88,14 @@
if (!res.ok) throw new Error(data.detail?.message || data.detail || res.statusText);
document.getElementById('bug-description').value = '';
document.getElementById('bug-contact').value = '';
toast('Grazie! Segnalazione inviata.', 'success');
toast(t('bugReport.successToast'), 'success');
loadMyBugReports();
} catch (e) {
toast('Errore nell\'invio: ' + e.message, 'error');
toast(t('bugReport.errorPrefix') + e.message, 'error');
} finally {
btn.disabled = false;
btn.textContent = original;
applyStaticTranslations(btn);
}
}
@@ -102,16 +117,22 @@
const list = document.getElementById('my-reports-list');
list.innerHTML = reports.map((r) => `
<div class="my-report-row">
<span class="badge bug-status-${escapeHtml(r.status)}">${escapeHtml(BUG_REPORT_STATUS_LABELS[r.status] || r.status)}</span>
<span class="badge bug-status-${escapeHtml(r.status)}">${escapeHtml(t('bugReport.status' + r.status.charAt(0).toUpperCase() + r.status.slice(1)))}</span>
<span class="my-report-desc">${escapeHtml(r.description)}</span>
<span class="my-report-date">${new Date(r.created_at).toLocaleString('it-IT')}</span>
<span class="my-report-date">${new Date(r.created_at).toLocaleString(currentDateLocale())}</span>
</div>
`).join('') || '<p class="hint">Non hai ancora inviato segnalazioni.</p>';
`).join('') || `<p class="hint">${escapeHtml(t('bugReport.myReportsEmpty'))}</p>`;
} catch (e) {
card.classList.add('hidden');
}
}
// Re-renders server-rendered content (my reports list) on a language switch,
// the same split app.js uses between data-i18n (static markup) and t() (data).
function onLanguageChange() {
loadMyBugReports();
}
loadMyBugReports();
</script>
</body>