import { LitElement, html } from "lit"; import { customElement, state } from "lit/decorators.js"; import "./LanguageModal"; import ar from "../../resources/lang/ar.json"; import bg from "../../resources/lang/bg.json"; import bn from "../../resources/lang/bn.json"; import cs from "../../resources/lang/cs.json"; import da from "../../resources/lang/da.json"; import de from "../../resources/lang/de.json"; import el from "../../resources/lang/el.json"; import en from "../../resources/lang/en.json"; import eo from "../../resources/lang/eo.json"; import es from "../../resources/lang/es.json"; import fa from "../../resources/lang/fa.json"; import fi from "../../resources/lang/fi.json"; import fr from "../../resources/lang/fr.json"; import gl from "../../resources/lang/gl.json"; import he from "../../resources/lang/he.json"; import hi from "../../resources/lang/hi.json"; import hu from "../../resources/lang/hu.json"; import it from "../../resources/lang/it.json"; import ja from "../../resources/lang/ja.json"; import ko from "../../resources/lang/ko.json"; import mk from "../../resources/lang/mk.json"; import nl from "../../resources/lang/nl.json"; import pl from "../../resources/lang/pl.json"; import pt_BR from "../../resources/lang/pt-BR.json"; import pt_PT from "../../resources/lang/pt-PT.json"; import ru from "../../resources/lang/ru.json"; import sh from "../../resources/lang/sh.json"; import sk from "../../resources/lang/sk.json"; import sl from "../../resources/lang/sl.json"; import sv_SE from "../../resources/lang/sv-SE.json"; import tp from "../../resources/lang/tp.json"; import tr from "../../resources/lang/tr.json"; import uk from "../../resources/lang/uk.json"; import zh_CN from "../../resources/lang/zh-CN.json"; @customElement("lang-selector") export class LangSelector extends LitElement { @state() public translations: Record | undefined; @state() public defaultTranslations: Record | undefined; @state() public currentLang: string = "en"; @state() private languageList: any[] = []; @state() private showModal: boolean = false; @state() private debugMode: boolean = false; private debugKeyPressed: boolean = false; private languageMap: Record = { ar, bg, bn, de, el, en, es, eo, fr, it, hi, hu, ja, nl, pl, "pt-PT": pt_PT, "pt-BR": pt_BR, ru, sh, tr, tp, uk, cs, he, da, fa, fi, "sv-SE": sv_SE, "zh-CN": zh_CN, ko, mk, gl, sl, sk, }; createRenderRoot() { return this; } connectedCallback() { super.connectedCallback(); this.setupDebugKey(); this.initializeLanguage(); } private setupDebugKey() { window.addEventListener("keydown", (e) => { if (e.key?.toLowerCase() === "t") this.debugKeyPressed = true; }); window.addEventListener("keyup", (e) => { if (e.key?.toLowerCase() === "t") this.debugKeyPressed = false; }); } private getClosestSupportedLang(lang: string): string { if (!lang) return "en"; if (lang in this.languageMap) return lang; const base = lang.slice(0, 2); const candidates = Object.keys(this.languageMap).filter((key) => key.startsWith(base), ); if (candidates.length > 0) { candidates.sort((a, b) => b.length - a.length); // More specific first return candidates[0]; } return "en"; } private async initializeLanguage() { const browserLocale = navigator.language; const savedLang = localStorage.getItem("lang"); const userLang = this.getClosestSupportedLang(savedLang ?? browserLocale); this.defaultTranslations = this.loadLanguage("en"); this.translations = this.loadLanguage(userLang); this.currentLang = userLang; await this.loadLanguageList(); this.applyTranslation(); } private loadLanguage(lang: string): Record { const language = this.languageMap[lang] ?? {}; const flat = flattenTranslations(language); return flat; } private async loadLanguageList() { try { const data = this.languageMap; let list: any[] = []; const browserLang = new Intl.Locale(navigator.language).language; for (const langCode of Object.keys(data)) { const langData = data[langCode].lang; if (!langData) continue; list.push({ code: langData.lang_code ?? langCode, native: langData.native ?? langCode, en: langData.en ?? langCode, svg: langData.svg ?? langCode, }); } let debugLang: any = null; if (this.debugKeyPressed) { debugLang = { code: "debug", native: "Debug", en: "Debug", svg: "xx", }; this.debugMode = true; } const currentLangEntry = list.find((l) => l.code === this.currentLang); const browserLangEntry = browserLang !== this.currentLang && browserLang !== "en" ? list.find((l) => l.code === browserLang) : undefined; const englishEntry = this.currentLang !== "en" ? list.find((l) => l.code === "en") : undefined; list = list.filter( (l) => l.code !== this.currentLang && l.code !== browserLang && l.code !== "en" && l.code !== "debug", ); list.sort((a, b) => a.en.localeCompare(b.en)); const finalList: any[] = []; if (currentLangEntry) finalList.push(currentLangEntry); if (englishEntry) finalList.push(englishEntry); if (browserLangEntry) finalList.push(browserLangEntry); finalList.push(...list); if (debugLang) finalList.push(debugLang); this.languageList = finalList; } catch (err) { console.error("Failed to load language list:", err); } } private changeLanguage(lang: string) { localStorage.setItem("lang", lang); this.translations = this.loadLanguage(lang); this.currentLang = lang; this.applyTranslation(); this.showModal = false; } 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", "replay-panel", "help-modal", "settings-modal", "username-input", "public-lobby", "user-setting", "o-modal", "o-button", "territory-patterns-modal", ]; document.title = this.translateText("main.title") ?? document.title; document.querySelectorAll("[data-i18n]").forEach((element) => { const key = element.getAttribute("data-i18n"); if (key === null) return; const text = this.translateText(key); if (text === null) { console.warn(`Translation key not found: ${key}`); return; } element.textContent = text; }); components.forEach((tag) => { document.querySelectorAll(tag).forEach((el) => { if (typeof (el as any).requestUpdate === "function") { (el as any).requestUpdate(); } }); }); } public translateText( key: string, params: Record = {}, ): string { let text: string | undefined; if (this.translations && key in this.translations) { text = this.translations[key]; } else if (this.defaultTranslations && key in this.defaultTranslations) { text = this.defaultTranslations[key]; } else { console.warn(`Translation key not found: ${key}`); return key; } for (const param in params) { const value = params[param]; text = text.replace(`{${param}}`, String(value)); } return text; } private openModal() { this.debugMode = this.debugKeyPressed; this.showModal = true; this.loadLanguageList(); } render() { const currentLang = this.languageList.find((l) => l.code === this.currentLang) ?? (this.currentLang === "debug" ? { code: "debug", native: "Debug", en: "Debug", svg: "xx", } : { native: "English", en: "English", svg: "uk_us_flag", }); return html`
this.changeLanguage(e.detail.lang)} @close-modal=${() => (this.showModal = false)} > `; } } function flattenTranslations( obj: Record, parentKey = "", result: Record = {}, ): Record { for (const key in obj) { const value = obj[key]; const fullKey = parentKey ? `${parentKey}.${key}` : key; if (typeof value === "string") { result[fullKey] = value; } else if (value && typeof value === "object" && !Array.isArray(value)) { flattenTranslations(value, fullKey, result); } else { console.warn("Unknown type", typeof value, value); } } return result; }