import { LitElement, html } from "lit"; import { customElement, state } from "lit/decorators.js"; // Import language files import bgTranslations from "../../resources/lang/bg.json"; import deTranslations from "../../resources/lang/de.json"; import enTranslations from "../../resources/lang/en.json"; import esTranslations from "../../resources/lang/es.json"; import frTranslations from "../../resources/lang/fr.json"; import jaTranslations from "../../resources/lang/ja.json"; import nlTranslations from "../../resources/lang/nl.json"; const translations = { en: enTranslations, bg: bgTranslations, ja: jaTranslations, fr: frTranslations, nl: nlTranslations, de: deTranslations, es: esTranslations, }; type Translation = Partial<(typeof translations)[keyof typeof translations]>; @customElement("lang-selector") export class LangSelector extends LitElement { @state() public translations: Translation = {}; @state() private defaultTranslations = {}; @state() private currentLang: string = "en"; createRenderRoot() { return this; } connectedCallback() { super.connectedCallback(); this.initializeLanguage(); } private async initializeLanguage() { const locale = new Intl.Locale(navigator.language); const defaultLang = locale.language; const userLang = localStorage.getItem("lang") || defaultLang; this.defaultTranslations = await this.loadLanguage("en"); this.translations = await this.loadLanguage(userLang); this.currentLang = userLang; this.applyTranslation(); } private async loadLanguage(lang: string): Promise { try { const translation = translations[lang as keyof typeof translations]; if (!translation) throw new Error(`Language file not found: ${lang}`); return translation; } catch (error) { console.error("🚨 Translation load error:", error); return {}; } } private applyTranslation() { const components = [ "single-player-modal", "host-lobby-modal", "join-private-lobby-modal", "emoji-table", "leader-board", "build-menu", "win-modal", "game-starting-modal", "top-bar", "player-panel", "help-modal", "username-input", "public-lobby", ]; const main = this.translations.main; if (main && "title" in main) { document.title = main.title; } document.querySelectorAll("[data-i18n]").forEach((element) => { const key = element.getAttribute("data-i18n"); const text = this.translateText(key); if (text) { element.innerHTML = text; } else { console.warn(`Missing translation key: ${key}`); } }); components.forEach((tagName) => { const el = document.querySelector(tagName) as LitElement; if (el && typeof el.requestUpdate === "function") { el.requestUpdate(); } else { console.warn( `requestUpdate() not available on <${tagName}> or element not found.`, ); } }); } public translateText( key: string, params: Record = {}, ): string { const keys = key.split("."); let text = findTranslation(keys, this.translations); if (!text && this.defaultTranslations) { text = findTranslation(keys, this.defaultTranslations); } if (text == null || typeof text !== "string") { return null; } for (const [param, value] of Object.entries(params)) { text = text.replace(`{${param}}`, String(value)); } return text; } private async changeLanguage(lang: string) { localStorage.setItem("lang", lang); this.translations = await this.loadLanguage(lang); this.currentLang = lang; this.applyTranslation(); } render() { return html` `; } } function findTranslation( keys: string[], translations: Translation, ): string | null { let ptr: unknown = translations; for (const k of keys) { ptr = ptr?.[k]; if (!ptr) break; } if (ptr && typeof ptr === "string") { return ptr; } else { return null; } }