i18n: Complete DE/EN language switcher integration

- Add LangManager with 270+ translation keys, anti-flicker lang detection
- Replace all hardcoded German strings in app.js, components.js, dashboard.html, index.html
- Dynamic getter properties for fact-check labels, category badges
- Language-aware map tiles (DE/EN OSM servers), CSP updated for tile.openstreetmap.org
- Lang switcher in header bar and login page
- Locale-aware date formatting, translateApiError for backend messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-03-05 16:13:11 +01:00
Ursprung 1644f8786c
Commit 44997d511b
7 geänderte Dateien mit 422 neuen und 362 gelöschten Zeilen

Datei anzeigen

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>(function(){var t=localStorage.getItem('osint_theme');if(t)document.documentElement.setAttribute('data-theme',t);try{var a=JSON.parse(localStorage.getItem('osint_a11y')||'{}');Object.keys(a).forEach(function(k){if(a[k])document.documentElement.setAttribute('data-a11y-'+k,'true');});}catch(e){}})()</script>
<script>(function(){var t=localStorage.getItem('osint_theme');if(t)document.documentElement.setAttribute('data-theme',t);try{var a=JSON.parse(localStorage.getItem('osint_a11y')||'{}');Object.keys(a).forEach(function(k){if(a[k])document.documentElement.setAttribute('data-a11y-'+k,'true');});}catch(e){}var l=localStorage.getItem('osint_lang')||'de';document.documentElement.lang=l;})()</script>
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png">
@@ -12,15 +12,16 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css?v=20260304a">
<link rel="stylesheet" href="/static/css/style.css?v=20260305a">
<script src="/static/js/lang.js?v=20260305a"></script>
</head>
<body>
<a href="#login-form" class="skip-link">Zum Anmeldeformular springen</a>
<a href="#login-form" class="skip-link" data-i18n="login.skip_link">Zum Anmeldeformular springen</a>
<main class="login-container">
<div class="login-box">
<div class="login-logo">
<h1>Aegis<span style="color: var(--accent)">Sight</span></h1>
<div class="subtitle">Lagemonitor</div>
<div class="subtitle" data-i18n="login.subtitle">Lagemonitor</div>
</div>
<div id="login-error" class="login-error" role="alert" aria-live="assertive"></div>
@@ -29,29 +30,34 @@
<!-- Schritt 1: E-Mail eingeben -->
<form id="email-form">
<div class="form-group">
<label for="email">E-Mail-Adresse</label>
<input type="email" id="email" name="email" autocomplete="email" required aria-required="true" placeholder="name@organisation.de">
<label for="email" data-i18n="login.email_label">E-Mail-Adresse</label>
<input type="email" id="email" name="email" autocomplete="email" required aria-required="true" placeholder="name@organisation.de" data-i18n-placeholder="login.email_placeholder">
</div>
<button type="submit" class="btn btn-primary btn-full" id="email-btn">Anmelden</button>
<button type="submit" class="btn btn-primary btn-full" id="email-btn" data-i18n="login.submit">Anmelden</button>
</form>
<!-- Schritt 2: Code eingeben -->
<form id="code-form" style="display:none;">
<p style="color: var(--text-secondary); margin: 0 0 16px 0; font-size: 14px;">
<p id="code-sent-text" style="color: var(--text-secondary); margin: 0 0 16px 0; font-size: 14px;">
Ein 6-stelliger Code wurde an <strong id="sent-email"></strong> gesendet.
</p>
<div class="form-group">
<label for="code">Code eingeben</label>
<label for="code" data-i18n="login.code_label">Code eingeben</label>
<input type="text" id="code" name="code" autocomplete="one-time-code" required aria-required="true"
placeholder="000000" maxlength="6" pattern="[0-9]{6}"
style="text-align:center; font-size:24px; letter-spacing:8px; font-family:monospace;">
</div>
<button type="submit" class="btn btn-primary btn-full" id="code-btn">Verifizieren</button>
<button type="button" class="btn btn-secondary btn-full" id="back-btn" style="margin-top:8px;">Zurück</button>
<button type="submit" class="btn btn-primary btn-full" id="code-btn" data-i18n="login.verify">Verifizieren</button>
<button type="button" class="btn btn-secondary btn-full" id="back-btn" style="margin-top:8px;" data-i18n="login.back">Zurück</button>
</form>
<div style="text-align:center;margin-top:16px;">
<button class="btn btn-secondary btn-small theme-toggle-btn" id="theme-toggle" onclick="ThemeManager.toggle()" title="Theme wechseln" aria-label="Theme wechseln">&#9788;</button>
<div class="lang-switcher" id="lang-switcher" style="display:inline-flex;margin-bottom:8px;">
<button class="lang-btn" data-lang="de" onclick="LangManager.setLang('de')" title="Deutsch">DE</button>
<button class="lang-btn" data-lang="en" onclick="LangManager.setLang('en')" title="English">EN</button>
</div>
<br>
<button class="btn btn-secondary btn-small theme-toggle-btn" id="theme-toggle" onclick="ThemeManager.toggle()" data-i18n-title="header.theme_toggle" title="Theme wechseln" aria-label="Theme wechseln">&#9788;</button>
</div>
</div>
</main>
@@ -132,7 +138,7 @@
errorEl.style.display = 'none';
successEl.style.display = 'none';
btn.disabled = true;
btn.textContent = 'Wird gesendet...';
btn.textContent = LangManager.t('login.sending');
currentEmail = document.getElementById('email').value.trim();
@@ -159,7 +165,7 @@
errorEl.style.display = 'block';
} finally {
btn.disabled = false;
btn.textContent = 'Anmelden';
btn.textContent = LangManager.t('login.submit');
}
});
@@ -170,7 +176,7 @@
const btn = document.getElementById('code-btn');
errorEl.style.display = 'none';
btn.disabled = true;
btn.textContent = 'Wird geprüft...';
btn.textContent = LangManager.t('login.verifying');
try {
const response = await fetch('/api/auth/verify-code', {
@@ -196,7 +202,7 @@
errorEl.style.display = 'block';
} finally {
btn.disabled = false;
btn.textContent = 'Verifizieren';
btn.textContent = LangManager.t('login.verify');
}
});