lang
This commit is contained in:
@@ -24,233 +24,14 @@ if (php_sapi_name() !== 'cli') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------
|
require_once __DIR__ . '/i18n.php'; // <— NEU: zentrale Sprachlogik
|
||||||
// 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);
|
|
||||||
|
|
||||||
// 2) optional auch ins PHP error_log
|
// ab hier kannst du überall $GLOBALS['lang'] und $GLOBALS['availableLangs'] nutzen
|
||||||
@error_log('[i18n] ' . $msg);
|
// und für JS:
|
||||||
|
$usbConfig = [
|
||||||
// 3) Bei ?debug_i18n=1 zusätzlich HTML-Kommentar ausgeben
|
// ... dein sonstiges Zeug ...
|
||||||
if (isset($_GET['debug_i18n']) && $_GET['debug_i18n'] == '1') {
|
'i18n' => app_i18n_get_frontend_config(),
|
||||||
echo "<!-- [i18n] " . htmlspecialchars($msg, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . " -->\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,
|
|
||||||
];
|
|
||||||
|
|
||||||
// -----------------------------------------------------------
|
// -----------------------------------------------------------
|
||||||
// 7) Rest des Systems laden
|
// 7) Rest des Systems laden
|
||||||
// -----------------------------------------------------------
|
// -----------------------------------------------------------
|
||||||
|
|||||||
234
config/i18n.php
Normal file
234
config/i18n.php
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
<?php
|
||||||
|
// config/i18n.php
|
||||||
|
// Zentrale Sprachlogik für das gesamte Projekt
|
||||||
|
|
||||||
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
|
@session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liest die meta-Infos einer Sprachdatei.
|
||||||
|
* Erwartet Struktur:
|
||||||
|
* {
|
||||||
|
* "meta": { "code": "de", "label": "Deutsch", "flag": "🇩🇪", "enabled": true },
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Gibt NULL zurück, wenn:
|
||||||
|
* - Datei nicht lesbar
|
||||||
|
* - JSON ungültig
|
||||||
|
* - kein meta vorhanden
|
||||||
|
* - meta.enabled existiert und NICHT true ist
|
||||||
|
*/
|
||||||
|
function app_i18n_load_language_meta_from_file(string $file): ?array
|
||||||
|
{
|
||||||
|
$json = @file_get_contents($file);
|
||||||
|
if ($json === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($json, true);
|
||||||
|
if (!is_array($data)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$meta = $data['meta'] ?? null;
|
||||||
|
if (!is_array($meta)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nur aktive Sprachen (enabled === true)
|
||||||
|
if (array_key_exists('enabled', $meta) && !$meta['enabled']) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code aus meta, Fallback: Dateiname
|
||||||
|
$code = '';
|
||||||
|
if (!empty($meta['code'])) {
|
||||||
|
$code = strtolower(substr((string)$meta['code'], 0, 5));
|
||||||
|
} else {
|
||||||
|
$base = basename($file, '.json');
|
||||||
|
$code = strtolower($base);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auf 2-Buchstaben-Codes normalisieren (de-DE → de)
|
||||||
|
if (strlen($code) > 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'] ?? [],
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,15 +1,5 @@
|
|||||||
// public/assets/js/header.js
|
// public/assets/js/header.js
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
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 cfg = window.usbConfig || {};
|
||||||
const i18nCfg = cfg.i18n || {};
|
const i18nCfg = cfg.i18n || {};
|
||||||
const availLangs = i18nCfg.available || {};
|
const availLangs = i18nCfg.available || {};
|
||||||
@@ -47,43 +37,30 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
const urlLang = normalizeLang(url.searchParams.get("lang"));
|
const urlLang = normalizeLang(url.searchParams.get("lang"));
|
||||||
|
|
||||||
// a) ?lang= aus der URL, wenn gültig
|
// 1) ?lang
|
||||||
if (urlLang && availCodes.includes(urlLang)) {
|
if (urlLang && availCodes.includes(urlLang)) {
|
||||||
return urlLang;
|
return urlLang;
|
||||||
}
|
}
|
||||||
|
|
||||||
// b) localStorage (vom lang.js gesetzt)
|
// 2) Browsersprache
|
||||||
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
|
|
||||||
const browserLang = detectBrowserLang();
|
const browserLang = detectBrowserLang();
|
||||||
if (browserLang) {
|
if (browserLang) {
|
||||||
return browserLang;
|
return browserLang;
|
||||||
}
|
}
|
||||||
|
|
||||||
// d) Fallback: en, wenn vorhanden
|
// 3) EN
|
||||||
if (availCodes.includes("en")) {
|
if (availCodes.includes("en")) {
|
||||||
return "en";
|
return "en";
|
||||||
}
|
}
|
||||||
|
|
||||||
// e) Sonst erste verfügbare Sprache
|
|
||||||
if (availCodes.length > 0) {
|
if (availCodes.length > 0) {
|
||||||
return availCodes[0];
|
return availCodes[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// f) absolute Notlösung
|
|
||||||
return "en";
|
return "en";
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// LOGIN-BUTTON
|
||||||
// LOGIN-BUTTON → /login mit redirect + lang
|
|
||||||
// --------------------------------------------------
|
|
||||||
const loginBtn = document.getElementById("loginButton");
|
const loginBtn = document.getElementById("loginButton");
|
||||||
|
|
||||||
if (loginBtn) {
|
if (loginBtn) {
|
||||||
@@ -96,7 +73,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
|
|
||||||
const lang = resolveCurrentLang();
|
const lang = resolveCurrentLang();
|
||||||
|
|
||||||
// Wenn wir bereits auf /login sind → nur zum #auth scrollen
|
|
||||||
if (currentPath === "/login" || currentPath === "/login/") {
|
if (currentPath === "/login" || currentPath === "/login/") {
|
||||||
const authEl = document.getElementById("auth");
|
const authEl = document.getElementById("auth");
|
||||||
if (authEl) {
|
if (authEl) {
|
||||||
@@ -105,7 +81,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sonst: auf Login-Seite mit lang + redirect-Parameter
|
|
||||||
const url = "/login/?lang=" + encodeURIComponent(lang) +
|
const url = "/login/?lang=" + encodeURIComponent(lang) +
|
||||||
"&redirect=" + encodeURIComponent(redirect) +
|
"&redirect=" + encodeURIComponent(redirect) +
|
||||||
"#auth";
|
"#auth";
|
||||||
@@ -114,9 +89,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// AVATAR-MENÜ & LOGOUT wie gehabt (deine bestehende Logik)
|
||||||
// AVATAR-MENÜ (userAvatar / userMenu)
|
|
||||||
// --------------------------------------------------
|
|
||||||
const avatarBtn = document.getElementById("userAvatar");
|
const avatarBtn = document.getElementById("userAvatar");
|
||||||
const userMenu = document.getElementById("userMenu");
|
const userMenu = document.getElementById("userMenu");
|
||||||
|
|
||||||
@@ -129,7 +102,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
avatarBtn.setAttribute("aria-expanded", expanded ? "false" : "true");
|
avatarBtn.setAttribute("aria-expanded", expanded ? "false" : "true");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Klick außerhalb schließt Menü
|
|
||||||
document.addEventListener("click", function (event) {
|
document.addEventListener("click", function (event) {
|
||||||
if (userMenu.classList.contains("hidden")) {
|
if (userMenu.classList.contains("hidden")) {
|
||||||
return;
|
return;
|
||||||
@@ -145,9 +117,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// LOGOUT-MODAL
|
|
||||||
// --------------------------------------------------
|
|
||||||
const logoutButtons = document.querySelectorAll('[data-logout-link="true"]');
|
const logoutButtons = document.querySelectorAll('[data-logout-link="true"]');
|
||||||
const backdrop = document.getElementById("logoutModalBackdrop");
|
const backdrop = document.getElementById("logoutModalBackdrop");
|
||||||
const btnConfirm = document.getElementById("logoutConfirm");
|
const btnConfirm = document.getElementById("logoutConfirm");
|
||||||
@@ -182,7 +151,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
|
|
||||||
showLogoutModal(href);
|
showLogoutModal(href);
|
||||||
|
|
||||||
// Avatar-Menü beim Öffnen des Modals schließen
|
|
||||||
if (userMenu && !userMenu.classList.contains("hidden")) {
|
if (userMenu && !userMenu.classList.contains("hidden")) {
|
||||||
userMenu.classList.add("hidden");
|
userMenu.classList.add("hidden");
|
||||||
if (avatarBtn) {
|
if (avatarBtn) {
|
||||||
@@ -192,7 +160,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bestätigen → weiter zur Logout-URL
|
|
||||||
btnConfirm.addEventListener("click", function () {
|
btnConfirm.addEventListener("click", function () {
|
||||||
if (logoutTarget) {
|
if (logoutTarget) {
|
||||||
window.location.href = logoutTarget;
|
window.location.href = logoutTarget;
|
||||||
@@ -201,12 +168,10 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Abbrechen → Modal schließen
|
|
||||||
btnCancel.addEventListener("click", function () {
|
btnCancel.addEventListener("click", function () {
|
||||||
hideLogoutModal();
|
hideLogoutModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Klick auf Hintergrund schließt Modal (außer man klickt auf die Card)
|
|
||||||
backdrop.addEventListener("click", function (event) {
|
backdrop.addEventListener("click", function (event) {
|
||||||
const card = backdrop.querySelector(".max-w-sm");
|
const card = backdrop.querySelector(".max-w-sm");
|
||||||
if (card && !card.contains(event.target)) {
|
if (card && !card.contains(event.target)) {
|
||||||
@@ -214,7 +179,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ESC-Taste schließt Modal
|
|
||||||
document.addEventListener("keydown", function (event) {
|
document.addEventListener("keydown", function (event) {
|
||||||
if (event.key === "Escape" && !backdrop.classList.contains("hidden")) {
|
if (event.key === "Escape" && !backdrop.classList.contains("hidden")) {
|
||||||
hideLogoutModal();
|
hideLogoutModal();
|
||||||
|
|||||||
@@ -1,26 +1,14 @@
|
|||||||
// public/assets/js/lang.js
|
// public/assets/js/lang.js
|
||||||
(function () {
|
(function () {
|
||||||
// ---------------------------------------------
|
|
||||||
// 1) Basis-Config aus PHP (app_config.php)
|
|
||||||
// ---------------------------------------------
|
|
||||||
const usbConfig = window.usbConfig || {};
|
const usbConfig = window.usbConfig || {};
|
||||||
const i18nConfig = usbConfig.i18n || {};
|
const i18nConfig = usbConfig.i18n || {};
|
||||||
|
|
||||||
// Map: { de: {code, label, flag}, en: {...}, ... }
|
|
||||||
const availableLangs = i18nConfig.available || {};
|
const availableLangs = i18nConfig.available || {};
|
||||||
|
|
||||||
// Liste der unterstützten Sprachen als Codes
|
|
||||||
const supportedLangs = Object.keys(availableLangs);
|
const supportedLangs = Object.keys(availableLangs);
|
||||||
|
|
||||||
// Aktuelle Sprache (wird unten per getInitialLang() gesetzt)
|
|
||||||
let currentLang = "en";
|
let currentLang = "en";
|
||||||
|
|
||||||
// Translations-Map (geflacht)
|
|
||||||
let translations = {};
|
let translations = {};
|
||||||
|
|
||||||
// ---------------------------------------------
|
|
||||||
// 2) Helper: Translations flatten
|
|
||||||
// ---------------------------------------------
|
|
||||||
function flattenTranslations(obj, target = {}) {
|
function flattenTranslations(obj, target = {}) {
|
||||||
Object.keys(obj || {}).forEach((key) => {
|
Object.keys(obj || {}).forEach((key) => {
|
||||||
const val = obj[key];
|
const val = obj[key];
|
||||||
@@ -33,13 +21,9 @@
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------
|
|
||||||
// 3) Domain-Platzhalter ersetzen
|
|
||||||
// ---------------------------------------------
|
|
||||||
function applyDomainPlaceholders(text) {
|
function applyDomainPlaceholders(text) {
|
||||||
if (typeof text !== "string") return text;
|
if (typeof text !== "string") return text;
|
||||||
|
|
||||||
// Primär aus usbConfig.domains, Fallback auf window.appDomains
|
|
||||||
const domains = usbConfig.domains || window.appDomains || {};
|
const domains = usbConfig.domains || window.appDomains || {};
|
||||||
const replacements = {
|
const replacements = {
|
||||||
"{{primary_domain}}": domains.primaryDomain || "usbcheck.it",
|
"{{primary_domain}}": domains.primaryDomain || "usbcheck.it",
|
||||||
@@ -58,9 +42,6 @@
|
|||||||
}, text);
|
}, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------
|
|
||||||
// 4) Browsersprache erkennen (2-Buchstaben-Code)
|
|
||||||
// ------------------------------------------------
|
|
||||||
function detectBrowserLang() {
|
function detectBrowserLang() {
|
||||||
const nav = window.navigator || {};
|
const nav = window.navigator || {};
|
||||||
const candidates = [];
|
const candidates = [];
|
||||||
@@ -84,58 +65,38 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------
|
// *** Deine Priorität:
|
||||||
// 5) Initiale Sprache bestimmen
|
// 1) ?lang
|
||||||
//
|
// 2) Browser
|
||||||
// Priorität:
|
// 3) en
|
||||||
// a) URL ?lang= (falls gültig)
|
|
||||||
// b) localStorage (explizit gewählte Sprache)
|
|
||||||
// c) Browsersprache
|
|
||||||
// d) 'en', wenn vorhanden
|
|
||||||
// e) erste verfügbare Sprache
|
|
||||||
// ---------------------------------------------
|
|
||||||
function getInitialLang() {
|
function getInitialLang() {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const paramLang = (urlParams.get("lang") || "").toLowerCase();
|
const paramLang = (urlParams.get("lang") || "").toLowerCase();
|
||||||
|
|
||||||
// a) URL-Parameter, wenn gültig
|
// 1) URL-Parameter
|
||||||
if (paramLang && supportedLangs.includes(paramLang)) {
|
if (paramLang && supportedLangs.includes(paramLang)) {
|
||||||
return paramLang;
|
return paramLang;
|
||||||
}
|
}
|
||||||
|
|
||||||
// b) localStorage (vom User über Switcher gewählt)
|
// 2) Browsersprache
|
||||||
const stored = (localStorage.getItem("usbcheck_lang") || "").toLowerCase();
|
const browser = detectBrowserLang();
|
||||||
if (stored) {
|
if (browser) {
|
||||||
if (supportedLangs.includes(stored)) {
|
return browser;
|
||||||
return stored;
|
|
||||||
}
|
|
||||||
// Ungültigen alten Wert aufräumen
|
|
||||||
localStorage.removeItem("usbcheck_lang");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// c) Browsersprache
|
// 3) EN (immer vorhanden laut deiner Vorgabe)
|
||||||
const browserLang = detectBrowserLang();
|
|
||||||
if (browserLang) {
|
|
||||||
return browserLang;
|
|
||||||
}
|
|
||||||
|
|
||||||
// d) Wenn 'en' existiert → nimm 'en'
|
|
||||||
if (supportedLangs.includes("en")) {
|
if (supportedLangs.includes("en")) {
|
||||||
return "en";
|
return "en";
|
||||||
}
|
}
|
||||||
|
|
||||||
// e) Sonst erste verfügbare Sprache
|
// Sicherheitsfallback
|
||||||
if (supportedLangs.length > 0) {
|
if (supportedLangs.length > 0) {
|
||||||
return supportedLangs[0];
|
return supportedLangs[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Worst-Case (sollte nie eintreten)
|
|
||||||
return "en";
|
return "en";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------
|
|
||||||
// 6) i18n JSON per Fetch laden
|
|
||||||
// ---------------------------------------------
|
|
||||||
async function loadLangFile(lang) {
|
async function loadLangFile(lang) {
|
||||||
const code = (lang || "").toLowerCase();
|
const code = (lang || "").toLowerCase();
|
||||||
if (!supportedLangs.includes(code)) {
|
if (!supportedLangs.includes(code)) {
|
||||||
@@ -145,9 +106,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/assets/i18n/${code}.json`, {
|
const res = await fetch(`/assets/i18n/${code}.json`, { cache: "no-store" });
|
||||||
cache: "no-store",
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error(`Failed to load /assets/i18n/${code}.json`);
|
if (!res.ok) throw new Error(`Failed to load /assets/i18n/${code}.json`);
|
||||||
const raw = await res.json();
|
const raw = await res.json();
|
||||||
translations = flattenTranslations(raw);
|
translations = flattenTranslations(raw);
|
||||||
@@ -157,9 +116,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------
|
|
||||||
// 7) Data-i18n-Texte einsetzen
|
|
||||||
// ---------------------------------------------
|
|
||||||
function applyTranslations() {
|
function applyTranslations() {
|
||||||
document.documentElement.setAttribute("lang", currentLang);
|
document.documentElement.setAttribute("lang", currentLang);
|
||||||
|
|
||||||
@@ -169,21 +125,16 @@
|
|||||||
let value = translations[key];
|
let value = translations[key];
|
||||||
if (typeof value !== "string") return;
|
if (typeof value !== "string") return;
|
||||||
|
|
||||||
// {year}-Platzhalter ersetzen (z. B. footer_copy)
|
|
||||||
if (value.includes("{year}")) {
|
if (value.includes("{year}")) {
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
value = value.replace("{year}", year);
|
value = value.replace("{year}", year);
|
||||||
}
|
}
|
||||||
|
|
||||||
value = applyDomainPlaceholders(value);
|
value = applyDomainPlaceholders(value);
|
||||||
|
|
||||||
node.innerHTML = value;
|
node.innerHTML = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------
|
|
||||||
// 8) Label + Flag im Button aktualisieren
|
|
||||||
// ---------------------------------------------
|
|
||||||
function updateLangCurrentLabel(lang) {
|
function updateLangCurrentLabel(lang) {
|
||||||
const code = (lang || "").toLowerCase();
|
const code = (lang || "").toLowerCase();
|
||||||
|
|
||||||
@@ -208,14 +159,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------
|
// Wenn du später mal explizit via JS Sprache setzen willst
|
||||||
// 9) Sprache setzen (via Switcher)
|
// (z.B. beim Klick auf ein eigenes UI-Element), kannst du:
|
||||||
//
|
|
||||||
// - akzeptiert nur Sprachen aus availableLangs
|
|
||||||
// - speichert in localStorage
|
|
||||||
// - schreibt ?lang= in die URL
|
|
||||||
// - lädt Seite neu, damit PHP + JS synchron sind
|
|
||||||
// ---------------------------------------------
|
|
||||||
function setLang(lang) {
|
function setLang(lang) {
|
||||||
const code = (lang || "").toLowerCase();
|
const code = (lang || "").toLowerCase();
|
||||||
if (!supportedLangs.includes(code)) {
|
if (!supportedLangs.includes(code)) {
|
||||||
@@ -225,20 +170,13 @@
|
|||||||
|
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.set("lang", code);
|
url.searchParams.set("lang", code);
|
||||||
localStorage.setItem("usbcheck_lang", code);
|
|
||||||
|
|
||||||
window.location.href = url.toString();
|
window.location.href = url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------
|
|
||||||
// 10) DOM Ready
|
|
||||||
// ---------------------------------------------
|
|
||||||
document.addEventListener("DOMContentLoaded", async function () {
|
document.addEventListener("DOMContentLoaded", async function () {
|
||||||
// Initiale Sprache (kombiniert aus URL, localStorage, Browser)
|
|
||||||
currentLang = getInitialLang();
|
currentLang = getInitialLang();
|
||||||
updateLangCurrentLabel(currentLang);
|
updateLangCurrentLabel(currentLang);
|
||||||
|
|
||||||
// i18n JSON laden & Texte einsetzen
|
|
||||||
await loadLangFile(currentLang);
|
await loadLangFile(currentLang);
|
||||||
applyTranslations();
|
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) {
|
document.addEventListener("click", function (e) {
|
||||||
const btn = e.target.closest(".lang-pill");
|
const btn = e.target.closest(".lang-pill");
|
||||||
if (btn) {
|
if (!btn) return;
|
||||||
const lang = btn.getAttribute("data-lang");
|
|
||||||
if (lang) {
|
const targetLang = btn.getAttribute("data-lang");
|
||||||
// Nur wenn data-lang gesetzt ist → JS-gesteuerter Wechsel
|
if (!targetLang) return;
|
||||||
setLang(lang);
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
setLang(targetLang);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user