feat(i18n): Progress-Popup + Pipeline-Stati lokalisieren
- components._getStepLabel und progress-popup-title nutzen T() fuer Erste Recherche laeuft / Aktualisierung laeuft / In Warteschlange / Wird abgebrochen. - pipeline._formatHeader / _relativeTime / _formatCount lokalisiert: Status-Texte (erledigt/laeuft/Fehler), Zeitangaben (gerade eben, vor X Min/Std/Tagen), Aktualisierung-laeuft-Header. - dashboard.html: data-i18n auf pipeline-empty, progress-popup-title, progress-check-label (4 Stueck). - Cache-Buster fuer components.js + pipeline.js auf v=20260513d.
Dieser Commit ist enthalten in:
@@ -301,7 +301,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pipeline-body">
|
<div class="pipeline-body">
|
||||||
<div class="pipeline-stage" id="pipeline-stage" aria-label="Analysepipeline-Visualisierung">
|
<div class="pipeline-stage" id="pipeline-stage" aria-label="Analysepipeline-Visualisierung">
|
||||||
<div class="pipeline-empty" id="pipeline-empty">Noch nie aktualisiert. Starte den ersten Refresh.</div>
|
<div class="pipeline-empty" id="pipeline-empty" data-i18n="pipeline.empty">Noch nie aktualisiert. Starte den ersten Refresh.</div>
|
||||||
</div>
|
</div>
|
||||||
<aside class="pipeline-sidenote" id="pipeline-sidenote" hidden>
|
<aside class="pipeline-sidenote" id="pipeline-sidenote" hidden>
|
||||||
Recherche-Lagen werden mehrfach evaluiert, um das Bild Schritt für Schritt aufzubauen.
|
Recherche-Lagen werden mehrfach evaluiert, um das Bild Schritt für Schritt aufzubauen.
|
||||||
@@ -726,9 +726,9 @@
|
|||||||
<script src="/static/js/i18n.js?v=20260513a"></script>
|
<script src="/static/js/i18n.js?v=20260513a"></script>
|
||||||
<script src="/static/js/api.js?v=20260423a"></script>
|
<script src="/static/js/api.js?v=20260423a"></script>
|
||||||
<script src="/static/js/ws.js?v=20260316b"></script>
|
<script src="/static/js/ws.js?v=20260316b"></script>
|
||||||
<script src="/static/js/components.js?v=20260513a"></script>
|
<script src="/static/js/components.js?v=20260513d"></script>
|
||||||
<script src="/static/js/layout.js?v=20260316b"></script>
|
<script src="/static/js/layout.js?v=20260316b"></script>
|
||||||
<script src="/static/js/pipeline.js?v=20260501i"></script>
|
<script src="/static/js/pipeline.js?v=20260513d"></script>
|
||||||
<script src="/static/js/app.js?v=20260513c"></script>
|
<script src="/static/js/app.js?v=20260513c"></script>
|
||||||
<script src="/static/js/cluster-data.js?v=20260322f"></script>
|
<script src="/static/js/cluster-data.js?v=20260322f"></script>
|
||||||
<script src="/static/js/tutorial.js?v=20260316z"></script>
|
<script src="/static/js/tutorial.js?v=20260316z"></script>
|
||||||
@@ -782,7 +782,7 @@
|
|||||||
<div class="progress-overlay" id="progress-overlay" style="display:none;">
|
<div class="progress-overlay" id="progress-overlay" style="display:none;">
|
||||||
<div class="progress-popup" id="progress-popup">
|
<div class="progress-popup" id="progress-popup">
|
||||||
<div class="progress-popup-header">
|
<div class="progress-popup-header">
|
||||||
<span class="progress-popup-title" id="progress-popup-title">Aktualisierung läuft</span>
|
<span class="progress-popup-title" id="progress-popup-title" data-i18n="progress.title.refresh">Aktualisierung läuft</span>
|
||||||
<span class="progress-popup-timer" id="progress-popup-timer"></span>
|
<span class="progress-popup-timer" id="progress-popup-timer"></span>
|
||||||
<button class="progress-popup-minimize" id="progress-popup-minimize" style="display:none;" onclick="App.minimizeProgress()" title="Minimieren">−</button>
|
<button class="progress-popup-minimize" id="progress-popup-minimize" style="display:none;" onclick="App.minimizeProgress()" title="Minimieren">−</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -792,22 +792,22 @@
|
|||||||
<div class="progress-checklist" id="progress-checklist" style="display:none;">
|
<div class="progress-checklist" id="progress-checklist" style="display:none;">
|
||||||
<div class="progress-check-item" data-step="queued">
|
<div class="progress-check-item" data-step="queued">
|
||||||
<span class="progress-check-icon">○</span>
|
<span class="progress-check-icon">○</span>
|
||||||
<span class="progress-check-label">In Warteschlange</span>
|
<span class="progress-check-label" data-i18n="progress.title.queued">In Warteschlange</span>
|
||||||
<span class="progress-check-detail"></span>
|
<span class="progress-check-detail"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress-check-item" data-step="researching">
|
<div class="progress-check-item" data-step="researching">
|
||||||
<span class="progress-check-icon">○</span>
|
<span class="progress-check-icon">○</span>
|
||||||
<span class="progress-check-label">Quellen werden durchsucht</span>
|
<span class="progress-check-label" data-i18n="progress.check.researching">Quellen werden durchsucht</span>
|
||||||
<span class="progress-check-detail"></span>
|
<span class="progress-check-detail"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress-check-item" data-step="analyzing">
|
<div class="progress-check-item" data-step="analyzing">
|
||||||
<span class="progress-check-icon">○</span>
|
<span class="progress-check-icon">○</span>
|
||||||
<span class="progress-check-label">Meldungen werden analysiert</span>
|
<span class="progress-check-label" data-i18n="progress.check.analyzing">Meldungen werden analysiert</span>
|
||||||
<span class="progress-check-detail"></span>
|
<span class="progress-check-detail"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress-check-item" data-step="factchecking">
|
<div class="progress-check-item" data-step="factchecking">
|
||||||
<span class="progress-check-icon">○</span>
|
<span class="progress-check-icon">○</span>
|
||||||
<span class="progress-check-label">Faktencheck läuft</span>
|
<span class="progress-check-label" data-i18n="progress.factcheck_running">Faktencheck läuft</span>
|
||||||
<span class="progress-check-detail"></span>
|
<span class="progress-check-detail"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -65,5 +65,40 @@
|
|||||||
"refresh.no_developments": "Keine neuen Entwicklungen",
|
"refresh.no_developments": "Keine neuen Entwicklungen",
|
||||||
"refresh.new_articles_suffix": "neue Artikel",
|
"refresh.new_articles_suffix": "neue Artikel",
|
||||||
"refresh.confirmed_suffix": "Fakten bestätigt",
|
"refresh.confirmed_suffix": "Fakten bestätigt",
|
||||||
"refresh.contradicted_suffix": "widerlegt"
|
"refresh.contradicted_suffix": "widerlegt",
|
||||||
|
"progress.status.queued": "In Warteschlange",
|
||||||
|
"progress.status.researching": "Recherchiert...",
|
||||||
|
"progress.status.deep_researching": "Tiefenrecherche...",
|
||||||
|
"progress.status.analyzing": "Analysiert...",
|
||||||
|
"progress.status.factchecking": "Faktencheck...",
|
||||||
|
"progress.status.cancelling": "Wird abgebrochen...",
|
||||||
|
"progress.title.first_refresh": "Erste Recherche läuft",
|
||||||
|
"progress.title.refresh": "Aktualisierung läuft",
|
||||||
|
"progress.title.queued": "In Warteschlange",
|
||||||
|
"progress.title.cancelling": "Wird abgebrochen…",
|
||||||
|
"progress.factcheck_running": "Faktencheck läuft",
|
||||||
|
"progress.check.researching": "Quellen werden durchsucht",
|
||||||
|
"progress.check.analyzing": "Meldungen werden analysiert",
|
||||||
|
"pipeline.empty": "Noch nie aktualisiert. Starte den ersten Refresh.",
|
||||||
|
"pipeline.load_failed": "Pipeline laden fehlgeschlagen",
|
||||||
|
"pipeline.running": "Aktualisierung läuft...",
|
||||||
|
"pipeline.cancelled": "abgebrochen",
|
||||||
|
"pipeline.with_errors": "mit Fehler beendet",
|
||||||
|
"pipeline.duration_prefix": "Dauer:",
|
||||||
|
"pipeline.status.done": "erledigt",
|
||||||
|
"pipeline.status.running": "läuft...",
|
||||||
|
"pipeline.status.error": "Fehler",
|
||||||
|
"pipeline.count.sources_reviewed": "{n} Quellen geprüft",
|
||||||
|
"pipeline.count.collected": "{n} Meldungen",
|
||||||
|
"pipeline.count.collected_from": "{n} Meldungen aus {s} Quellen",
|
||||||
|
"time.just_now": "gerade eben",
|
||||||
|
"time.minutes_ago": "vor {n} Min",
|
||||||
|
"time.hours_ago": "vor {n} Std",
|
||||||
|
"time.days_ago": "vor {n} Tagen",
|
||||||
|
"time.day_ago": "vor 1 Tag",
|
||||||
|
"toast.incident_refreshed": "Lage aktualisiert.",
|
||||||
|
"toast.data_refreshed": "Daten aktualisiert.",
|
||||||
|
"toast.source_updated": "Quelle aktualisiert.",
|
||||||
|
"toast.session_expires": "Session läuft in {min} Minute(n) ab. Bitte erneut anmelden.",
|
||||||
|
"confirm.delete_incident": "Lage wirklich löschen? Alle gesammelten Daten gehen verloren."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,5 +65,40 @@
|
|||||||
"refresh.no_developments": "No new developments",
|
"refresh.no_developments": "No new developments",
|
||||||
"refresh.new_articles_suffix": "new articles",
|
"refresh.new_articles_suffix": "new articles",
|
||||||
"refresh.confirmed_suffix": "facts confirmed",
|
"refresh.confirmed_suffix": "facts confirmed",
|
||||||
"refresh.contradicted_suffix": "contradicted"
|
"refresh.contradicted_suffix": "contradicted",
|
||||||
|
"progress.status.queued": "Queued",
|
||||||
|
"progress.status.researching": "Researching...",
|
||||||
|
"progress.status.deep_researching": "Deep research...",
|
||||||
|
"progress.status.analyzing": "Analyzing...",
|
||||||
|
"progress.status.factchecking": "Fact-checking...",
|
||||||
|
"progress.status.cancelling": "Cancelling...",
|
||||||
|
"progress.title.first_refresh": "Initial research running",
|
||||||
|
"progress.title.refresh": "Refresh running",
|
||||||
|
"progress.title.queued": "Queued",
|
||||||
|
"progress.title.cancelling": "Cancelling…",
|
||||||
|
"progress.factcheck_running": "Fact-check running",
|
||||||
|
"progress.check.researching": "Searching sources",
|
||||||
|
"progress.check.analyzing": "Analyzing articles",
|
||||||
|
"pipeline.empty": "Never refreshed. Start the first refresh.",
|
||||||
|
"pipeline.load_failed": "Failed to load pipeline",
|
||||||
|
"pipeline.running": "Refresh running...",
|
||||||
|
"pipeline.cancelled": "cancelled",
|
||||||
|
"pipeline.with_errors": "finished with errors",
|
||||||
|
"pipeline.duration_prefix": "Duration:",
|
||||||
|
"pipeline.status.done": "done",
|
||||||
|
"pipeline.status.running": "running...",
|
||||||
|
"pipeline.status.error": "error",
|
||||||
|
"pipeline.count.sources_reviewed": "{n} sources checked",
|
||||||
|
"pipeline.count.collected": "{n} articles",
|
||||||
|
"pipeline.count.collected_from": "{n} articles from {s} sources",
|
||||||
|
"time.just_now": "just now",
|
||||||
|
"time.minutes_ago": "{n} min ago",
|
||||||
|
"time.hours_ago": "{n}h ago",
|
||||||
|
"time.days_ago": "{n} days ago",
|
||||||
|
"time.day_ago": "1 day ago",
|
||||||
|
"toast.incident_refreshed": "Situation refreshed.",
|
||||||
|
"toast.data_refreshed": "Data refreshed.",
|
||||||
|
"toast.source_updated": "Source updated.",
|
||||||
|
"toast.session_expires": "Session expires in {min} minute(s). Please sign in again.",
|
||||||
|
"confirm.delete_incident": "Really delete this situation? All collected data will be lost."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ const UI = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_getStepLabel(step) {
|
_getStepLabel(step) {
|
||||||
const map = {
|
const fallback = {
|
||||||
queued: 'In Warteschlange',
|
queued: 'In Warteschlange',
|
||||||
researching: 'Recherchiert...',
|
researching: 'Recherchiert...',
|
||||||
deep_researching: 'Tiefenrecherche...',
|
deep_researching: 'Tiefenrecherche...',
|
||||||
@@ -298,7 +298,10 @@ const UI = {
|
|||||||
factchecking: 'Faktencheck...',
|
factchecking: 'Faktencheck...',
|
||||||
cancelling: 'Wird abgebrochen...',
|
cancelling: 'Wird abgebrochen...',
|
||||||
};
|
};
|
||||||
return map[step] || step;
|
if (!fallback[step]) return step;
|
||||||
|
return (typeof T === 'function')
|
||||||
|
? T('progress.status.' + step, fallback[step])
|
||||||
|
: fallback[step];
|
||||||
},
|
},
|
||||||
|
|
||||||
showProgress(status, extra = {}, incidentId = null, isFirstRefresh = false) {
|
showProgress(status, extra = {}, incidentId = null, isFirstRefresh = false) {
|
||||||
@@ -386,16 +389,17 @@ const UI = {
|
|||||||
// Title - haengt von Status ab (queued = wartet, cancelling = bricht ab, sonst laeuft)
|
// Title - haengt von Status ab (queued = wartet, cancelling = bricht ab, sonst laeuft)
|
||||||
const titleEl = document.getElementById('progress-popup-title');
|
const titleEl = document.getElementById('progress-popup-title');
|
||||||
if (titleEl) {
|
if (titleEl) {
|
||||||
|
const _t = (k, fb) => (typeof T === 'function') ? T(k, fb) : fb;
|
||||||
let title;
|
let title;
|
||||||
if (status === 'queued') {
|
if (status === 'queued') {
|
||||||
const pos = (state && state._queuePos) ? ' (#' + state._queuePos + ')' : '';
|
const pos = (state && state._queuePos) ? ' (#' + state._queuePos + ')' : '';
|
||||||
title = 'In Warteschlange' + pos;
|
title = _t('progress.title.queued', 'In Warteschlange') + pos;
|
||||||
} else if (status === 'cancelling') {
|
} else if (status === 'cancelling') {
|
||||||
title = 'Wird abgebrochen\u2026';
|
title = _t('progress.title.cancelling', 'Wird abgebrochen\u2026');
|
||||||
} else if (state.isFirst) {
|
} else if (state.isFirst) {
|
||||||
title = 'Erste Recherche l\u00e4uft';
|
title = _t('progress.title.first_refresh', 'Erste Recherche l\u00e4uft');
|
||||||
} else {
|
} else {
|
||||||
title = 'Aktualisierung l\u00e4uft';
|
title = _t('progress.title.refresh', 'Aktualisierung l\u00e4uft');
|
||||||
}
|
}
|
||||||
titleEl.textContent = title;
|
titleEl.textContent = title;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,7 +254,8 @@ const Pipeline = {
|
|||||||
|
|
||||||
// Brandneue Lage ohne Refresh
|
// Brandneue Lage ohne Refresh
|
||||||
if (!this._lastRefreshHeader) {
|
if (!this._lastRefreshHeader) {
|
||||||
this._renderEmpty('Noch nie aktualisiert. Starte den ersten Refresh.');
|
const _t = (k, fb) => (typeof T === 'function') ? T(k, fb) : fb;
|
||||||
|
this._renderEmpty(_t('pipeline.empty', 'Noch nie aktualisiert. Starte den ersten Refresh.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,20 +503,22 @@ const Pipeline = {
|
|||||||
_formatHeader() {
|
_formatHeader() {
|
||||||
const r = this._lastRefreshHeader;
|
const r = this._lastRefreshHeader;
|
||||||
if (!r) return '';
|
if (!r) return '';
|
||||||
|
const _t = (k, fb) => (typeof T === 'function') ? T(k, fb) : fb;
|
||||||
|
const lastLabel = _t('pipeline.last_refresh', 'Letzter Refresh');
|
||||||
let parts = [];
|
let parts = [];
|
||||||
if (r.started_at) {
|
if (r.started_at) {
|
||||||
const rel = this._relativeTime(r.started_at);
|
const rel = this._relativeTime(r.started_at);
|
||||||
parts.push(rel ? `Letzter Refresh: ${rel}` : `Letzter Refresh: ${r.started_at}`);
|
parts.push(rel ? `${lastLabel}: ${rel}` : `${lastLabel}: ${r.started_at}`);
|
||||||
}
|
}
|
||||||
if (r.duration_sec != null) {
|
if (r.duration_sec != null) {
|
||||||
parts.push(`Dauer: ${r.duration_sec} s`);
|
parts.push(`${_t('pipeline.duration_prefix', 'Dauer:')} ${r.duration_sec} s`);
|
||||||
}
|
}
|
||||||
if (r.status === 'running') {
|
if (r.status === 'running') {
|
||||||
parts = ['Aktualisierung läuft...'];
|
parts = [_t('pipeline.running', 'Aktualisierung läuft...')];
|
||||||
} else if (r.status === 'cancelled') {
|
} else if (r.status === 'cancelled') {
|
||||||
parts.push('abgebrochen');
|
parts.push(_t('pipeline.cancelled', 'abgebrochen'));
|
||||||
} else if (r.status === 'error') {
|
} else if (r.status === 'error') {
|
||||||
parts.push('mit Fehler beendet');
|
parts.push(_t('pipeline.with_errors', 'mit Fehler beendet'));
|
||||||
}
|
}
|
||||||
return parts.join(' · ');
|
return parts.join(' · ');
|
||||||
},
|
},
|
||||||
@@ -527,28 +530,34 @@ const Pipeline = {
|
|||||||
if (isNaN(d.getTime())) return '';
|
if (isNaN(d.getTime())) return '';
|
||||||
const diffMs = Date.now() - d.getTime();
|
const diffMs = Date.now() - d.getTime();
|
||||||
const min = Math.floor(diffMs / 60000);
|
const min = Math.floor(diffMs / 60000);
|
||||||
if (min < 1) return 'gerade eben';
|
const _t = (k, fb) => (typeof T === 'function') ? T(k, fb) : fb;
|
||||||
if (min < 60) return `vor ${min} Min`;
|
if (min < 1) return _t('time.just_now', 'gerade eben');
|
||||||
|
if (min < 60) return _t('time.minutes_ago', 'vor {n} Min').replace('{n}', min);
|
||||||
const h = Math.floor(min / 60);
|
const h = Math.floor(min / 60);
|
||||||
if (h < 24) return `vor ${h} Std`;
|
if (h < 24) return _t('time.hours_ago', 'vor {n} Std').replace('{n}', h);
|
||||||
const days = Math.floor(h / 24);
|
const days = Math.floor(h / 24);
|
||||||
return `vor ${days} Tag${days === 1 ? '' : 'en'}`;
|
if (days === 1) return _t('time.day_ago', 'vor 1 Tag');
|
||||||
|
return _t('time.days_ago', 'vor {n} Tagen').replace('{n}', days);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_formatCount(stepKey, cv, cs, status) {
|
_formatCount(stepKey, cv, cs, status) {
|
||||||
|
const _t = (k, fb) => (typeof T === 'function') ? T(k, fb) : fb;
|
||||||
|
const sDone = _t('pipeline.status.done', 'erledigt');
|
||||||
|
const sRun = _t('pipeline.status.running', 'läuft...');
|
||||||
|
const sErr = _t('pipeline.status.error', 'Fehler');
|
||||||
// Qualitaetscheck: KEINE Zahlen, nur Status (Anforderung 3 vom User)
|
// Qualitaetscheck: KEINE Zahlen, nur Status (Anforderung 3 vom User)
|
||||||
if (stepKey === 'qc' || stepKey === 'summary') {
|
if (stepKey === 'qc' || stepKey === 'summary') {
|
||||||
if (status === 'done') return '<span class="count-status">erledigt</span>';
|
if (status === 'done') return `<span class="count-status">${sDone}</span>`;
|
||||||
if (status === 'active') return '<span class="count-status">läuft...</span>';
|
if (status === 'active') return `<span class="count-status">${sRun}</span>`;
|
||||||
if (status === 'error') return '<span class="count-status">Fehler</span>';
|
if (status === 'error') return `<span class="count-status">${sErr}</span>`;
|
||||||
return '<span class="count-status">-</span>';
|
return '<span class="count-status">-</span>';
|
||||||
}
|
}
|
||||||
if (status === 'pending') return '<span class="count-status">-</span>';
|
if (status === 'pending') return '<span class="count-status">-</span>';
|
||||||
if (status === 'active') return '<span class="count-status">läuft...</span>';
|
if (status === 'active') return `<span class="count-status">${sRun}</span>`;
|
||||||
if (status === 'error') return '<span class="count-status">Fehler</span>';
|
if (status === 'error') return `<span class="count-status">${sErr}</span>`;
|
||||||
if (cv == null) return '<span class="count-status">-</span>';
|
if (cv == null) return '<span class="count-status">-</span>';
|
||||||
|
|
||||||
switch (stepKey) {
|
switch (stepKey) {
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren