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;
}
}