Files
GameList/wwwroot/js/i18n.js

317 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const translations = {
en: {
"lang.label": "Language",
"lang.en": "English",
"lang.de": "Deutsch",
"auth.loginTab": "Log in",
"auth.registerTab": "Register",
"auth.username": "Username",
"auth.password": "Password",
"auth.displayName": "Display name (shows to group)",
"auth.adminKey": "Admin key (optional)",
"auth.loginSubmit": "Log in",
"auth.registerSubmit": "Create account",
"auth.loginHeading": "Log in",
"auth.registerHeading": "Create account",
"auth.switchToRegister": "Need an account? Register",
"auth.switchToLogin": "Have an account? Log in",
"auth.logout": "Logout",
"auth.welcome": "Welcome, {name}!",
"auth.defaultName": "Player",
"auth.loading": "Loading…",
"auth.needCredentials": "Username and password required",
"auth.invalidCredentials": "Invalid username or password",
"phase.suggest": "Suggest",
"phase.reveal": "Reveal",
"phase.vote": "Vote",
"phase.results": "Results",
"phase.loading": "Loading…",
"counts.format": "Players: {players} • Suggestions: {suggestions} • Votes: {votes}",
"nav.prev": "Back",
"nav.next": "Next",
"nav.waitingForResults": "Waiting…",
"nav.freezeTitle": "Ready to reveal?",
"nav.freezeHint": "Moving forward will freeze your suggestions. Titles become locked; only extra details stay editable.",
"nav.freezeModalTitle": "Freeze suggestions?",
"nav.freezeModalBody": "Once you leave Suggest, your games are locked: titles cannot be changed or deleted. Only optional details (description, links, players, artwork) remain editable. Continue?",
"nav.voteHint": "Cast votes for every game to unlock results.",
"suggest.title": "Suggest games (up to 5)",
"suggest.new": "Add new suggestion",
"suggest.addButton": "Suggest a game",
"suggest.hint": "Only you can see your suggestions until voting starts.",
"form.gameName": "Game name *",
"form.genre": "Genre",
"form.description": "Description",
"form.players": "Players",
"form.min": "Min",
"form.max": "Max",
"form.screenshot": "Screenshot URL",
"form.youtube": "YouTube URL",
"form.gameUrl": "Game website URL",
"form.submit": "Submit",
"form.placeholder.description": "Short description",
"form.placeholder.gameName": "Game name *",
"form.placeholder.genre": "Genre",
"form.placeholder.screenshot": "Screenshot URL",
"form.placeholder.youtube": "YouTube URL",
"form.placeholder.gameUrl": "Game website URL",
"section.mySuggestions": "Your suggestions",
"section.allSuggestions": "All suggestions",
"section.allSuggestions.count": "All {count} suggestions",
"section.vote": "Vote 0-10",
"section.vote.count": "Vote for all {count} games",
"section.results": "Results",
"card.edit": "Edit",
"card.delete": "Delete",
"card.players": "Players: {min}{max}",
"card.site": "Site ↗",
"card.youtube": "YouTube ↗",
"card.openScreenshot": "Open screenshot",
"vote.saved": "Saved vote",
"vote.missing": "Missing",
"vote.missingWarn": "You havent voted yet. Slide to set a score.",
"vote.finalize": "Finalize votes",
"vote.unfinalize": "Edit votes",
"vote.finalHint": "Finalize when youre done. You can unfinalize to change scores.",
"results.rank": "Rank",
"results.game": "Game",
"results.author": "Author",
"results.votesList": "All votes",
"results.myVote": "Your vote",
"results.links": "Links",
"results.link.site": "Site ↗",
"results.link.youtube": "YouTube ↗",
"admin.title": "Admin",
"admin.tools": "Admin tools",
"admin.resultsOpenToggle": "Allow results phase",
"admin.resultsLocked": "Results locked by admin",
"admin.resultsUpdated": "Results availability updated",
"admin.reset": "Reset (keep players)",
"admin.factoryReset": "Factory reset",
"admin.resetDone": "Reset complete",
"admin.factoryResetDone": "Factory reset complete",
"toast.unexpected": "Unexpected error",
"toast.registered": "Registered",
"toast.loggedIn": "Logged in",
"toast.suggestionAdded": "Suggestion added",
"toast.suggestionDeleted": "Suggestion deleted",
"toast.savedChanges": "Saved changes",
"toast.nameRequired": "Name required",
"toast.displayNameRequired": "Display name is required",
"toast.invalidImageUrl": "Screenshot URL must be http(s) and end with an image file.",
"modal.editTitle": "Edit game",
"modal.addTitle": "Suggest a game",
"modal.confirmDeleteTitle": "Are you sure?",
"modal.confirmDelete": "Confirm delete",
"modal.save": "Save changes",
"modal.cancel": "Cancel",
"modal.close": "Close",
"lightbox.close": "Close",
},
de: {
"lang.label": "Sprache",
"lang.en": "Englisch",
"lang.de": "Deutsch",
"auth.loginTab": "Anmelden",
"auth.registerTab": "Registrieren",
"auth.username": "Benutzername",
"auth.password": "Passwort",
"auth.displayName": "Anzeigename (für die Gruppe sichtbar)",
"auth.adminKey": "Admin-Schlüssel (optional)",
"auth.loginSubmit": "Anmelden",
"auth.registerSubmit": "Konto erstellen",
"auth.loginHeading": "Anmelden",
"auth.registerHeading": "Konto erstellen",
"auth.switchToRegister": "Noch kein Konto? Registrieren",
"auth.switchToLogin": "Schon ein Konto? Anmelden",
"auth.logout": "Abmelden",
"auth.welcome": "Willkommen, {name}!",
"auth.defaultName": "Spieler",
"auth.loading": "Lädt…",
"auth.needCredentials": "Benutzername und Passwort erforderlich",
"auth.invalidCredentials": "Ungültiger Benutzername oder Passwort",
"phase.suggest": "Vorschlagen",
"phase.reveal": "Enthüllen",
"phase.vote": "Bewerten",
"phase.results": "Ergebnisse",
"phase.loading": "Lädt…",
"counts.format": "Spieler: {players} • Vorschläge: {suggestions} • Stimmen: {votes}",
"nav.prev": "Zurück",
"nav.next": "Weiter",
"nav.waitingForResults": "Warten…",
"nav.freezeTitle": "Bereit zum Aufdecken?",
"nav.freezeHint": "Beim Weitergehen werden deine Vorschläge eingefroren. Titel bleiben gesperrt; nur Zusatzinfos bleiben bearbeitbar.",
"nav.freezeModalTitle": "Vorschläge einfrieren?",
"nav.freezeModalBody": "Sobald du die Vorschlagsphase verlässt, sind deine Spiele gesperrt: Titel können nicht mehr geändert oder gelöscht werden. Nur optionale Angaben (Beschreibung, Links, Spielerzahlen, Bilder) bleiben bearbeitbar. Fortfahren?",
"nav.voteHint": "Bewerte alle Spiele, um die Ergebnisse freizuschalten.",
"suggest.title": "Schlage Spiele vor (bis zu 5)",
"suggest.new": "Neuen Vorschlag hinzufügen",
"suggest.addButton": "Spiel vorschlagen",
"suggest.hint": "Nur du siehst deine Vorschläge bis zum Start der Abstimmung.",
"form.gameName": "Spielname *",
"form.genre": "Genre",
"form.description": "Beschreibung",
"form.players": "Spieler",
"form.min": "Min",
"form.max": "Max",
"form.screenshot": "Screenshot-URL",
"form.youtube": "YouTube-URL",
"form.gameUrl": "Spiel-Webseite",
"form.submit": "Absenden",
"form.placeholder.description": "Kurze Beschreibung",
"form.placeholder.gameName": "Spielname *",
"form.placeholder.genre": "Genre",
"form.placeholder.screenshot": "Screenshot-URL",
"form.placeholder.youtube": "YouTube-URL",
"form.placeholder.gameUrl": "Spiel-Webseite",
"section.mySuggestions": "Deine Vorschläge",
"section.allSuggestions": "Alle Vorschläge",
"section.allSuggestions.count": "Alle {count} Vorschläge",
"section.vote": "Bewerten 0-10",
"section.vote.count": "Bewerte alle {count} Spiele",
"section.results": "Ergebnisse",
"card.edit": "Bearbeiten",
"card.delete": "Löschen",
"card.players": "Spieler: {min}{max}",
"card.site": "Webseite ↗",
"card.youtube": "YouTube ↗",
"card.openScreenshot": "Screenshot öffnen",
"vote.saved": "Stimme gespeichert",
"vote.missing": "Fehlt",
"vote.missingWarn": "Du hast hier noch nicht abgestimmt. Schiebe den Regler.",
"vote.finalize": "Abstimmung abschließen",
"vote.unfinalize": "Abstimmung bearbeiten",
"vote.finalHint": "Schließe ab, wenn du fertig bist. Zum Ändern wieder öffnen.",
"results.rank": "Rang",
"results.game": "Spiel",
"results.author": "Autor",
"results.votesList": "Alle Stimmen",
"results.myVote": "Deine Stimme",
"results.links": "Links",
"results.link.site": "Webseite ↗",
"results.link.youtube": "YouTube ↗",
"admin.title": "Admin",
"admin.tools": "Admin-Werkzeuge",
"admin.resultsOpenToggle": "Ergebnisse freigeben",
"admin.resultsLocked": "Ergebnisse vom Admin gesperrt",
"admin.resultsUpdated": "Ergebnisfreigabe aktualisiert",
"admin.reset": "Zurücksetzen (Spieler behalten)",
"admin.factoryReset": "Werkseinstellung",
"admin.resetDone": "Zurücksetzen abgeschlossen",
"admin.factoryResetDone": "Werkseinstellung abgeschlossen",
"toast.unexpected": "Unerwarteter Fehler",
"toast.registered": "Registriert",
"toast.loggedIn": "Angemeldet",
"toast.suggestionAdded": "Vorschlag hinzugefügt",
"toast.suggestionDeleted": "Vorschlag gelöscht",
"toast.savedChanges": "Änderungen gespeichert",
"toast.nameRequired": "Name erforderlich",
"toast.displayNameRequired": "Anzeigename ist erforderlich",
"toast.invalidImageUrl": "Screenshot-URL muss mit http(s) beginnen und auf eine Bilddatei enden.",
"modal.editTitle": "Spiel bearbeiten",
"modal.addTitle": "Spiel vorschlagen",
"modal.confirmDeleteTitle": "Bist du sicher?",
"modal.confirmDelete": "Löschen bestätigen",
"modal.save": "Änderungen speichern",
"modal.cancel": "Abbrechen",
"modal.close": "Schließen",
"lightbox.close": "Schließen",
}
};
const storageKey = "app_lang";
const defaultLang = "en";
let currentLang = defaultLang;
const listeners = [];
function interpolate(template, params = {}) {
return template.replace(/\{(\w+)\}/g, (_, key) => (params[key] ?? `{${key}}`));
}
function t(key, params) {
const fallback = translations[defaultLang][key] ?? key;
const phrase = translations[currentLang]?.[key] ?? fallback;
return interpolate(phrase, params);
}
function detectLanguage() {
const stored = localStorage.getItem(storageKey);
if (stored && translations[stored]) return stored;
const nav = navigator.language?.slice(0, 2);
if (nav && translations[nav]) return nav;
return defaultLang;
}
function applyTranslations(root = document) {
root.querySelectorAll("[data-i18n]").forEach((el) => {
const key = el.dataset.i18n;
const attrs = (el.dataset.i18nAttr || "")
.split(",")
.map((a) => a.trim())
.filter(Boolean);
const text = t(key);
if (attrs.length === 0) {
el.textContent = text;
} else {
attrs.forEach((attr) => el.setAttribute(attr, text));
}
});
}
function notify() {
listeners.forEach((fn) => fn(currentLang));
}
function setLanguage(lang) {
if (!translations[lang]) lang = defaultLang;
currentLang = lang;
localStorage.setItem(storageKey, lang);
document.documentElement.lang = lang;
applyTranslations();
notify();
}
function getLanguage() {
return currentLang;
}
function initI18n() {
currentLang = detectLanguage();
document.documentElement.lang = currentLang;
applyTranslations();
notify();
return currentLang;
}
function onLanguageChange(fn) {
listeners.push(fn);
}
export { t, setLanguage, getLanguage, initI18n, applyTranslations, onLanguageChange, translations };