mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:30:43 +00:00
Move lang-selector into a lit element. This is required so we can import the lang json files using webpack file hashing. We need file hashing to bust the cache.
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
// Import language files
|
||||
import enTranslations from "../../resources/lang/en.json";
|
||||
import bgTranslations from "../../resources/lang/bg.json";
|
||||
import jaTranslations from "../../resources/lang/ja.json";
|
||||
import frTranslations from "../../resources/lang/fr.json";
|
||||
import nlTranslations from "../../resources/lang/nl.json";
|
||||
import deTranslations from "../../resources/lang/de.json";
|
||||
import esTranslations from "../../resources/lang/es.json";
|
||||
|
||||
const translations = {
|
||||
en: enTranslations,
|
||||
bg: bgTranslations,
|
||||
ja: jaTranslations,
|
||||
fr: frTranslations,
|
||||
nl: nlTranslations,
|
||||
de: deTranslations,
|
||||
es: esTranslations,
|
||||
};
|
||||
|
||||
@customElement("lang-selector")
|
||||
export class LangSelector extends LitElement {
|
||||
@state() public translations: any = {};
|
||||
@state() private defaultTranslations: any = {};
|
||||
@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(this.translations);
|
||||
}
|
||||
|
||||
private async loadLanguage(lang: string): Promise<any> {
|
||||
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(translations: any) {
|
||||
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",
|
||||
];
|
||||
|
||||
document.title = translations.main?.title || document.title;
|
||||
|
||||
document.querySelectorAll("[data-i18n]").forEach((element) => {
|
||||
const key = element.getAttribute("data-i18n");
|
||||
const keys = key.split(".");
|
||||
let text = translations;
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) break;
|
||||
}
|
||||
if (!text && this.defaultTranslations) {
|
||||
let fallback = this.defaultTranslations;
|
||||
for (const k of keys) {
|
||||
fallback = fallback?.[k];
|
||||
if (!fallback) break;
|
||||
}
|
||||
text = fallback;
|
||||
}
|
||||
if (text) {
|
||||
element.innerHTML = text;
|
||||
} else {
|
||||
console.warn(`Missing translation key: ${key}`);
|
||||
}
|
||||
});
|
||||
|
||||
components.forEach((tagName) => {
|
||||
const el = document.querySelector(tagName) as any;
|
||||
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, string | number> = {},
|
||||
): string {
|
||||
const keys = key.split(".");
|
||||
let text: any = this.translations;
|
||||
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) break;
|
||||
}
|
||||
|
||||
if (!text && this.defaultTranslations) {
|
||||
text = this.defaultTranslations;
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) return key;
|
||||
}
|
||||
}
|
||||
|
||||
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(this.translations);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<select
|
||||
@change=${(e: Event) =>
|
||||
this.changeLanguage((e.target as HTMLSelectElement).value)}
|
||||
class="text-center appearance-none w-full bg-blue-100 hover:bg-blue-200 text-blue-900 p-3 sm:p-4 lg:p-5 font-medium text-sm sm:text-base lg:text-lg rounded-md border-none cursor-pointer transition-colors duration-300"
|
||||
>
|
||||
<option value="en" ?selected=${this.currentLang === "en"}>
|
||||
English
|
||||
</option>
|
||||
<option value="bg" ?selected=${this.currentLang === "bg"}>
|
||||
Български
|
||||
</option>
|
||||
<option value="ja" ?selected=${this.currentLang === "ja"}>
|
||||
日本語
|
||||
</option>
|
||||
<option value="fr" ?selected=${this.currentLang === "fr"}>
|
||||
Français
|
||||
</option>
|
||||
<option value="nl" ?selected=${this.currentLang === "nl"}>
|
||||
Nederlands
|
||||
</option>
|
||||
<option value="de" ?selected=${this.currentLang === "de"}>
|
||||
Deutsch
|
||||
</option>
|
||||
<option value="es" ?selected=${this.currentLang === "es"}>
|
||||
Español
|
||||
</option>
|
||||
</select>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ import { GameType } from "../core/game/Game";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import GoogleAdElement from "./GoogleAdElement";
|
||||
import { GameConfig, GameInfo, GameRecord } from "../core/Schemas";
|
||||
import "./LangSelector";
|
||||
import { LangSelector } from "./LangSelector";
|
||||
|
||||
export interface JoinLobbyEvent {
|
||||
// Multiplayer games only have gameID, gameConfig is not known until game starts.
|
||||
@@ -51,6 +53,13 @@ class Client {
|
||||
constructor() {}
|
||||
|
||||
initialize(): void {
|
||||
const langSelector = document.querySelector(
|
||||
"lang-selector",
|
||||
) as LangSelector;
|
||||
if (!langSelector) {
|
||||
consolex.warn("Lang selector element not found");
|
||||
}
|
||||
|
||||
this.flagInput = document.querySelector("flag-input") as FlagInput;
|
||||
if (!this.flagInput) {
|
||||
consolex.warn("Flag input element not found");
|
||||
|
||||
+18
-20
@@ -1,3 +1,5 @@
|
||||
import { LangSelector } from "./LangSelector";
|
||||
|
||||
export function renderTroops(troops: number): string {
|
||||
return renderNumber(troops / 10);
|
||||
}
|
||||
@@ -71,29 +73,25 @@ export function generateCryptoRandomUUID(): string {
|
||||
);
|
||||
}
|
||||
|
||||
export function translateText(
|
||||
// Re-export translateText from LangSelector
|
||||
export const translateText = (
|
||||
key: string,
|
||||
params: Record<string, string | number> = {},
|
||||
): string {
|
||||
const keys = key.split(".");
|
||||
let text: any = (window as any).translations;
|
||||
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) break;
|
||||
): string => {
|
||||
const langSelector = document.querySelector("lang-selector") as LangSelector;
|
||||
if (!langSelector) {
|
||||
console.warn("LangSelector not found in DOM");
|
||||
return key;
|
||||
}
|
||||
|
||||
if (!text && (window as any).defaultTranslations) {
|
||||
text = (window as any).defaultTranslations;
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) return key;
|
||||
}
|
||||
// Wait for translations to be loaded
|
||||
if (
|
||||
!langSelector.translations ||
|
||||
Object.keys(langSelector.translations).length === 0
|
||||
) {
|
||||
console.warn("Translations not loaded yet");
|
||||
return key;
|
||||
}
|
||||
|
||||
for (const [param, value] of Object.entries(params)) {
|
||||
text = text.replace(`{${param}}`, String(value));
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
return langSelector.translateText(key, params);
|
||||
};
|
||||
|
||||
+1
-108
@@ -260,18 +260,7 @@
|
||||
secondary
|
||||
></o-button>
|
||||
<div class="container__row">
|
||||
<select
|
||||
id="lang-selector"
|
||||
class="text-center appearance-none w-full bg-blue-100 hover:bg-blue-200 text-blue-900 p-3 sm:p-4 lg:p-5 font-medium text-sm sm:text-base lg:text-lg rounded-md border-none cursor-pointer transition-colors duration-300"
|
||||
>
|
||||
<option value="en">English</option>
|
||||
<option value="bg">Български</option>
|
||||
<option value="ja">日本語</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="nl">Nederlands</option>
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="es">Español</option>
|
||||
</select>
|
||||
<lang-selector class="w-full"></lang-selector>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@@ -359,102 +348,6 @@
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", async function () {
|
||||
const locale = new Intl.Locale(navigator.language);
|
||||
const defaultLang = locale.language;
|
||||
const userLang = localStorage.getItem("lang") || defaultLang;
|
||||
|
||||
async function loadLanguage(lang) {
|
||||
try {
|
||||
const response = await fetch(`/lang/${lang}.json`);
|
||||
if (!response.ok)
|
||||
throw new Error(`Language file not found: ${lang}`);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("🚨 Translation load error:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function applyTranslation(translations) {
|
||||
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",
|
||||
];
|
||||
|
||||
document.title = translations.main?.title || document.title;
|
||||
|
||||
document.querySelectorAll("[data-i18n]").forEach((element) => {
|
||||
const key = element.getAttribute("data-i18n");
|
||||
const keys = key.split(".");
|
||||
let text = translations;
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) break;
|
||||
}
|
||||
if (!text && window.defaultTranslations) {
|
||||
let fallback = window.defaultTranslations;
|
||||
for (const k of keys) {
|
||||
fallback = fallback?.[k];
|
||||
if (!fallback) break;
|
||||
}
|
||||
text = fallback;
|
||||
}
|
||||
if (text) {
|
||||
element.innerHTML = text;
|
||||
} else {
|
||||
console.warn(`Missing translation key: ${key}`);
|
||||
}
|
||||
});
|
||||
components.forEach((tagName) => {
|
||||
const el = document.querySelector(tagName);
|
||||
if (el && typeof el.requestUpdate === "function") {
|
||||
el.requestUpdate();
|
||||
} else {
|
||||
console.warn(
|
||||
`requestUpdate() not available on <${tagName}> or element not found.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function changeLanguage(lang) {
|
||||
// console.log(`Changing language to: ${lang}`);
|
||||
localStorage.setItem("lang", lang);
|
||||
const translations = await loadLanguage(lang);
|
||||
window.translations = translations;
|
||||
applyTranslation(translations);
|
||||
}
|
||||
|
||||
const defaultTranslations = await loadLanguage("en");
|
||||
window.defaultTranslations = defaultTranslations;
|
||||
const translations = await loadLanguage(userLang);
|
||||
window.translations = translations;
|
||||
applyTranslation(translations);
|
||||
|
||||
const langSelector = document.getElementById("lang-selector");
|
||||
if (langSelector) {
|
||||
langSelector.value = userLang;
|
||||
}
|
||||
document
|
||||
.getElementById("lang-selector")
|
||||
.addEventListener("change", function (event) {
|
||||
changeLanguage(event.target.value);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Analytics -->
|
||||
<script
|
||||
|
||||
Reference in New Issue
Block a user