Files
GameList/wwwroot/js/i18n.js

475 lines
24 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. Game names become locked; only extra details stay editable.",
"nav.freezeModalTitle": "Freeze suggestions?",
"nav.freezeModalBody": "Once you leave Suggest, your games are locked: game names 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.",
"nav.voteFinalized": "✅ You finalized your votes. Sit back and relax while the other players finalize their votes.",
"suggest.title": "Suggest games (up to 5)",
"suggest.new": "Add new suggestion",
"suggest.addButton": "Suggest a game",
"suggest.jokerAddButton": "🃏 Joker: add another 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",
"card.linked": "Votes linked",
"card.linkedWith": "Linked with: {names}",
"vote.saved": "Saved vote",
"vote.missing": "Missing",
"vote.missingWarn": "You havent voted yet.",
"vote.missingFinalWarn": "You didn't vote for this game.",
"vote.missingFooter": "At least one game is missing a score. Check before finalizing.",
"vote.finalize": "Finalize votes",
"vote.unfinalize": "Edit votes",
"vote.finalHint": "Finalize when youre done. You can unfinalize to change scores.",
"vote.waitAdmin": "Waiting for admin to unlock results.",
"results.rank": "Rank",
"results.game": "Game",
"results.author": "Author",
"results.average": "Ø",
"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",
"admin.readyForResults": "Ready for results",
"admin.waitingForPlayers": "Waiting for players: {names}",
"admin.playerName": "Name",
"admin.playerUsername": "Username",
"admin.playerStatus": "Status",
"admin.playerGames": "Games",
"admin.playerJoker": "Joker",
"admin.playerDelete": "Delete",
"admin.grantJokerChip": "Grant",
"admin.statusSuggesting": "Suggesting",
"admin.statusVoting": "Voting",
"admin.statusFinished": "Finished",
"admin.deleteTitle": "Delete account?",
"admin.deleteBody": "Delete player \"{name}\" and all their games and votes? This cannot be undone.",
"admin.deleteConfirm": "Delete",
"admin.deleteDone": "Player deleted",
"admin.jokerGranted": "Joker granted",
"admin.linkTitle": "Link games",
"admin.linkSource": "Game to link",
"admin.linkTarget": "Link to (parent)",
"admin.linkAction": "Link & clear votes",
"admin.linkSourcePlaceholder": "Select source",
"admin.linkTargetPlaceholder": "Select target",
"admin.linkValidation": "Choose two different games to link.",
"admin.linkDone": "Games linked. Votes cleared.",
"admin.unlinkTitle": "Remove links?",
"admin.unlinkBody": "Remove all links involving \"{name}\"? This clears votes and unfinalizes voters in this group: {peers}.",
"admin.unlinkConfirm": "Remove links",
"admin.unlinkDone": "Links removed. Votes cleared.",
"admin.unlinkUnknownPeers": "linked games",
"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",
"help.label": "Help",
"help.title": "FAQ & tips",
"help.intro": "Expand a section for common player questions and edge cases.",
"help.cat.gettingStarted": "Getting started",
"help.cat.suggest": "Suggest phase",
"help.cat.vote": "Vote phase",
"help.cat.results": "Results phase",
"help.q.join": "How do I join?",
"help.a.join": "Register with a username, password, and display name. Then log in with the same credentials.",
"help.q.adminKey": "What is the admin key?",
"help.a.adminKey": "If your host shares the admin key, enter it during registration to unlock admin controls.",
"help.q.displayName": "Why do I need a display name?",
"help.a.displayName": "Suggestions and votes show who submitted them. You need a display name before you can add games or scores.",
"help.q.limit": "How many games can I add?",
"help.a.limit": "Up to 5 suggestions per player in Suggest. Each joker granted in Vote lets you add one more; every joker is consumed when you use it.",
"help.q.editNames": "Can I edit or delete suggestions later?",
"help.a.editNames": "Game names lock after leaving Suggest. Optional fields stay editable; admins can edit or delete any time.",
"help.q.mediaRules": "Any rules for links and images?",
"help.a.mediaRules": "Use http(s) links. Screenshots must end with an image file (png, jpg, gif, webp, avif). Invalid links are rejected.",
"help.q.cantAdd": "Why was my suggestion rejected?",
"help.a.cantAdd": "You can only add games in Suggest or with a joker in Vote. Provide a display name, stay within the 5 + joker limit, use http(s) links to reachable media, and keep player counts between 1 and 32 with min \u2264 max.",
"help.q.howVote": "How do votes work?",
"help.a.howVote": "Move the 010 slider for each game. Scores save instantly; you can adjust until you finalize.",
"help.q.finalize": "What does finalize do?",
"help.a.finalize": "Finalize locks your votes while you're in Vote. You must score every game first. Toggle it off in the same phase to make changes.",
"help.q.voteBlocked": "Why can't I vote?",
"help.a.voteBlocked": "Voting only works in the Vote phase when you're logged in and have a display name. If you skipped the display name, re-register; admins cannot add one for you.",
"help.q.jokerAfterFreeze": "I finalized but thought of a great game. Can I still add it?",
"help.a.jokerAfterFreeze": "Ask an admin for a joker during Vote. It gives you one extra slot, consumes the joker, and unfinalizes everyone so the new game can be scored.",
"help.q.linkedVotes": "What are linked games?",
"help.a.linkedVotes": "Admins can link duplicates during Vote. Linked games share scores. Linking or unlinking clears votes for that group and reopens affected players to rescore.",
"help.q.newGameAfterFinal": "A new game appeared after I finalized. What now?",
"help.a.newGameAfterFinal": "New games (from jokers) or link changes remove finalization. Check your list and score every game again before finalizing.",
"help.q.scoreRange": "What scores are allowed?",
"help.a.scoreRange": "Scores must be whole numbers from 0 to 10. Anything outside that range is rejected.",
"help.q.resultsLocked": "Why can't I see results?",
"help.a.resultsLocked": "Results stay hidden until an admin opens them. You move to Results automatically when unlocked.",
"help.q.resultsContent": "What do results show?",
"help.a.resultsContent": "A leaderboard with averages, vote counts, your own score, and any links or media for each game.",
"help.q.editInResults": "Can I edit games or votes in Results?",
"help.a.editInResults": "No. Suggestions and votes are locked in Results. Changes require moving back to Vote (for example if an admin closes results or resets).",
},
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. Spielnamen werden gesperrt; nur Zusatzinfos bleiben bearbeitbar.",
"nav.freezeModalTitle": "Vorschläge einfrieren?",
"nav.freezeModalBody": "Sobald du die Vorschlagsphase verlässt, sind deine Spiele gesperrt: Die Namen von deinen Spielen 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.",
"nav.voteFinalized": "✅ Du hast deine Abstimmung abgeschlossen. Lehn dich zurück, bis die anderen fertig sind.",
"suggest.title": "Schlage Spiele vor (bis zu 5)",
"suggest.new": "Neuen Vorschlag hinzufügen",
"suggest.addButton": "Spiel vorschlagen",
"suggest.jokerAddButton": "🃏 Joker: Weiteres Spiel hinzufügen",
"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",
"card.linked": "Verknüpfte Stimmen",
"card.linkedWith": "Verknüpft mit: {names}",
"vote.saved": "Stimme gespeichert",
"vote.missing": "Fehlt",
"vote.missingWarn": "Du hast hier noch nicht abgestimmt.",
"vote.missingFinalWarn": "Du hast für dieses Spiel nicht abgestimmt.",
"vote.missingFooter": "Für mindestens einen Spiel fehlt noch eine Wertung. Prüfe vor dem Abschließen.",
"vote.finalize": "Abstimmung abschließen",
"vote.unfinalize": "Abstimmung bearbeiten",
"vote.finalHint": "Schließe ab, wenn du fertig bist. Zum Ändern wieder öffnen.",
"vote.waitAdmin": "Warten, bis der Admin die Ergebnisse freigibt.",
"results.rank": "Rang",
"results.game": "Spiel",
"results.author": "Autor",
"results.average": "Ø",
"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",
"admin.readyForResults": "Bereit für Ergebnisse",
"admin.waitingForPlayers": "Warten auf: {names}",
"admin.playerName": "Name",
"admin.playerUsername": "Benutzername",
"admin.playerStatus": "Status",
"admin.playerGames": "Spiele",
"admin.playerJoker": "Joker",
"admin.playerDelete": "Löschen",
"admin.grantJokerChip": "Joker",
"admin.statusSuggesting": "Vorschlagen",
"admin.statusVoting": "Bewerten",
"admin.statusFinished": "Fertig",
"admin.deleteTitle": "Konto löschen?",
"admin.deleteBody": "Spieler \"{name}\" samt Spielen und Stimmen löschen? Dies kann nicht rückgängig gemacht werden.",
"admin.deleteConfirm": "Löschen",
"admin.deleteDone": "Spieler gelöscht",
"admin.jokerGranted": "Joker vergeben",
"admin.linkTitle": "Spiele verknüpfen",
"admin.linkSource": "Spiel verknüpfen",
"admin.linkTarget": "Verknüpfen mit",
"admin.linkAction": "Verknüpfen & Stimmen löschen",
"admin.linkSourcePlaceholder": "Quelle wählen",
"admin.linkTargetPlaceholder": "Ziel wählen",
"admin.linkValidation": "Wähle zwei verschiedene Spiele aus.",
"admin.linkDone": "Spiele verknüpft. Stimmen gelöscht.",
"admin.unlinkTitle": "Links entfernen?",
"admin.unlinkBody": "Alle Links zu \"{name}\" entfernen? Dadurch werden Stimmen gelöscht und Finalisierungen aufgehoben für: {peers}.",
"admin.unlinkConfirm": "Links entfernen",
"admin.unlinkDone": "Links entfernt. Stimmen gelöscht.",
"admin.unlinkUnknownPeers": "verknüpfte Spiele",
"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",
"help.label": "Hilfe",
"help.title": "FAQ & Tipps",
"help.intro": "Klappe einen Abschnitt auf, um häufige Spielerfragen und Sonderfälle zu sehen.",
"help.cat.gettingStarted": "Erste Schritte",
"help.cat.suggest": "Phase Vorschlagen",
"help.cat.vote": "Phase Bewerten",
"help.cat.results": "Phase Ergebnisse",
"help.q.join": "Wie kann ich mitmachen?",
"help.a.join": "Registriere dich mit Benutzername, Passwort und Anzeigenamen. Danach mit denselben Daten anmelden.",
"help.q.adminKey": "Was ist der Admin-Schlüssel?",
"help.a.adminKey": "Wenn der Host dir den Admin-Schlüssel gibt, trage ihn bei der Registrierung ein, um Admin-Rechte zu erhalten.",
"help.q.displayName": "Warum brauche ich einen Anzeigenamen?",
"help.a.displayName": "Vorschläge und Stimmen zeigen, von wem sie stammen. Ohne Anzeigenamen kannst du keine Spiele oder Wertungen hinzufügen.",
"help.q.limit": "Wie viele Spiele darf ich hinzufügen?",
"help.a.limit": "Bis zu 5 Vorschläge pro Spieler in Vorschlagen. Jeder Joker in der Abstimmungsphase ermöglicht einen weiteren; der Joker wird beim Nutzen verbraucht.",
"help.q.editNames": "Kann ich Vorschläge später ändern oder löschen?",
"help.a.editNames": "Spieltitel sind nach Verlassen der Vorschlagsphase gesperrt. Optionale Felder bleiben bearbeitbar; Admins können jederzeit ändern oder löschen.",
"help.q.mediaRules": "Gibt es Regeln für Links und Bilder?",
"help.a.mediaRules": "Nutze http(s)-Links. Screenshots müssen auf eine Bilddatei enden (png, jpg, gif, webp, avif). Ungültige Links werden abgelehnt.",
"help.q.cantAdd": "Warum wurde mein Vorschlag abgelehnt?",
"help.a.cantAdd": "Du kannst nur in Vorschlagen oder mit einem Joker in Abstimmen hinzufügen. Gib einen Anzeigenamen an, bleib innerhalb des 5-Plus-Joker-Limits, nutze erreichbare http(s)-Links und halte Spielerzahlen zwischen 1 und 32 mit Min \u2264 Max.",
"help.q.howVote": "Wie funktioniert die Abstimmung?",
"help.a.howVote": "Bewege den 010 Schieberegler für jedes Spiel. Stimmen speichern sofort; du kannst ändern, bis du abschließt.",
"help.q.finalize": "Was bedeutet Abschließen?",
"help.a.finalize": "Abschließen sperrt deine Stimmen in der Phase Abstimmen. Alle Spiele müssen bewertet sein. Schalte es in derselben Phase wieder aus, um Änderungen zu machen.",
"help.q.voteBlocked": "Warum kann ich nicht abstimmen?",
"help.a.voteBlocked": "Abstimmen geht nur in der Phase Abstimmen, wenn du eingeloggt bist und einen Anzeigenamen hast. Wenn du keinen angegeben hast, registriere dich neu; Admins können ihn nicht nachtragen.",
"help.q.jokerAfterFreeze": "Ich habe abgeschlossen, aber mir fällt ein tolles Spiel ein. Kann ich es noch hinzufügen?",
"help.a.jokerAfterFreeze": "Bitte in der Abstimmungsphase einen Admin um einen Joker. Er gibt dir einen zusätzlichen Slot, verbraucht den Joker und öffnet alle Finalisierungen, damit das neue Spiel bewertet werden kann.",
"help.q.linkedVotes": "Was sind verknüpfte Spiele?",
"help.a.linkedVotes": "Admins können in der Abstimmungsphase Duplikate verknüpfen. Verknüpfte Spiele teilen Stimmen. Verknüpfen oder Trennen löscht die Stimmen der Gruppe und öffnet betroffene Spieler erneut.",
"help.q.newGameAfterFinal": "Nach dem Abschließen ist ein neues Spiel aufgetaucht. Was nun?",
"help.a.newGameAfterFinal": "Neue Spiele (durch Joker) oder Link-Änderungen heben Finalisierungen auf. Prüfe deine Liste und bewerte alles erneut, bevor du wieder abschließt.",
"help.q.scoreRange": "Welche Wertungen sind erlaubt?",
"help.a.scoreRange": "Nur ganze Zahlen von 0 bis 10 sind gültig. Alles andere wird abgelehnt.",
"help.q.resultsLocked": "Warum sehe ich keine Ergebnisse?",
"help.a.resultsLocked": "Ergebnisse bleiben verborgen, bis ein Admin sie freigibt. Danach wechselst du automatisch zur Ergebnisphase.",
"help.q.resultsContent": "Was zeigen die Ergebnisse?",
"help.a.resultsContent": "Eine Rangliste mit Durchschnitt, Stimmenanzahl, deiner eigenen Stimme sowie Links und Medien pro Spiel.",
"help.q.editInResults": "Kann ich in den Ergebnissen noch etwas ändern?",
"help.a.editInResults": "Nein. Vorschläge und Stimmen sind in den Ergebnissen gesperrt. Änderungen erfordern eine Rückkehr zur Abstimmungsphase (z. B. wenn ein Admin die Ergebnisse schließt oder zurücksetzt).",
}
};
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 };