Translate all out-of-game UI (start screen, lobbies, etc.) (#316)

This PR adds full translation support for all out-of-game UI elements,
including the start screen, public/private lobby modals, and other
pre-game interfaces.

All static text has been externalized to en.json and ja.json for future
language support.
If you find any spots that are not yet translated (missing from the
JSON), please let me know.
Thanks a lot!

This is a follow-up to PR
[#305](https://github.com/openfrontio/OpenFrontIO/pull/305).

---------

Co-authored-by: Cldprv <dubois.cnm@tutanota.com>
Co-authored-by: jacks0n <rosty.west89@gmail.com>
This commit is contained in:
Aotumuri
2025-03-24 17:12:04 -07:00
committed by GitHub
co-authored by Cldprv jacks0n
parent f188af6029
commit 9088adeb7a
14 changed files with 845 additions and 215 deletions
+95 -1
View File
@@ -304,7 +304,10 @@
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>
</select>
</div>
</div>
@@ -394,7 +397,98 @@
});
});
</script>
<script src="lang.js"></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