296 lines
9.2 KiB
JavaScript
296 lines
9.2 KiB
JavaScript
// public/assets/js/lang.js
|
||
|
||
(function () {
|
||
// ---------------------------------------------
|
||
// 1) Basis-Config aus PHP (app_config.php)
|
||
// ---------------------------------------------
|
||
const usbConfig = window.usbConfig || {};
|
||
const i18nConfig = usbConfig.i18n || {};
|
||
|
||
// Map: { de: {code, label, flag}, en: {...}, dk: {...}, ... }
|
||
const availableLangs = i18nConfig.available || {};
|
||
|
||
// Liste der unterstützten Sprachen als Codes
|
||
const supportedLangs = Object.keys(availableLangs);
|
||
|
||
// Sprache, die PHP in fileload.php ermittelt hat
|
||
let currentLang = (i18nConfig.current || usbConfig.lang || "en").toLowerCase();
|
||
|
||
// Wenn PHP eine Sprache setzt, die nicht in available ist, nimm erste verfügbare
|
||
if (!supportedLangs.includes(currentLang) && supportedLangs.length > 0) {
|
||
currentLang = supportedLangs[0];
|
||
}
|
||
|
||
// Fallback, wenn gar nichts da ist
|
||
if (!currentLang) {
|
||
currentLang = "en";
|
||
}
|
||
|
||
let translations = {};
|
||
|
||
// ---------------------------------------------
|
||
// 2) Helper: Translations flatten
|
||
// ---------------------------------------------
|
||
function flattenTranslations(obj, target = {}) {
|
||
Object.keys(obj || {}).forEach((key) => {
|
||
const val = obj[key];
|
||
if (val && typeof val === "object" && !Array.isArray(val)) {
|
||
flattenTranslations(val, target);
|
||
} else {
|
||
target[key] = val;
|
||
}
|
||
});
|
||
return target;
|
||
}
|
||
|
||
// ---------------------------------------------
|
||
// 3) Domain-Platzhalter ersetzen
|
||
// ---------------------------------------------
|
||
function applyDomainPlaceholders(text) {
|
||
if (typeof text !== "string") return text;
|
||
|
||
// Primär aus usbConfig.domains, Fallback auf window.appDomains
|
||
const domains = usbConfig.domains || window.appDomains || {};
|
||
const replacements = {
|
||
"{{primary_domain}}": domains.primaryDomain || "usbcheck.it",
|
||
"{{primary_url}}":
|
||
domains.primaryUrl ||
|
||
"https://" + (domains.primaryDomain || "usbcheck.it"),
|
||
"{{fakecheck_domain}}": domains.fakecheckDomain || "ismyusbfake.com",
|
||
"{{fakecheck_url}}":
|
||
domains.fakecheckUrl ||
|
||
"https://" + (domains.fakecheckDomain || "ismyusbfake.com"),
|
||
};
|
||
|
||
return Object.keys(replacements).reduce((acc, token) => {
|
||
const value = replacements[token];
|
||
return acc.split(token).join(value);
|
||
}, text);
|
||
}
|
||
|
||
// ---------------------------------------------
|
||
// 4) Initiale Sprache bestimmen
|
||
//
|
||
// Priorität:
|
||
// a) URL ?lang= (falls gültig und in available)
|
||
// b) PHP (usbConfig.i18n.current / usbConfig.lang)
|
||
// c) localStorage (falls gültig)
|
||
// d) 'en', wenn vorhanden
|
||
// e) erste verfügbare Sprache
|
||
// ---------------------------------------------
|
||
function getInitialLang() {
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const paramLang = (urlParams.get("lang") || "").toLowerCase();
|
||
|
||
// a) URL-Parameter, wenn gültig
|
||
if (paramLang && supportedLangs.includes(paramLang)) {
|
||
return paramLang;
|
||
}
|
||
|
||
// b) Server-seitig ermittelte Sprache (inkl. HTTP_ACCEPT_LANGUAGE)
|
||
const serverLang = (i18nConfig.current || usbConfig.lang || "").toLowerCase();
|
||
if (serverLang && supportedLangs.includes(serverLang)) {
|
||
return serverLang;
|
||
}
|
||
|
||
// c) localStorage, falls gültig
|
||
const stored = (localStorage.getItem("usbcheck_lang") || "").toLowerCase();
|
||
if (stored) {
|
||
if (supportedLangs.includes(stored)) {
|
||
return stored;
|
||
}
|
||
// ungültigen alten Wert aufräumen
|
||
localStorage.removeItem("usbcheck_lang");
|
||
}
|
||
|
||
// d) Wenn 'en' existiert → nimm 'en'
|
||
if (supportedLangs.includes("en")) {
|
||
return "en";
|
||
}
|
||
|
||
// e) Sonst erste verfügbare Sprache
|
||
if (supportedLangs.length > 0) {
|
||
return supportedLangs[0];
|
||
}
|
||
|
||
// Worst-Case (sollte nie eintreten)
|
||
return "en";
|
||
}
|
||
|
||
// ---------------------------------------------
|
||
// 5) i18n JSON per Fetch laden
|
||
// ---------------------------------------------
|
||
async function loadLangFile(lang) {
|
||
const code = (lang || "").toLowerCase();
|
||
if (!supportedLangs.includes(code)) {
|
||
console.warn("i18n: unsupported language in loadLangFile:", code);
|
||
translations = {};
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const res = await fetch(`/assets/i18n/${code}.json`, {
|
||
cache: "no-store",
|
||
});
|
||
if (!res.ok) throw new Error(`Failed to load /assets/i18n/${code}.json`);
|
||
const raw = await res.json();
|
||
translations = flattenTranslations(raw);
|
||
} catch (err) {
|
||
console.error("i18n load error:", err);
|
||
translations = {};
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------
|
||
// 6) Data-i18n-Texte einsetzen
|
||
// ---------------------------------------------
|
||
function applyTranslations() {
|
||
document.documentElement.setAttribute("lang", currentLang);
|
||
|
||
document.querySelectorAll("[data-i18n]").forEach((node) => {
|
||
const key = node.getAttribute("data-i18n");
|
||
if (!key) return;
|
||
let value = translations[key];
|
||
if (typeof value !== "string") return;
|
||
|
||
// {year}-Platzhalter ersetzen (z. B. footer_copy)
|
||
if (value.includes("{year}")) {
|
||
const year = new Date().getFullYear();
|
||
value = value.replace("{year}", year);
|
||
}
|
||
|
||
value = applyDomainPlaceholders(value);
|
||
|
||
node.innerHTML = value;
|
||
});
|
||
}
|
||
|
||
// ---------------------------------------------
|
||
// 7) Label + Flag im Button aktualisieren
|
||
//
|
||
// Nutzt ausschließlich availableLangs aus PHP:
|
||
// availableLangs[code] = { code, label, flag }
|
||
// ---------------------------------------------
|
||
function updateLangCurrentLabel(lang) {
|
||
const code = (lang || "").toLowerCase();
|
||
|
||
const info = availableLangs[code] || {
|
||
code: code || "en",
|
||
label: (code || "en").toUpperCase(),
|
||
flag: "",
|
||
};
|
||
|
||
const node =
|
||
document.getElementById("langCurrentLabel") ||
|
||
document.getElementById("langCurrent");
|
||
if (!node) return;
|
||
|
||
const flag = info.flag || "";
|
||
const label = info.label || info.code.toUpperCase();
|
||
|
||
if (flag) {
|
||
node.innerHTML = `<span class="mr-1.5">${flag}</span><span>${label}</span>`;
|
||
} else {
|
||
node.textContent = label;
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------
|
||
// 8) Sprache setzen
|
||
//
|
||
// - akzeptiert JEDE Sprache, die in availableLangs liegt
|
||
// - speichert in localStorage
|
||
// - schreibt ?lang= in die URL
|
||
// - lädt Seite neu, damit PHP + JS synchron sind
|
||
// ---------------------------------------------
|
||
async function setLang(lang) {
|
||
const code = (lang || "").toLowerCase();
|
||
if (!supportedLangs.includes(code)) {
|
||
console.warn("Unsupported language:", code, supportedLangs);
|
||
return;
|
||
}
|
||
|
||
// URL-Parameter setzen
|
||
const url = new URL(window.location.href);
|
||
url.searchParams.set("lang", code);
|
||
localStorage.setItem("usbcheck_lang", code);
|
||
|
||
// komplette Seite neu laden,
|
||
// damit PHP (fileload.php) + Meta-Tags + JS-Konfig konsistent sind
|
||
window.location.href = url.toString();
|
||
}
|
||
|
||
// ---------------------------------------------
|
||
// 9) DOM Ready
|
||
// ---------------------------------------------
|
||
document.addEventListener("DOMContentLoaded", async function () {
|
||
// Initiale Sprache (kombiniert aus URL, PHP, localStorage)
|
||
currentLang = getInitialLang();
|
||
updateLangCurrentLabel(currentLang);
|
||
|
||
// i18n JSON laden & Texte einsetzen
|
||
await loadLangFile(currentLang);
|
||
applyTranslations();
|
||
|
||
const langCurrent = document.getElementById("langCurrent");
|
||
const langMenu = document.getElementById("langMenu");
|
||
|
||
if (langCurrent && langMenu) {
|
||
langCurrent.addEventListener("click", function (e) {
|
||
e.stopPropagation();
|
||
langMenu.classList.toggle("hidden");
|
||
});
|
||
|
||
document.addEventListener("click", function (e) {
|
||
if (!langMenu.classList.contains("hidden")) {
|
||
if (!langMenu.contains(e.target) && !langCurrent.contains(e.target)) {
|
||
langMenu.classList.add("hidden");
|
||
}
|
||
}
|
||
});
|
||
|
||
langMenu.addEventListener("click", function (e) {
|
||
const pill = e.target.closest(".lang-pill");
|
||
if (pill) {
|
||
langMenu.classList.add("hidden");
|
||
}
|
||
});
|
||
}
|
||
|
||
// Sprachumschaltung über .lang-pill (Buttons im Dropdown)
|
||
document.addEventListener("click", function (e) {
|
||
const btn = e.target.closest(".lang-pill");
|
||
if (btn) {
|
||
const lang = btn.getAttribute("data-lang");
|
||
if (lang) {
|
||
setLang(lang);
|
||
}
|
||
}
|
||
});
|
||
|
||
// (Login / Avatar Dummy kann bleiben – unverändert)
|
||
const loginButton = document.getElementById("loginButton");
|
||
const userAvatar = document.getElementById("userAvatar");
|
||
const avatarInitials = document.getElementById("avatarInitials");
|
||
|
||
if (loginButton && userAvatar) {
|
||
loginButton.addEventListener("click", function () {
|
||
loginButton.classList.add("hidden");
|
||
userAvatar.classList.remove("hidden");
|
||
|
||
if (avatarInitials && !avatarInitials.textContent.trim()) {
|
||
avatarInitials.textContent = "UC";
|
||
}
|
||
});
|
||
|
||
if (userAvatar) {
|
||
userAvatar.addEventListener("click", function () {
|
||
alert(
|
||
"Account-Menü Platzhalter – hier später Profil/Logout etc. einbauen."
|
||
);
|
||
});
|
||
}
|
||
}
|
||
});
|
||
})();
|