From 5de109b64932fd5b51be1e8cef30e70b117f94e2 Mon Sep 17 00:00:00 2001 From: Lars Gebhardt-Kusche Date: Fri, 28 Nov 2025 03:06:13 +0100 Subject: [PATCH] lang --- config/fileload.php | 231 +----------------------------------- config/i18n.php | 234 +++++++++++++++++++++++++++++++++++++ public/assets/js/header.js | 54 ++------- public/assets/js/lang.js | 117 ++++--------------- 4 files changed, 273 insertions(+), 363 deletions(-) create mode 100644 config/i18n.php diff --git a/config/fileload.php b/config/fileload.php index 1a4a307..7469e92 100644 --- a/config/fileload.php +++ b/config/fileload.php @@ -24,233 +24,14 @@ if (php_sapi_name() !== 'cli') { } } -// ----------------------------------------------------------- -// Kleine Helper-Funktion für internes Logging + HTML-Debug -// ----------------------------------------------------------- -function usb_i18n_debug_log(string $msg): void -{ - // 1) In eine eigene Log-Datei schreiben (im config/-Ordner) - $logFile = __DIR__ . '/i18n_debug.log'; - $line = '[' . date('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL; - @file_put_contents($logFile, $line, FILE_APPEND); +require_once __DIR__ . '/i18n.php'; // <— NEU: zentrale Sprachlogik - // 2) optional auch ins PHP error_log - @error_log('[i18n] ' . $msg); - - // 3) Bei ?debug_i18n=1 zusätzlich HTML-Kommentar ausgeben - if (isset($_GET['debug_i18n']) && $_GET['debug_i18n'] == '1') { - echo "\n"; - } -} - -// ----------------------------------------------------------- -// Browser-Sprache aus HTTP_ACCEPT_LANGUAGE bestimmen -// ----------------------------------------------------------- -function usb_detect_browser_lang(array $availableLangs): ?string -{ - if (empty($availableLangs)) { - usb_i18n_debug_log('Browser-Lang: keine availableLangs – abbrechen'); - return null; - } - - $header = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? ''; - usb_i18n_debug_log('HTTP_ACCEPT_LANGUAGE: ' . $header); - - if ($header === '') { - return null; - } - - $parts = explode(',', $header); - - foreach ($parts as $part) { - $part = trim($part); - if ($part === '') { - continue; - } - - // Sprache vor dem ; nehmen (z.B. "de-DE", "en-US") - $langTag = strtolower(explode(';', $part)[0]); - if ($langTag === '') { - continue; - } - - // 2-Buchstaben-Code extrahieren - $code2 = substr($langTag, 0, 2); - if (!preg_match('/^[a-z]{2}$/', $code2)) { - continue; - } - - $exists = array_key_exists($code2, $availableLangs) ? 'yes' : 'no'; - usb_i18n_debug_log("Browser-Lang-Kandidat: {$code2} (exists: {$exists})"); - - if (isset($availableLangs[$code2])) { - return $code2; - } - } - - return null; -} - -// ----------------------------------------------------------- -// 1) Sprache aus ?lang lesen (nur 2-Buchstaben-Code zulassen) -// ----------------------------------------------------------- -$requestedLang = $_GET['lang'] ?? null; - -if (is_string($requestedLang)) { - $requestedLang = strtolower($requestedLang); - if (!preg_match('/^[a-z]{2}$/', $requestedLang)) { - $requestedLang = null; - } -} else { - $requestedLang = null; -} - -usb_i18n_debug_log('requestedLang (GET): ' . var_export($requestedLang, true)); - -// ----------------------------------------------------------- -// 2) Verfügbare JSON-Sprachen erkennen -// ----------------------------------------------------------- -$i18nDir = __DIR__ . '/../public/assets/i18n'; - -$langFiles = []; -if (is_dir($i18nDir)) { - $langFiles = glob($i18nDir . '/*.json') ?: []; -} - -$availableLangs = []; - -// Alle vorhandenen JSONs einsammeln -foreach ($langFiles as $file) { - $raw = @file_get_contents($file); - if ($raw === false) { - usb_i18n_debug_log('Konnte Datei nicht lesen: ' . $file); - continue; - } - - $json = json_decode($raw, true); - if (!is_array($json)) { - usb_i18n_debug_log('Ungültiges JSON in ' . $file . ' :: ' . json_last_error_msg()); - continue; - } - - $meta = $json['meta'] ?? []; - - // Optional: nur Sprachen mit enabled=false ausblenden - if (array_key_exists('enabled', $meta) && $meta['enabled'] === false) { - usb_i18n_debug_log('Sprache deaktiviert (enabled=false) in ' . $file); - continue; - } - - // Sprachcode bestimmen (immer 2-Buchstaben) - $code = strtolower($meta['code'] ?? basename($file, '.json')); - if (!preg_match('/^[a-z]{2}$/', $code)) { - // Sonderdateien (template.json etc.) ignorieren - usb_i18n_debug_log('Ignoriere Datei mit unpassendem Code: ' . $file); - continue; - } - - $label = $meta['label'] ?? strtoupper($code); - $flag = $meta['flag'] ?? '🏳️'; - - $availableLangs[$code] = [ - 'code' => $code, - 'label' => $label, - 'flag' => $flag, - ]; -} - -usb_i18n_debug_log('availableLangs keys: ' . implode(', ', array_keys($availableLangs))); - -// Falls keine Sprachdateien gefunden wurden → Minimal-Fallback -if (empty($availableLangs)) { - usb_i18n_debug_log('WARN: keine Sprachdateien gefunden, fallback auf en'); - $availableLangs = [ - 'en' => [ - 'code' => 'en', - 'label' => 'English', - 'flag' => '🏳️', - ], - ]; -} - -// ----------------------------------------------------------- -// 3) Endgültige Sprache wählen nach deiner Priorität -// ----------------------------------------------------------- -$lang = null; - -// 1) ?lang=xx wird bevorzugt, wenn gültig + vorhanden -if ($requestedLang && isset($availableLangs[$requestedLang])) { - $lang = $requestedLang; - usb_i18n_debug_log('Auswahl: requestedLang übernommen: ' . $lang); -} - -// 2) Sonst HTTP_ACCEPT_LANGUAGE (Browser), erste passende Sprache -if ($lang === null) { - $browserLang = usb_detect_browser_lang($availableLangs); - if ($browserLang !== null) { - $lang = $browserLang; - usb_i18n_debug_log('Auswahl: Browser-Lang übernommen: ' . $lang); - } -} - -// 3) Wenn Browser-Sprache nicht existiert → 'en', wenn vorhanden -if ($lang === null && isset($availableLangs['en'])) { - $lang = 'en'; - usb_i18n_debug_log('Auswahl: Fallback auf en, da kein Match'); -} - -// 4) Sonst: erste Sprache aus $availableLangs -if ($lang === null) { - $keys = array_keys($availableLangs); - $lang = $keys[0] ?? 'en'; - usb_i18n_debug_log('Auswahl: Fallback auf erste Sprache: ' . $lang); -} - -usb_i18n_debug_log('FINAL LANG: ' . $lang); - -// ----------------------------------------------------------- -// 4) Aktive Sprachdatei laden -// ----------------------------------------------------------- -$activeLangFile = $i18nDir . '/' . $lang . '.json'; -$activeLangData = []; - -if (is_readable($activeLangFile)) { - $json = json_decode(@file_get_contents($activeLangFile), true); - if (is_array($json)) { - $activeLangData = $json; - } else { - usb_i18n_debug_log('Aktive JSON nicht array: ' . $activeLangFile); - } -} else { - usb_i18n_debug_log('Aktive Sprachdatei nicht lesbar: ' . $activeLangFile); -} - -// ----------------------------------------------------------- -// 5) Fallback-Sprache: immer EN, wenn vorhanden & nicht aktuell -// ----------------------------------------------------------- -$fallbackLangData = []; -$fallbackFile = $i18nDir . '/en.json'; - -if ($lang !== 'en' && is_readable($fallbackFile)) { - $json = json_decode(@file_get_contents($fallbackFile), true); - if (is_array($json)) { - $fallbackLangData = $json; - } else { - usb_i18n_debug_log('Fallback-JSON (en) nicht array: ' . $fallbackFile); - } -} - -// ----------------------------------------------------------- -// 6) Globale i18n-Struktur bereitstellen -// ----------------------------------------------------------- -$GLOBALS['lang'] = $lang; -$GLOBALS['availableLangs'] = $availableLangs; - -$GLOBALS['i18n'] = [ - 'current' => $activeLangData, - 'fallback' => $fallbackLangData, +// ab hier kannst du überall $GLOBALS['lang'] und $GLOBALS['availableLangs'] nutzen +// und für JS: +$usbConfig = [ + // ... dein sonstiges Zeug ... + 'i18n' => app_i18n_get_frontend_config(), ]; - // ----------------------------------------------------------- // 7) Rest des Systems laden // ----------------------------------------------------------- diff --git a/config/i18n.php b/config/i18n.php new file mode 100644 index 0000000..4b53e3b --- /dev/null +++ b/config/i18n.php @@ -0,0 +1,234 @@ + 2) { + $code = substr($code, 0, 2); + } + + if ($code === '') { + return null; + } + + $label = isset($meta['label']) && $meta['label'] !== '' + ? (string)$meta['label'] + : strtoupper($code); + + $flag = isset($meta['flag']) ? (string)$meta['flag'] : ''; + + return [ + 'code' => $code, + 'label' => $label, + 'flag' => $flag, + ]; +} + +/** + * Alle verfügbaren Sprachen aus /public/assets/i18n/*.json ermitteln. + * Verfügbar = JSON mit meta.enabled === true. + * EN wird garantiert hinzugefügt (Fallback), falls nicht gefunden. + */ +function app_i18n_detect_available_languages(): array +{ + $baseDir = realpath(__DIR__ . '/../public/assets/i18n'); + if ($baseDir === false) { + // Wenn gar kein Verzeichnis da ist: minimaler EN-Fallback + return [ + 'en' => [ + 'code' => 'en', + 'label' => 'English', + 'flag' => '', + ], + ]; + } + + $files = glob($baseDir . '/*.json') ?: []; + $langs = []; + + foreach ($files as $file) { + $meta = app_i18n_load_language_meta_from_file($file); + if ($meta === null) { + continue; + } + + $code = $meta['code']; + + // Erste gültige Definition pro Code gewinnt + if (!isset($langs[$code])) { + $langs[$code] = $meta; + } + } + + // EN muss immer vorhanden sein (laut deiner Vorgabe) + if (!isset($langs['en'])) { + // Versuch: gibt es eine en.json, auch wenn enabled=false? + foreach ($files as $file) { + $base = strtolower(basename($file, '.json')); + if ($base === 'en') { + $json = @file_get_contents($file); + $data = json_decode($json, true); + $meta = is_array($data['meta'] ?? null) ? $data['meta'] : []; + + $label = isset($meta['label']) && $meta['label'] !== '' + ? (string)$meta['label'] + : 'English'; + $flag = isset($meta['flag']) ? (string)$meta['flag'] : ''; + + $langs['en'] = [ + 'code' => 'en', + 'label' => $label, + 'flag' => $flag, + ]; + break; + } + } + } + + // Wenn immer noch kein EN → minimaler Stub + if (!isset($langs['en'])) { + $langs['en'] = [ + 'code' => 'en', + 'label' => 'English', + 'flag' => '', + ]; + } + + ksort($langs); + + return $langs; +} + +/** + * Browsersprache aus HTTP_ACCEPT_LANGUAGE extrahieren (2-Buchstaben), + * aber nur, wenn sie in $available existiert. + */ +function app_i18n_detect_browser_lang(array $available): ?string +{ + $header = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? ''; + if ($header === '') { + return null; + } + + $parts = explode(',', $header); + foreach ($parts as $part) { + $part = trim($part); + if ($part === '') { + continue; + } + + $code = strtolower(substr($part, 0, 2)); // "de-DE" → "de" + if (isset($available[$code])) { + return $code; + } + } + + return null; +} + +/** + * Aktuelle Sprache bestimmen: + * 1) ?lang=xx (wenn in $available) + * 2) Browsersprache (wenn in $available) + * 3) Fallback "en" + */ +function app_i18n_resolve_current_lang(array $available): string +{ + // 1) URL-Parameter ?lang=xx + if (!empty($_GET['lang'])) { + $param = strtolower(substr($_GET['lang'], 0, 2)); + if (isset($available[$param])) { + return $param; + } + } + + // 2) Browsersprache + $browser = app_i18n_detect_browser_lang($available); + if ($browser !== null) { + return $browser; + } + + // 3) Standard EN + if (isset($available['en'])) { + return 'en'; + } + + // Sicherheitsfallback: erste verfügbare Sprache + $keys = array_keys($available); + return $keys[0] ?? 'en'; +} + +// ----------------------------------------------------- +// Bootstrap ausführen +// ----------------------------------------------------- + +$availableLangs = app_i18n_detect_available_languages(); +$currentLang = app_i18n_resolve_current_lang($availableLangs); + +// Global bereitstellen +$GLOBALS['availableLangs'] = $availableLangs; +$GLOBALS['lang'] = $currentLang; + +// Optional in Session merken (muss nicht, schadet aber auch nicht) +$_SESSION['lang'] = $currentLang; + +/** + * Frontend-Config für JS (usbConfig.i18n) + * → benutzt direkt die Meta-Daten aus den JSONs (code, label, flag) + */ +function app_i18n_get_frontend_config(): array +{ + return [ + 'current' => $GLOBALS['lang'] ?? 'en', + 'available' => $GLOBALS['availableLangs'] ?? [], + ]; +} diff --git a/public/assets/js/header.js b/public/assets/js/header.js index 7b7555e..93e9304 100644 --- a/public/assets/js/header.js +++ b/public/assets/js/header.js @@ -1,19 +1,9 @@ // public/assets/js/header.js document.addEventListener("DOMContentLoaded", function () { - // -------------------------------------------------- - // Sprachauflösung: basiert auf derselben Logik wie lang.js - // - // Priorität: - // a) ?lang= in der URL - // b) localStorage (usbcheck_lang) - // c) Browsersprache - // d) 'en' wenn vorhanden - // e) erste verfügbare Sprache - // -------------------------------------------------- - const cfg = window.usbConfig || {}; - const i18nCfg = cfg.i18n || {}; - const availLangs = i18nCfg.available || {}; - const availCodes = Object.keys(availLangs); + const cfg = window.usbConfig || {}; + const i18nCfg = cfg.i18n || {}; + const availLangs = i18nCfg.available || {}; + const availCodes = Object.keys(availLangs); function normalizeLang(code) { if (!code) return ""; @@ -47,43 +37,30 @@ document.addEventListener("DOMContentLoaded", function () { const url = new URL(window.location.href); const urlLang = normalizeLang(url.searchParams.get("lang")); - // a) ?lang= aus der URL, wenn gültig + // 1) ?lang if (urlLang && availCodes.includes(urlLang)) { return urlLang; } - // b) localStorage (vom lang.js gesetzt) - const stored = normalizeLang(localStorage.getItem("usbcheck_lang")); - if (stored && availCodes.includes(stored)) { - return stored; - } - if (stored && !availCodes.includes(stored)) { - localStorage.removeItem("usbcheck_lang"); - } - - // c) Browsersprache + // 2) Browsersprache const browserLang = detectBrowserLang(); if (browserLang) { return browserLang; } - // d) Fallback: en, wenn vorhanden + // 3) EN if (availCodes.includes("en")) { return "en"; } - // e) Sonst erste verfügbare Sprache if (availCodes.length > 0) { return availCodes[0]; } - // f) absolute Notlösung return "en"; } - // -------------------------------------------------- - // LOGIN-BUTTON → /login mit redirect + lang - // -------------------------------------------------- + // LOGIN-BUTTON const loginBtn = document.getElementById("loginButton"); if (loginBtn) { @@ -96,7 +73,6 @@ document.addEventListener("DOMContentLoaded", function () { const lang = resolveCurrentLang(); - // Wenn wir bereits auf /login sind → nur zum #auth scrollen if (currentPath === "/login" || currentPath === "/login/") { const authEl = document.getElementById("auth"); if (authEl) { @@ -105,7 +81,6 @@ document.addEventListener("DOMContentLoaded", function () { return; } - // Sonst: auf Login-Seite mit lang + redirect-Parameter const url = "/login/?lang=" + encodeURIComponent(lang) + "&redirect=" + encodeURIComponent(redirect) + "#auth"; @@ -114,9 +89,7 @@ document.addEventListener("DOMContentLoaded", function () { }); } - // -------------------------------------------------- - // AVATAR-MENÜ (userAvatar / userMenu) - // -------------------------------------------------- + // AVATAR-MENÜ & LOGOUT wie gehabt (deine bestehende Logik) const avatarBtn = document.getElementById("userAvatar"); const userMenu = document.getElementById("userMenu"); @@ -129,7 +102,6 @@ document.addEventListener("DOMContentLoaded", function () { avatarBtn.setAttribute("aria-expanded", expanded ? "false" : "true"); }); - // Klick außerhalb schließt Menü document.addEventListener("click", function (event) { if (userMenu.classList.contains("hidden")) { return; @@ -145,9 +117,6 @@ document.addEventListener("DOMContentLoaded", function () { }); } - // -------------------------------------------------- - // LOGOUT-MODAL - // -------------------------------------------------- const logoutButtons = document.querySelectorAll('[data-logout-link="true"]'); const backdrop = document.getElementById("logoutModalBackdrop"); const btnConfirm = document.getElementById("logoutConfirm"); @@ -182,7 +151,6 @@ document.addEventListener("DOMContentLoaded", function () { showLogoutModal(href); - // Avatar-Menü beim Öffnen des Modals schließen if (userMenu && !userMenu.classList.contains("hidden")) { userMenu.classList.add("hidden"); if (avatarBtn) { @@ -192,7 +160,6 @@ document.addEventListener("DOMContentLoaded", function () { }); }); - // Bestätigen → weiter zur Logout-URL btnConfirm.addEventListener("click", function () { if (logoutTarget) { window.location.href = logoutTarget; @@ -201,12 +168,10 @@ document.addEventListener("DOMContentLoaded", function () { } }); - // Abbrechen → Modal schließen btnCancel.addEventListener("click", function () { hideLogoutModal(); }); - // Klick auf Hintergrund schließt Modal (außer man klickt auf die Card) backdrop.addEventListener("click", function (event) { const card = backdrop.querySelector(".max-w-sm"); if (card && !card.contains(event.target)) { @@ -214,7 +179,6 @@ document.addEventListener("DOMContentLoaded", function () { } }); - // ESC-Taste schließt Modal document.addEventListener("keydown", function (event) { if (event.key === "Escape" && !backdrop.classList.contains("hidden")) { hideLogoutModal(); diff --git a/public/assets/js/lang.js b/public/assets/js/lang.js index f0a403a..1d2e8cd 100644 --- a/public/assets/js/lang.js +++ b/public/assets/js/lang.js @@ -1,26 +1,14 @@ // public/assets/js/lang.js (function () { - // --------------------------------------------- - // 1) Basis-Config aus PHP (app_config.php) - // --------------------------------------------- - const usbConfig = window.usbConfig || {}; + const usbConfig = window.usbConfig || {}; const i18nConfig = usbConfig.i18n || {}; - // Map: { de: {code, label, flag}, en: {...}, ... } const availableLangs = i18nConfig.available || {}; - - // Liste der unterstützten Sprachen als Codes const supportedLangs = Object.keys(availableLangs); - // Aktuelle Sprache (wird unten per getInitialLang() gesetzt) let currentLang = "en"; - - // Translations-Map (geflacht) let translations = {}; - // --------------------------------------------- - // 2) Helper: Translations flatten - // --------------------------------------------- function flattenTranslations(obj, target = {}) { Object.keys(obj || {}).forEach((key) => { const val = obj[key]; @@ -33,13 +21,9 @@ 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", @@ -58,9 +42,6 @@ }, text); } - // ------------------------------------------------ - // 4) Browsersprache erkennen (2-Buchstaben-Code) - // ------------------------------------------------ function detectBrowserLang() { const nav = window.navigator || {}; const candidates = []; @@ -84,58 +65,38 @@ return null; } - // --------------------------------------------- - // 5) Initiale Sprache bestimmen - // - // Priorität: - // a) URL ?lang= (falls gültig) - // b) localStorage (explizit gewählte Sprache) - // c) Browsersprache - // d) 'en', wenn vorhanden - // e) erste verfügbare Sprache - // --------------------------------------------- + // *** Deine Priorität: + // 1) ?lang + // 2) Browser + // 3) en function getInitialLang() { const urlParams = new URLSearchParams(window.location.search); const paramLang = (urlParams.get("lang") || "").toLowerCase(); - // a) URL-Parameter, wenn gültig + // 1) URL-Parameter if (paramLang && supportedLangs.includes(paramLang)) { return paramLang; } - // b) localStorage (vom User über Switcher gewählt) - 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"); + // 2) Browsersprache + const browser = detectBrowserLang(); + if (browser) { + return browser; } - // c) Browsersprache - const browserLang = detectBrowserLang(); - if (browserLang) { - return browserLang; - } - - // d) Wenn 'en' existiert → nimm 'en' + // 3) EN (immer vorhanden laut deiner Vorgabe) if (supportedLangs.includes("en")) { return "en"; } - // e) Sonst erste verfügbare Sprache + // Sicherheitsfallback if (supportedLangs.length > 0) { return supportedLangs[0]; } - // Worst-Case (sollte nie eintreten) return "en"; } - // --------------------------------------------- - // 6) i18n JSON per Fetch laden - // --------------------------------------------- async function loadLangFile(lang) { const code = (lang || "").toLowerCase(); if (!supportedLangs.includes(code)) { @@ -145,9 +106,7 @@ } try { - const res = await fetch(`/assets/i18n/${code}.json`, { - cache: "no-store", - }); + 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); @@ -157,9 +116,6 @@ } } - // --------------------------------------------- - // 7) Data-i18n-Texte einsetzen - // --------------------------------------------- function applyTranslations() { document.documentElement.setAttribute("lang", currentLang); @@ -169,21 +125,16 @@ 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; }); } - // --------------------------------------------- - // 8) Label + Flag im Button aktualisieren - // --------------------------------------------- function updateLangCurrentLabel(lang) { const code = (lang || "").toLowerCase(); @@ -208,14 +159,8 @@ } } - // --------------------------------------------- - // 9) Sprache setzen (via Switcher) - // - // - akzeptiert nur Sprachen aus availableLangs - // - speichert in localStorage - // - schreibt ?lang= in die URL - // - lädt Seite neu, damit PHP + JS synchron sind - // --------------------------------------------- + // Wenn du später mal explizit via JS Sprache setzen willst + // (z.B. beim Klick auf ein eigenes UI-Element), kannst du: function setLang(lang) { const code = (lang || "").toLowerCase(); if (!supportedLangs.includes(code)) { @@ -225,20 +170,13 @@ const url = new URL(window.location.href); url.searchParams.set("lang", code); - localStorage.setItem("usbcheck_lang", code); - window.location.href = url.toString(); } - // --------------------------------------------- - // 10) DOM Ready - // --------------------------------------------- document.addEventListener("DOMContentLoaded", async function () { - // Initiale Sprache (kombiniert aus URL, localStorage, Browser) currentLang = getInitialLang(); updateLangCurrentLabel(currentLang); - // i18n JSON laden & Texte einsetzen await loadLangFile(currentLang); applyTranslations(); @@ -258,26 +196,19 @@ } } }); - - 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) + // Nur falls du später data-lang in .lang-pill nutzt, + // im aktuellen header.php kommen die Links ja direkt mit ?lang=... aus PHP. document.addEventListener("click", function (e) { const btn = e.target.closest(".lang-pill"); - if (btn) { - const lang = btn.getAttribute("data-lang"); - if (lang) { - // Nur wenn data-lang gesetzt ist → JS-gesteuerter Wechsel - setLang(lang); - e.preventDefault(); - } - } + if (!btn) return; + + const targetLang = btn.getAttribute("data-lang"); + if (!targetLang) return; + + e.preventDefault(); + setLang(targetLang); }); }); })();