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">
@@ -19,7 +19,7 @@
<link rel="stylesheet" href="/static/css/style.css?v=20260304h">
</head>
<body>
<a href="#main-content" class="skip-link">Zum Hauptinhalt springen</a>
<a href="#main-content" class="skip-link" data-i18n="header.skip_link">Zum Hauptinhalt springen</a>
<div class="dashboard">
<!-- Header -->
<header class="header">
@@ -28,7 +28,11 @@
<h1 class="sr-only">AegisSight Monitor Dashboard</h1>
</div>
<div class="header-right">
<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">
<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>
<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 class="header-user-info">
<div class="header-user-top">
<span class="header-user" id="header-user"></span>
@@ -37,25 +41,25 @@
<span class="header-org-name" id="header-org-name"></span>
</div>
<div class="header-license-warning" id="header-license-warning"></div>
<button class="btn btn-secondary btn-small" id="logout-btn">Abmelden</button>
<button class="btn btn-secondary btn-small" id="logout-btn" data-i18n="header.logout">Abmelden</button>
</div>
</header>
<!-- Sidebar -->
<nav class="sidebar" aria-label="Seitenleiste">
<div class="sidebar-section">
<button class="btn btn-primary btn-full btn-small" id="new-incident-btn">+ Neue Lage / Recherche</button>
<button class="btn btn-primary btn-full btn-small" id="new-incident-btn" data-i18n="nav.new_incident">+ Neue Lage / Recherche</button>
</div>
<div class="sidebar-filter">
<button class="sidebar-filter-btn active" data-filter="all" onclick="App.setSidebarFilter('all')" aria-pressed="true">Alle</button>
<button class="sidebar-filter-btn" data-filter="mine" onclick="App.setSidebarFilter('mine')" aria-pressed="false">Eigene</button>
<button class="sidebar-filter-btn active" data-filter="all" onclick="App.setSidebarFilter('all')" aria-pressed="true" data-i18n="nav.filter_all">Alle</button>
<button class="sidebar-filter-btn" data-filter="mine" onclick="App.setSidebarFilter('mine')" aria-pressed="false" data-i18n="nav.filter_mine">Eigene</button>
</div>
<div class="sidebar-section">
<h2 class="sidebar-section-title collapsible" onclick="App.toggleSidebarSection('active-incidents')" role="button" tabindex="0" aria-expanded="true">
<span class="sidebar-chevron" id="chevron-active-incidents" aria-hidden="true">&#9662;</span>
Aktive Lagen
<span data-i18n="nav.active_incidents">Aktive Lagen</span>
<span class="sidebar-section-count" id="count-active-incidents"></span>
</h2>
<div id="active-incidents" aria-live="polite"></div>
@@ -64,7 +68,7 @@
<div class="sidebar-section">
<h2 class="sidebar-section-title collapsible" onclick="App.toggleSidebarSection('active-research')" role="button" tabindex="0" aria-expanded="true">
<span class="sidebar-chevron" id="chevron-active-research" aria-hidden="true">&#9662;</span>
Aktive Recherchen
<span data-i18n="nav.active_research">Aktive Recherchen</span>
<span class="sidebar-section-count" id="count-active-research"></span>
</h2>
<div id="active-research" aria-live="polite"></div>
@@ -73,14 +77,14 @@
<div class="sidebar-section">
<h2 class="sidebar-section-title collapsible" onclick="App.toggleSidebarSection('archived-incidents')" role="button" tabindex="0" aria-expanded="false">
<span class="sidebar-chevron" id="chevron-archived-incidents" aria-hidden="true">&#9662;</span>
Archiv
<span data-i18n="nav.archive">Archiv</span>
<span class="sidebar-section-count" id="count-archived-incidents"></span>
</h2>
<div id="archived-incidents" aria-live="polite" style="display:none;"></div>
</div>
<div class="sidebar-sources-link">
<button class="btn btn-secondary btn-full btn-small" onclick="App.openSourceManagement()">Quellen verwalten</button>
<button class="btn btn-secondary btn-full btn-small sidebar-feedback-btn" onclick="App.openFeedback()">Feedback senden</button>
<button class="btn btn-secondary btn-full btn-small" onclick="App.openSourceManagement()" data-i18n="nav.manage_sources">Quellen verwalten</button>
<button class="btn btn-secondary btn-full btn-small sidebar-feedback-btn" onclick="App.openFeedback()" data-i18n="nav.send_feedback">Feedback senden</button>
<div class="sidebar-stats-mini">
<span id="stat-sources-count">0 Quellen</span> &middot; <span id="stat-articles-count">0 Artikel</span>
</div>
@@ -91,8 +95,8 @@
<main class="main-content" id="main-content">
<div class="empty-state" id="empty-state">
<div class="empty-state-icon">&#9737;</div>
<div class="empty-state-title">Kein Vorfall ausgewählt</div>
<div class="empty-state-text">Erstelle eine neue Lage oder wähle einen bestehenden Vorfall aus der Seitenleiste.</div>
<div class="empty-state-title" data-i18n="empty.no_incident">Kein Vorfall ausgewählt</div>
<div class="empty-state-text" data-i18n="empty.no_incident_text">Erstelle eine neue Lage oder wähle einen bestehenden Vorfall aus der Seitenleiste.</div>
</div>
<!-- Lagebild (hidden by default) -->
@@ -108,22 +112,22 @@
<h2 class="incident-header-title" id="incident-title"></h2>
</div>
<div class="incident-header-actions">
<button class="btn btn-primary btn-small" id="refresh-btn">Aktualisieren</button>
<button class="btn btn-secondary btn-small" id="edit-incident-btn">Bearbeiten</button>
<button class="btn btn-primary btn-small" id="refresh-btn" data-i18n="btn.refresh">Aktualisieren</button>
<button class="btn btn-secondary btn-small" id="edit-incident-btn" data-i18n="btn.edit">Bearbeiten</button>
<div class="export-dropdown" id="export-dropdown">
<button class="btn btn-secondary btn-small" onclick="App.toggleExportDropdown(event)" aria-expanded="false" aria-haspopup="true">Exportieren &#9662;</button>
<button class="btn btn-secondary btn-small" onclick="App.toggleExportDropdown(event)" aria-expanded="false" aria-haspopup="true" data-i18n="btn.export">Exportieren &#9662;</button>
<div class="export-dropdown-menu" id="export-dropdown-menu" role="menu">
<button class="export-dropdown-item" role="menuitem" onclick="App.exportIncident('md','report')">Lagebericht (Markdown)</button>
<button class="export-dropdown-item" role="menuitem" onclick="App.exportIncident('json','report')">Lagebericht (JSON)</button>
<button class="export-dropdown-item" role="menuitem" onclick="App.exportIncident('md','report')" data-i18n="export.report_md">Lagebericht (Markdown)</button>
<button class="export-dropdown-item" role="menuitem" onclick="App.exportIncident('json','report')" data-i18n="export.report_json">Lagebericht (JSON)</button>
<hr class="export-dropdown-divider" role="separator">
<button class="export-dropdown-item" role="menuitem" onclick="App.exportIncident('md','full')">Vollexport (Markdown)</button>
<button class="export-dropdown-item" role="menuitem" onclick="App.exportIncident('json','full')">Vollexport (JSON)</button>
<button class="export-dropdown-item" role="menuitem" onclick="App.exportIncident('md','full')" data-i18n="export.full_md">Vollexport (Markdown)</button>
<button class="export-dropdown-item" role="menuitem" onclick="App.exportIncident('json','full')" data-i18n="export.full_json">Vollexport (JSON)</button>
<hr class="export-dropdown-divider" role="separator">
<button class="export-dropdown-item" role="menuitem" onclick="App.printIncident()">Drucken / PDF</button>
<button class="export-dropdown-item" role="menuitem" onclick="App.printIncident()" data-i18n="export.print">Drucken / PDF</button>
</div>
</div>
<button class="btn btn-secondary btn-small" id="archive-incident-btn">Archivieren</button>
<button class="btn btn-danger btn-small" id="delete-incident-btn">Löschen</button>
<button class="btn btn-secondary btn-small" id="archive-incident-btn" data-i18n="btn.archive">Archivieren</button>
<button class="btn btn-danger btn-small" id="delete-incident-btn" data-i18n="btn.delete">Löschen</button>
</div>
</div>
<div class="incident-header-row2">
@@ -154,37 +158,37 @@
<div class="progress-steps">
<div class="progress-step" id="step-researching">
<div class="progress-step-dot"></div>
<span>Recherche</span>
<span data-i18n="progress.step_research">Recherche</span>
</div>
<div class="progress-step" id="step-analyzing">
<div class="progress-step-dot"></div>
<span>Analyse</span>
<span data-i18n="progress.step_analysis">Analyse</span>
</div>
<div class="progress-step" id="step-factchecking">
<div class="progress-step-dot"></div>
<span>Faktencheck</span>
<span data-i18n="progress.step_factcheck">Faktencheck</span>
</div>
</div>
<div class="progress-track">
<div class="progress-fill" id="progress-fill"></div>
</div>
<div class="progress-label-container">
<span id="progress-label" class="progress-label">Warte auf Start...</span>
<span id="progress-label" class="progress-label" data-i18n="progress.wait">Warte auf Start...</span>
<span id="progress-timer" class="progress-timer"></span>
</div>
<button id="progress-cancel-btn" class="progress-cancel-btn" onclick="App.cancelRefresh()">Abbrechen</button>
<button id="progress-cancel-btn" class="progress-cancel-btn" onclick="App.cancelRefresh()" data-i18n="btn.cancel">Abbrechen</button>
</div>
<!-- Layout-Toolbar -->
<div class="layout-toolbar" id="layout-toolbar" style="display:none;">
<div class="layout-toggles">
<button class="layout-toggle-btn active" data-tile="lagebild" onclick="LayoutManager.toggleTile('lagebild')" aria-pressed="true">Lagebild</button>
<button class="layout-toggle-btn active" data-tile="faktencheck" onclick="LayoutManager.toggleTile('faktencheck')" aria-pressed="true">Faktencheck</button>
<button class="layout-toggle-btn active" data-tile="quellen" onclick="LayoutManager.toggleTile('quellen')" aria-pressed="true">Quellen</button>
<button class="layout-toggle-btn active" data-tile="timeline" onclick="LayoutManager.toggleTile('timeline')" aria-pressed="true">Timeline</button>
<button class="layout-toggle-btn active" data-tile="karte" onclick="LayoutManager.toggleTile('karte')" aria-pressed="true">Karte</button>
<button class="layout-toggle-btn active" data-tile="lagebild" onclick="LayoutManager.toggleTile('lagebild')" aria-pressed="true" data-i18n="tile.lagebild">Lagebild</button>
<button class="layout-toggle-btn active" data-tile="faktencheck" onclick="LayoutManager.toggleTile('faktencheck')" aria-pressed="true" data-i18n="tile.faktencheck">Faktencheck</button>
<button class="layout-toggle-btn active" data-tile="quellen" onclick="LayoutManager.toggleTile('quellen')" aria-pressed="true" data-i18n="tile.quellen">Quellen</button>
<button class="layout-toggle-btn active" data-tile="timeline" onclick="LayoutManager.toggleTile('timeline')" aria-pressed="true" data-i18n="tile.timeline">Timeline</button>
<button class="layout-toggle-btn active" data-tile="karte" onclick="LayoutManager.toggleTile('karte')" aria-pressed="true" data-i18n="tile.karte">Karte</button>
</div>
<button class="btn btn-secondary btn-small" onclick="LayoutManager.reset()">Layout zurücksetzen</button>
<button class="btn btn-secondary btn-small" onclick="LayoutManager.reset()" data-i18n="btn.layout_reset">Layout zurücksetzen</button>
</div>
<!-- gridstack Dashboard-Grid -->
@@ -193,7 +197,7 @@
<div class="grid-stack-item-content">
<div class="card incident-analysis-summary">
<div class="card-header">
<div class="card-title clickable" role="button" tabindex="0" onclick="openContentModal('Lagebild', 'summary-content')">Lagebild</div>
<div class="card-title clickable" role="button" tabindex="0" onclick="openContentModal(LangManager.t('card.situation_report'), 'summary-content')" data-i18n="card.situation_report">Lagebild</div>
<span class="lagebild-timestamp" id="lagebild-timestamp"></span>
</div>
<div id="summary-content">
@@ -207,12 +211,12 @@
<div class="grid-stack-item-content">
<div class="card incident-analysis-factcheck" id="factcheck-card">
<div class="card-header">
<div class="card-title clickable" role="button" tabindex="0" onclick="openContentModal('Faktencheck', 'factcheck-list')">Faktencheck</div>
<div class="card-title clickable" role="button" tabindex="0" onclick="openContentModal(LangManager.t('card.factcheck'), 'factcheck-list')" data-i18n="card.factcheck">Faktencheck</div>
<div class="fc-filter-bar" id="fc-filters"></div>
</div>
<div class="factcheck-list" id="factcheck-list">
<div class="empty-state" style="padding:20px;">
<div class="empty-state-text">Noch keine Fakten geprüft</div>
<div class="empty-state-text" data-i18n="empty.no_factchecks">Noch keine Fakten geprüft</div>
</div>
</div>
</div>
@@ -224,8 +228,8 @@
<div class="card source-overview-card">
<div class="card-header source-overview-header-toggle" onclick="App.toggleSourceOverview()" role="button" tabindex="0" aria-expanded="false">
<span class="source-overview-chevron" id="source-overview-chevron" title="Aufklappen" aria-hidden="true">&#9656;</span>
<div class="card-title clickable">Quellenübersicht</div>
<button class="btn btn-secondary btn-small source-detail-btn" onclick="event.stopPropagation(); openContentModal('Quellenübersicht', 'source-overview-content')">Detailansicht</button>
<div class="card-title clickable" data-i18n="card.sources_overview">Quellenübersicht</div>
<button class="btn btn-secondary btn-small source-detail-btn" onclick="event.stopPropagation(); openContentModal(LangManager.t('card.sources_overview'), 'source-overview-content')" data-i18n="card.detail_view">Detailansicht</button>
</div>
<div id="source-overview-content" style="display:none;"></div>
</div>
@@ -236,25 +240,25 @@
<div class="grid-stack-item-content">
<div class="card timeline-card">
<div class="card-header">
<div class="card-title clickable" role="button" tabindex="0" onclick="openContentModal('Ereignis-Timeline', 'timeline')">Ereignis-Timeline</div>
<div class="card-title clickable" role="button" tabindex="0" onclick="openContentModal(LangManager.t('card.timeline'), 'timeline')" data-i18n="card.timeline">Ereignis-Timeline</div>
<div class="ht-controls">
<div class="ht-filter-group">
<button class="ht-filter-btn active" data-filter="all" onclick="App.setTimelineFilter('all')" aria-pressed="true">Alle</button>
<button class="ht-filter-btn" data-filter="articles" onclick="App.setTimelineFilter('articles')" aria-pressed="false">Meldungen</button>
<button class="ht-filter-btn" data-filter="snapshots" onclick="App.setTimelineFilter('snapshots')" aria-pressed="false">Lageberichte</button>
<button class="ht-filter-btn active" data-filter="all" onclick="App.setTimelineFilter('all')" aria-pressed="true" data-i18n="timeline.all">Alle</button>
<button class="ht-filter-btn" data-filter="articles" onclick="App.setTimelineFilter('articles')" aria-pressed="false" data-i18n="timeline.articles">Meldungen</button>
<button class="ht-filter-btn" data-filter="snapshots" onclick="App.setTimelineFilter('snapshots')" aria-pressed="false" data-i18n="timeline.snapshots">Lageberichte</button>
</div>
<span class="ht-count" id="article-count"></span>
<div class="ht-range-group">
<button class="ht-range-btn" data-range="24h" onclick="App.setTimelineRange('24h')" aria-pressed="false">24h</button>
<button class="ht-range-btn" data-range="7d" onclick="App.setTimelineRange('7d')" aria-pressed="false">7T</button>
<button class="ht-range-btn active" data-range="all" onclick="App.setTimelineRange('all')" aria-pressed="true">Alles</button>
<button class="ht-range-btn" data-range="24h" onclick="App.setTimelineRange('24h')" aria-pressed="false" data-i18n="timeline.range_24h">24h</button>
<button class="ht-range-btn" data-range="7d" onclick="App.setTimelineRange('7d')" aria-pressed="false" data-i18n="timeline.range_7d">7T</button>
<button class="ht-range-btn active" data-range="all" onclick="App.setTimelineRange('all')" aria-pressed="true" data-i18n="timeline.range_all">Alles</button>
</div>
<label for="timeline-search" class="sr-only">Timeline durchsuchen</label>
<input type="text" id="timeline-search" class="timeline-filter-input" placeholder="Suche..." oninput="App.debouncedRerenderTimeline()">
<input type="text" id="timeline-search" class="timeline-filter-input" placeholder="Suche..." data-i18n-placeholder="timeline.search_placeholder" oninput="App.debouncedRerenderTimeline()">
</div>
</div>
<div id="timeline" class="ht-timeline-container">
<div class="ht-empty">Noch keine Meldungen</div>
<div class="ht-empty" data-i18n="empty.no_articles">Noch keine Meldungen</div>
</div>
</div>
</div>
@@ -264,12 +268,12 @@
<div class="grid-stack-item-content">
<div class="card map-card">
<div class="card-header">
<div class="card-title">Geografische Verteilung</div>
<div class="card-title" data-i18n="card.map">Geografische Verteilung</div>
<span class="map-stats" id="map-stats"></span>
<button class="btn btn-secondary btn-small" id="geoparse-btn" onclick="App.triggerGeoparse()" title="Orte aus Artikeln erkennen">Orte erkennen</button>
<button class="btn btn-secondary btn-small" id="geoparse-btn" onclick="App.triggerGeoparse()" title="Orte aus Artikeln erkennen" data-i18n="btn.detect_locations">Orte erkennen</button>
</div>
<div class="map-container" id="map-container">
<div class="map-empty" id="map-empty">Keine Orte erkannt</div>
<div class="map-empty" id="map-empty" data-i18n="empty.no_locations">Keine Orte erkannt</div>
</div>
</div>
</div>
@@ -286,103 +290,103 @@
<div class="modal-overlay" id="modal-new" role="dialog" aria-modal="true" aria-labelledby="modal-new-title">
<div class="modal">
<div class="modal-header">
<div class="modal-title" id="modal-new-title">Neue Lage anlegen</div>
<div class="modal-title" id="modal-new-title" data-i18n="modal.new_incident">Neue Lage anlegen</div>
<button class="modal-close" onclick="closeModal('modal-new')" aria-label="Schließen">&times;</button>
</div>
<form id="new-incident-form">
<div class="modal-body">
<div class="form-group">
<label for="inc-title">Titel des Vorfalls</label>
<input type="text" id="inc-title" required aria-required="true" placeholder="z.B. Explosion in Madrid">
<label for="inc-title" data-i18n="form.incident_title">Titel des Vorfalls</label>
<input type="text" id="inc-title" required aria-required="true" placeholder="z.B. Explosion in Madrid" data-i18n-placeholder="form.incident_title_placeholder">
</div>
<div class="form-group">
<label for="inc-description">Beschreibung / Kontext</label>
<textarea id="inc-description" placeholder="Weitere Details zum Vorfall (optional)"></textarea>
<label for="inc-description" data-i18n="form.description">Beschreibung / Kontext</label>
<textarea id="inc-description" placeholder="Weitere Details zum Vorfall (optional)" data-i18n-placeholder="form.description_placeholder"></textarea>
</div>
<div class="form-group">
<label for="inc-type">Art der Lage</label>
<label for="inc-type" data-i18n="form.incident_type">Art der Lage</label>
<select id="inc-type" onchange="toggleTypeDefaults()">
<option value="adhoc">Ad-hoc Lage (Breaking News)</option>
<option value="research">Recherche (Hintergrund)</option>
<option value="adhoc" data-i18n="form.type_adhoc">Ad-hoc Lage (Breaking News)</option>
<option value="research" data-i18n="form.type_research">Recherche (Hintergrund)</option>
</select>
<div class="form-hint" id="type-hint">
<div class="form-hint" id="type-hint" data-i18n="form.type_hint_adhoc">
RSS-Feeds + WebSearch, automatische Aktualisierung empfohlen
</div>
</div>
<div class="form-group">
<label>Quellen</label>
<label data-i18n="form.sources">Quellen</label>
<div class="toggle-group">
<label class="toggle-label">
<input type="checkbox" id="inc-international" checked>
<span class="toggle-switch"></span>
<span class="toggle-text">Internationale Quellen einbeziehen</span>
<span class="toggle-text" data-i18n="form.international_sources">Internationale Quellen einbeziehen</span>
</label>
<div class="form-hint" id="sources-hint">DE + internationale Feeds (Reuters, BBC, Al Jazeera etc.)</div>
<div class="form-hint" id="sources-hint" data-i18n="form.sources_hint_intl">DE + internationale Feeds (Reuters, BBC, Al Jazeera etc.)</div>
</div>
</div>
<div class="form-group">
<label>Sichtbarkeit</label>
<label data-i18n="form.visibility">Sichtbarkeit</label>
<div class="toggle-group">
<label class="toggle-label">
<input type="checkbox" id="inc-visibility" checked>
<span class="toggle-switch"></span>
<span class="toggle-text" id="visibility-text">Öffentlich — für alle Nutzer sichtbar</span>
<span class="toggle-text" id="visibility-text" data-i18n="form.visibility_public">Öffentlich — für alle Nutzer sichtbar</span>
</label>
</div>
</div>
<div class="form-group">
<label for="inc-refresh-mode">Aktualisierung</label>
<label for="inc-refresh-mode" data-i18n="form.refresh_mode">Aktualisierung</label>
<select id="inc-refresh-mode" onchange="toggleRefreshInterval()">
<option value="manual">Manuell</option>
<option value="auto">Automatisch</option>
<option value="manual" data-i18n="form.refresh_manual">Manuell</option>
<option value="auto" data-i18n="form.refresh_auto">Automatisch</option>
</select>
</div>
<div class="form-group conditional-field" id="refresh-interval-field">
<label for="inc-refresh-value">Intervall</label>
<label for="inc-refresh-value" data-i18n="form.interval">Intervall</label>
<div class="interval-input-group">
<input type="number" id="inc-refresh-value" min="10" value="15">
<select id="inc-refresh-unit" onchange="updateIntervalMin()">
<option value="1" selected>Minuten</option>
<option value="60">Stunden</option>
<option value="1440">Tage</option>
<option value="10080">Wochen</option>
<option value="1" selected data-i18n="form.unit_minutes">Minuten</option>
<option value="60" data-i18n="form.unit_hours">Stunden</option>
<option value="1440" data-i18n="form.unit_days">Tage</option>
<option value="10080" data-i18n="form.unit_weeks">Wochen</option>
</select>
</div>
</div>
<div class="form-group">
<label for="inc-retention">Aufbewahrung (Tage)</label>
<label for="inc-retention" data-i18n="form.retention">Aufbewahrung (Tage)</label>
<input type="number" id="inc-retention" min="0" max="999" value="30" placeholder="0 = Unbegrenzt">
<div class="form-hint">0 = Unbegrenzt, max. 999 Tage</div>
<div class="form-hint" data-i18n="form.retention_hint">0 = Unbegrenzt, max. 999 Tage</div>
</div>
<div class="form-group" style="margin-top: 8px;">
<label>E-Mail-Benachrichtigungen</label>
<div class="form-hint" style="margin-bottom: 8px;">Per E-Mail benachrichtigen bei:</div>
<label data-i18n="form.email_notifications">E-Mail-Benachrichtigungen</label>
<div class="form-hint" style="margin-bottom: 8px;" data-i18n="form.email_notify_hint">Per E-Mail benachrichtigen bei:</div>
<div class="toggle-group">
<label class="toggle-label">
<input type="checkbox" id="inc-notify-summary">
<span class="toggle-switch"></span>
<span class="toggle-text">Neues Lagebild</span>
<span class="toggle-text" data-i18n="form.notify_summary">Neues Lagebild</span>
</label>
</div>
<div class="toggle-group" style="margin-top: 8px;">
<label class="toggle-label">
<input type="checkbox" id="inc-notify-new-articles">
<span class="toggle-switch"></span>
<span class="toggle-text">Neue Artikel</span>
<span class="toggle-text" data-i18n="form.notify_articles">Neue Artikel</span>
</label>
</div>
<div class="toggle-group" style="margin-top: 8px;">
<label class="toggle-label">
<input type="checkbox" id="inc-notify-status-change">
<span class="toggle-switch"></span>
<span class="toggle-text">Statusänderung Faktencheck</span>
<span class="toggle-text" data-i18n="form.notify_status">Statusänderung Faktencheck</span>
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeModal('modal-new')">Abbrechen</button>
<button type="submit" class="btn btn-primary" id="modal-new-submit">Lage anlegen</button>
<button type="button" class="btn btn-secondary" onclick="closeModal('modal-new')" data-i18n="btn.cancel">Abbrechen</button>
<button type="submit" class="btn btn-primary" id="modal-new-submit" data-i18n="modal.create_incident">Lage anlegen</button>
</div>
</form>
</div>
@@ -392,7 +396,7 @@
<div class="modal-overlay" id="modal-sources" role="dialog" aria-modal="true" aria-labelledby="modal-sources-title">
<div class="modal modal-wide">
<div class="modal-header">
<div class="modal-title" id="modal-sources-title">Quellenverwaltung</div>
<div class="modal-title" id="modal-sources-title" data-i18n="modal.source_management">Quellenverwaltung</div>
<button class="modal-close" onclick="closeModal('modal-sources')" aria-label="Schließen">&times;</button>
</div>
<div class="modal-body sources-modal-body">
@@ -528,29 +532,29 @@
<div class="modal-overlay" id="modal-feedback" role="dialog" aria-modal="true" aria-labelledby="modal-feedback-title">
<div class="modal">
<div class="modal-header">
<div class="modal-title" id="modal-feedback-title">Feedback senden</div>
<div class="modal-title" id="modal-feedback-title" data-i18n="modal.feedback">Feedback senden</div>
<button class="modal-close" onclick="closeModal('modal-feedback')" aria-label="Schließen">&times;</button>
</div>
<form id="feedback-form">
<div class="modal-body">
<div class="form-group">
<label for="fb-category">Kategorie</label>
<label for="fb-category" data-i18n="form.feedback_category">Kategorie</label>
<select id="fb-category">
<option value="bug">Fehlerbericht</option>
<option value="feature">Feature-Wunsch</option>
<option value="question">Frage</option>
<option value="other">Sonstiges</option>
<option value="bug" data-i18n="form.fb_bug">Fehlerbericht</option>
<option value="feature" data-i18n="form.fb_feature">Feature-Wunsch</option>
<option value="question" data-i18n="form.fb_question">Frage</option>
<option value="other" data-i18n="form.fb_other">Sonstiges</option>
</select>
</div>
<div class="form-group">
<label for="fb-message">Nachricht</label>
<textarea id="fb-message" required aria-required="true" minlength="10" maxlength="5000" rows="6" placeholder="Beschreibe dein Anliegen (mind. 10 Zeichen)..."></textarea>
<label for="fb-message" data-i18n="form.fb_message">Nachricht</label>
<textarea id="fb-message" required aria-required="true" minlength="10" maxlength="5000" rows="6" placeholder="Beschreibe dein Anliegen (mind. 10 Zeichen)..." data-i18n-placeholder="form.fb_placeholder"></textarea>
<div class="form-hint"><span id="fb-char-count">0</span> / 5.000 Zeichen</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeModal('modal-feedback')">Abbrechen</button>
<button type="submit" class="btn btn-primary" id="fb-submit-btn">Absenden</button>
<button type="button" class="btn btn-secondary" onclick="closeModal('modal-feedback')" data-i18n="btn.cancel">Abbrechen</button>
<button type="submit" class="btn btn-primary" id="fb-submit-btn" data-i18n="btn.submit_feedback">Absenden</button>
</div>
</form>
</div>
@@ -562,6 +566,7 @@
<script src="https://cdn.jsdelivr.net/npm/gridstack@12/dist/gridstack-all.js"></script>
<script src="/static/vendor/leaflet.js"></script>
<script src="/static/vendor/leaflet.markercluster.js"></script>
<script src="/static/js/lang.js?v=20260305a"></script>
<script src="/static/js/api.js?v=20260304h"></script>
<script src="/static/js/ws.js?v=20260304h"></script>
<script src="/static/js/components.js?v=20260304h"></script>