i18n: EN-Lagen-Seiten + zweisprachiges Kontaktformular (Phase 3+4)

Phase 3 - Englische Lagebild-Seiten:
- /en/situations/iran-conflict/, /en/situations/cyber-attacks/,
  /en/situations/deepfakes/ erstellt (Mirror der DE-Lagen mit
  englischer UI)
- lagebild.js: curLang() liest jetzt direkt <html lang>; neuer
  dataBase()-Helper, damit EN-Seiten die JSON-Daten aus dem
  DE-Pfad nachladen koennen (window.LAGEBILD_DATA_BASE pro Seite)
- 4 zuvor hardcodierte DE-Strings (emptyDevelopments, emptySummary,
  Quelle-Tooltip, Schliessen-Aria) ueber t() und das vorhandene
  lang.de/lang.en-Dictionary uebersetzt
- DE-Lagen-Seiten: hreflang-Tags wieder aktiv, Toggle zeigt nun
  korrekt auf das EN-Pendant statt /en/
- en/index.html Karussell-Buttons zeigen auf EN-Lagen
- Sitemap mit hreflang-Alternativen fuer alle Lagen ergaenzt

Phase 4 - Kontaktformular zweisprachig (Frontend):
- js/app.js submitContact() liest <html lang>, sendet lang im POST
  und zeigt Sende-/Fehler-Texte in der jeweiligen Sprache
- Backend (contact-form.py) wird separat ausgerollt, ist aber
  abwaertskompatibel: bei fehlendem lang-Param defaultet es auf de

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
2026-05-06 23:28:05 +02:00
Ursprung 8c8130509a
Commit 645fb33898
10 geänderte Dateien mit 691 neuen und 26 gelöschten Zeilen

Datei anzeigen

@@ -5,6 +5,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lagebild: Cyberangriffe auf deutsche Infrastruktur - AegisSight</title>
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet, noimageindex">
<link rel="alternate" hreflang="de" href="https://aegis-sight.de/lagen/cyberangriffe/">
<link rel="alternate" hreflang="en" href="https://aegis-sight.de/en/situations/cyber-attacks/">
<link rel="alternate" hreflang="x-default" href="https://aegis-sight.de/lagen/cyberangriffe/">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/fonts.css">
@@ -33,7 +36,7 @@
<div class="lang-switcher" role="group" aria-label="Sprache">
<span class="lang-active" lang="de" aria-current="true">DE</span>
<span class="lang-sep" aria-hidden="true">|</span>
<a class="lang-link" href="/en/" lang="en" hreflang="en" rel="alternate">EN</a>
<a class="lang-link" href="/en/situations/cyber-attacks/" lang="en" hreflang="en" rel="alternate">EN</a>
</div>
<button class="mobile-menu-toggle" aria-label="Menü öffnen" aria-expanded="false">
<span class="hamburger"><span></span><span></span><span></span></span>
@@ -54,7 +57,7 @@
<div class="lang-switcher" role="group" aria-label="Sprache">
<span class="lang-active" lang="de" aria-current="true">DE</span>
<span class="lang-sep" aria-hidden="true">|</span>
<a class="lang-link" href="/en/" lang="en" hreflang="en" rel="alternate">EN</a>
<a class="lang-link" href="/en/situations/cyber-attacks/" lang="en" hreflang="en" rel="alternate">EN</a>
</div>
</div>
<div class="mobile-menu-overlay"></div>

Datei anzeigen

@@ -5,6 +5,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Recherche: Rechtliche Lage von Deepfakes in Deutschland - AegisSight</title>
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet, noimageindex">
<link rel="alternate" hreflang="de" href="https://aegis-sight.de/lagen/deepfakes/">
<link rel="alternate" hreflang="en" href="https://aegis-sight.de/en/situations/deepfakes/">
<link rel="alternate" hreflang="x-default" href="https://aegis-sight.de/lagen/deepfakes/">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/fonts.css">
@@ -36,7 +39,7 @@
<div class="lang-switcher" role="group" aria-label="Sprache">
<span class="lang-active" lang="de" aria-current="true">DE</span>
<span class="lang-sep" aria-hidden="true">|</span>
<a class="lang-link" href="/en/" lang="en" hreflang="en" rel="alternate">EN</a>
<a class="lang-link" href="/en/situations/deepfakes/" lang="en" hreflang="en" rel="alternate">EN</a>
</div>
<button class="mobile-menu-toggle" aria-label="Menü öffnen" aria-expanded="false">
<span class="hamburger"><span></span><span></span><span></span></span>
@@ -59,7 +62,7 @@
<div class="lang-switcher" role="group" aria-label="Sprache">
<span class="lang-active" lang="de" aria-current="true">DE</span>
<span class="lang-sep" aria-hidden="true">|</span>
<a class="lang-link" href="/en/" lang="en" hreflang="en" rel="alternate">EN</a>
<a class="lang-link" href="/en/situations/deepfakes/" lang="en" hreflang="en" rel="alternate">EN</a>
</div>
</div>
<div class="mobile-menu-overlay"></div>

Datei anzeigen

@@ -5,6 +5,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lagebild Irankonflikt - AegisSight</title>
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet, noimageindex">
<link rel="alternate" hreflang="de" href="https://aegis-sight.de/lagen/iran-konflikt/">
<link rel="alternate" hreflang="en" href="https://aegis-sight.de/en/situations/iran-conflict/">
<link rel="alternate" hreflang="x-default" href="https://aegis-sight.de/lagen/iran-konflikt/">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/fonts.css">
@@ -36,7 +39,7 @@
<div class="lang-switcher" role="group" aria-label="Sprache">
<span class="lang-active" lang="de" aria-current="true">DE</span>
<span class="lang-sep" aria-hidden="true">|</span>
<a class="lang-link" href="/en/" lang="en" hreflang="en" rel="alternate">EN</a>
<a class="lang-link" href="/en/situations/iran-conflict/" lang="en" hreflang="en" rel="alternate">EN</a>
</div>
<button class="mobile-menu-toggle" aria-label="Menü öffnen" aria-expanded="false">
<span class="hamburger"><span></span><span></span><span></span></span>
@@ -59,7 +62,7 @@
<div class="lang-switcher" role="group" aria-label="Sprache">
<span class="lang-active" lang="de" aria-current="true">DE</span>
<span class="lang-sep" aria-hidden="true">|</span>
<a class="lang-link" href="/en/" lang="en" hreflang="en" rel="alternate">EN</a>
<a class="lang-link" href="/en/situations/iran-conflict/" lang="en" hreflang="en" rel="alternate">EN</a>
</div>
</div>
<div class="mobile-menu-overlay"></div>

Datei anzeigen

@@ -41,6 +41,8 @@ var Lagebild = {
sourceRef: "Quelle",
lastUpdate: "Letzte Aktualisierung: ",
minAgo: "vor {n} Min", hrsAgo: "vor {n} Std",
emptyDevelopments: "Noch keine Entwicklungen erfasst.",
emptySummary: "Keine Zusammenfassung verfügbar.",
},
en: {
hero: "SITUATION REPORT", heroResearch: "RESEARCH BRIEFING",
@@ -67,11 +69,22 @@ var Lagebild = {
sourceRef: "Source",
lastUpdate: "Last update: ",
minAgo: "{n} min ago", hrsAgo: "{n} hrs ago",
emptyDevelopments: "No developments recorded yet.",
emptySummary: "No summary available.",
}
},
curLang: function() {
return (typeof getCurrentLanguage === 'function') ? getCurrentLanguage() : 'de';
var l = (document.documentElement.lang || 'de').toLowerCase();
return l.indexOf('en') === 0 ? 'en' : 'de';
},
/* Where to fetch data/current.json from. Defaults to relative ("data/")
which works on the German pages. English mirror pages set
window.LAGEBILD_DATA_BASE to the absolute path of the German page so
both languages share the same data files. */
dataBase: function() {
return (typeof window !== 'undefined' && window.LAGEBILD_DATA_BASE) || '';
},
t: function(key) {
@@ -103,10 +116,11 @@ var Lagebild = {
this.initScrollProgress();
this.initParticles();
try {
var savedLang = (typeof getCurrentLanguage === 'function') ? getCurrentLanguage() : 'de';
var jsonFile = savedLang === 'en' ? 'data/current_en.json' : 'data/current.json';
var savedLang = this.curLang();
var base = this.dataBase();
var jsonFile = base + (savedLang === 'en' ? 'data/current_en.json' : 'data/current.json');
var resp = await fetch(jsonFile + '?t=' + Date.now());
if (!resp.ok && savedLang === 'en') { resp = await fetch('data/current.json?t=' + Date.now()); }
if (!resp.ok && savedLang === 'en') { resp = await fetch(base + 'data/current.json?t=' + Date.now()); }
if (!resp.ok) throw new Error('HTTP ' + resp.status);
this.data = await resp.json();
this.currentView = {
@@ -150,12 +164,12 @@ var Lagebild = {
var dev = inc.latest_developments || '';
var sources = (this.currentView && this.currentView.sources_json) || [];
var html = this.renderLatestDevelopmentsHtml(dev, sources);
el.innerHTML = html || '<p class="empty-hint">Noch keine Entwicklungen erfasst.</p>';
el.innerHTML = html || '<p class="empty-hint">' + this.t('emptyDevelopments') + '</p>';
} else {
var md = (this.currentView && this.currentView.summary) || '';
var zf = this.extractZusammenfassung(md);
if (!zf) {
el.innerHTML = '<p class="empty-hint">Keine Zusammenfassung verf&uuml;gbar.</p>';
el.innerHTML = '<p class="empty-hint">' + this.t('emptySummary') + '</p>';
return;
}
var body = this.mdToHtml(this.fixUmlauts(zf));
@@ -168,7 +182,7 @@ var Lagebild = {
if (src && src.url) {
return '<a class="citation-ref" href="' + self.esc(src.url) + '" target="_blank" rel="noopener" title="' + self.esc(src.name || '') + '">[' + nr + ']</a>';
}
return '<a class="citation-ref" title="Quelle ' + nr + '">[' + nr + ']</a>';
return '<a class="citation-ref" title="' + self.t('sourceRef') + ' ' + nr + '">[' + nr + ']</a>';
});
el.innerHTML = body;
}
@@ -638,7 +652,7 @@ var Lagebild = {
return;
}
try {
var resp = await fetch('data/snapshot-' + id + '.json');
var resp = await fetch(this.dataBase() + 'data/snapshot-' + id + '.json');
if (!resp.ok) throw new Error('HTTP ' + resp.status);
var sd = await resp.json();
var sj = sd.sources_json;
@@ -1214,11 +1228,12 @@ var Lagebild = {
},
switchContent: async function(lang) {
var jsonFile = lang === 'en' ? 'data/current_en.json' : 'data/current.json';
var base = this.dataBase();
var jsonFile = base + (lang === 'en' ? 'data/current_en.json' : 'data/current.json');
try {
var resp = await fetch(jsonFile + '?t=' + Date.now());
if (!resp.ok && lang === 'en') {
resp = await fetch('data/current.json?t=' + Date.now());
resp = await fetch(base + 'data/current.json?t=' + Date.now());
}
if (!resp.ok) throw new Error('HTTP ' + resp.status);
this.data = await resp.json();
@@ -1267,7 +1282,7 @@ var Lagebild = {
cta.className = 'floating-cta';
cta.innerHTML = '<span class="floating-cta-text">' + this.t('ctaText') + '</span>'
+ '<a href="mailto:info@aegis-sight.de" class="floating-cta-btn">' + this.t('ctaButton') + '</a>'
+ '<button class="floating-cta-close" aria-label="Schlie\u00dfen">&times;</button>';
+ '<button class="floating-cta-close" aria-label="' + this.t('srcClose') + '">&times;</button>';
document.body.appendChild(cta);
// Show after scrolling past hero