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'] ?? [], ]; } /** * Komplette JSON-Struktur für eine Sprache laden. * Nutzt einfachen Request-Cache, damit pro Sprache nur einmal von Platte gelesen wird. */ function app_i18n_load_lang_json(string $lang): array { static $cache = []; $lang = strtolower(substr($lang, 0, 5)); if (isset($cache[$lang])) { return $cache[$lang]; } $baseDir = realpath(__DIR__ . '/../public/assets/i18n'); if ($baseDir === false) { $cache[$lang] = []; return $cache[$lang]; } $path = $baseDir . '/' . $lang . '.json'; if (!is_file($path)) { // Fallback: en.json, falls vorhanden $fallback = $baseDir . '/en.json'; if (is_file($fallback)) { $json = @file_get_contents($fallback); $data = json_decode($json, true); $cache[$lang] = is_array($data) ? $data : []; return $cache[$lang]; } $cache[$lang] = []; return $cache[$lang]; } $json = @file_get_contents($path); $data = json_decode($json, true); $cache[$lang] = is_array($data) ? $data : []; return $cache[$lang]; } /** * Aus einem Label einen stabilen i18n-Key für Nav-Anker bauen. * Beispiel: "So funktioniert USBCheck!" -> "nav_so_funktioniert_usbcheck" */ function app_i18n_make_anchor_key(string $label): string { // HTML-Entities entfernen (z. B. &) $decoded = html_entity_decode($label, ENT_QUOTES | ENT_HTML5, 'UTF-8'); // Kleinbuchstaben $decoded = mb_strtolower($decoded, 'UTF-8'); // Alles, was kein a-z oder 0-9 ist, durch Unterstrich ersetzen $key = preg_replace('/[^a-z0-9]+/u', '_', $decoded); // Mehrfache Unterstriche trimmen $key = trim($key, '_'); if ($key === '') { $key = 'item'; } // Prefix, damit klar ist, dass es Navigationskeys sind return 'nav_' . $key; } /** * Nav-Anker für eine Seite aus der Sprachdatei holen. * * Haupt-Variante im JSON: * * "pages": { * "landing": { * "anchors": { * "how": "So funktioniert USBCheck", * "problem": "Warum gefälschte USB-Sticks gefährlich sind", * "features": "Funktionen", * "security": "Sicherheit", * "faq": "FAQ" * } * } * } * * Optional explizit: * "anchors": { * "how": { "label": "So funktioniert USBCheck", "i18n": "nav_how" }, * "faq": { "i18n": "nav_faq" } * } * * Rückgabe-Format: * [ * [ 'href' => '#how', 'label' => 'So funktioniert USBCheck', 'i18n' => 'nav_so_funktioniert_usbcheck' ], * [ 'href' => '#faq', 'label' => '', 'i18n' => 'nav_faq' ], * ] */ function app_get_nav_anchors(string $pageKey): array { $lang = $GLOBALS['lang'] ?? 'en'; $data = app_i18n_load_lang_json($lang); $cfg = $data['pages'][$pageKey]['anchors'] ?? null; if (!is_array($cfg)) { return []; } $anchors = []; foreach ($cfg as $id => $value) { $id = trim((string)$id); if ($id === '') { continue; } $href = '#' . $id; $label = ''; $i18n = ''; if (is_string($value)) { // String IMMER als Label übernehmen $labelTrim = trim($value); if ($labelTrim === '') { continue; } $label = $labelTrim; // i18n-Key automatisch aus dem Label ableiten $i18n = app_i18n_make_anchor_key($labelTrim); } elseif (is_array($value)) { // Explizite Variante: // "how": { "label": "...", "i18n": "nav_how" } if (!empty($value['label'])) { $label = trim((string)$value['label']); } if (!empty($value['i18n'])) { $i18n = trim((string)$value['i18n']); } if ($label === '' && $i18n === '') { continue; } // Wenn Label gesetzt, aber kein i18n: automatisch generieren if ($label !== '' && $i18n === '') { $i18n = app_i18n_make_anchor_key($label); } } else { // Weder String noch Array → ignorieren continue; } $anchors[] = [ 'href' => $href, 'label' => $label, 'i18n' => $i18n, ]; } return $anchors; }