i18n: Fix gray map tiles, translate A11y/Notification panels, remaining strings

- Fix gray map on EN: always use tile.openstreetmap.de (org has rate limits)
- Add A11yManager._updateLabels() for live language switch of accessibility panel
- Add NotificationCenter._updateLabels() for notification panel translation
- Replace all remaining hardcoded de-DE locales with dynamic locale switch
- Translate sidebar stats, source discovery toasts, session expiry warning
- Translate source form hints, type labels, article progress counter
- Add 15+ new translation keys for missing strings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-03-05 16:36:28 +01:00
Ursprung 28458118d4
Commit b604a80842
2 geänderte Dateien mit 106 neuen und 36 gelöschten Zeilen

Datei anzeigen

@@ -164,6 +164,31 @@ const A11yManager = {
localStorage.setItem(this._key, JSON.stringify(this._settings)); localStorage.setItem(this._key, JSON.stringify(this._settings));
}, },
_updateLabels() {
const btn = document.getElementById('a11y-btn');
if (btn) {
btn.title = LangManager.t('a11y.title');
btn.setAttribute('aria-label', LangManager.t('a11y.title'));
}
const panel = document.getElementById('a11y-panel');
if (panel) {
panel.setAttribute('aria-label', LangManager.t('a11y.title'));
const title = panel.querySelector('.a11y-panel-title');
if (title) title.textContent = LangManager.t('a11y.title');
}
const keys = ['contrast', 'focus', 'fontsize', 'motion'];
keys.forEach(k => {
const cb = document.getElementById('a11y-' + k);
if (cb) {
const label = cb.closest('.a11y-option');
if (label) {
const span = label.querySelector('span:last-child');
if (span) span.textContent = LangManager.t('a11y.' + k);
}
}
});
},
_openPanel() { _openPanel() {
this._isOpen = true; this._isOpen = true;
document.getElementById('a11y-panel').style.display = ''; document.getElementById('a11y-panel').style.display = '';
@@ -317,7 +342,7 @@ const NotificationCenter = {
list.innerHTML = this._notifications.map(n => { list.innerHTML = this._notifications.map(n => {
const time = new Date(n.timestamp); const time = new Date(n.timestamp);
const timeStr = time.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); const timeStr = time.toLocaleTimeString(LangManager.lang === 'de' ? 'de-DE' : 'en-GB', { hour: '2-digit', minute: '2-digit' });
const unreadClass = n.read ? '' : ' unread'; const unreadClass = n.read ? '' : ' unread';
const icon = n.icon || 'info'; const icon = n.icon || 'info';
return `<div class="notification-item${unreadClass}" onclick="NotificationCenter._handleClick(${n.incident_id})" data-id="${n.incident_id}" role="button" tabindex="0"> return `<div class="notification-item${unreadClass}" onclick="NotificationCenter._handleClick(${n.incident_id})" data-id="${n.incident_id}" role="button" tabindex="0">
@@ -374,6 +399,19 @@ const NotificationCenter = {
} }
}, },
_updateLabels() {
const bell = document.getElementById('notification-bell');
if (bell) {
bell.title = LangManager.t('notif.title');
bell.setAttribute('aria-label', LangManager.t('notif.title'));
}
const panelTitle = document.querySelector('.notification-panel-title');
if (panelTitle) panelTitle.textContent = LangManager.t('notif.title');
const markRead = document.getElementById('notification-mark-read');
if (markRead) markRead.textContent = LangManager.t('notif.mark_read');
this._renderList();
},
async _syncFromDB() { async _syncFromDB() {
try { try {
const items = await API.listNotifications(50); const items = await API.listNotifications(50);
@@ -489,7 +527,7 @@ const App = {
// Feedback // Feedback
document.getElementById('feedback-form').addEventListener('submit', (e) => this.submitFeedback(e)); document.getElementById('feedback-form').addEventListener('submit', (e) => this.submitFeedback(e));
document.getElementById('fb-message').addEventListener('input', (e) => { document.getElementById('fb-message').addEventListener('input', (e) => {
document.getElementById('fb-char-count').textContent = e.target.value.length.toLocaleString('de-DE'); document.getElementById('fb-char-count').textContent = e.target.value.length.toLocaleString(LangManager.lang === 'de' ? 'de-DE' : 'en-GB');
}); });
// Sidebar-Chevrons initial auf offen setzen (Archiv geschlossen) // Sidebar-Chevrons initial auf offen setzen (Archiv geschlossen)
@@ -677,7 +715,7 @@ const App = {
const deleteBtn = document.getElementById('delete-incident-btn'); const deleteBtn = document.getElementById('delete-incident-btn');
const isCreator = incident.created_by_username === this._currentUsername; const isCreator = incident.created_by_username === this._currentUsername;
deleteBtn.disabled = !isCreator; deleteBtn.disabled = !isCreator;
deleteBtn.title = isCreator ? '' : `Nur ${incident.created_by_username} kann diese Lage löschen`; deleteBtn.title = isCreator ? '' : LangManager.t('incident.delete_only_creator', { name: incident.created_by_username });
// Zusammenfassung mit Quellenverzeichnis // Zusammenfassung mit Quellenverzeichnis
const summaryText = document.getElementById('summary-text'); const summaryText = document.getElementById('summary-text');
@@ -695,7 +733,8 @@ const App = {
const updated = incident.updated_at ? new Date(incident.updated_at) : null; const updated = incident.updated_at ? new Date(incident.updated_at) : null;
const metaUpdated = document.getElementById('meta-updated'); const metaUpdated = document.getElementById('meta-updated');
if (updated) { if (updated) {
const fullDate = `${updated.toLocaleDateString('de-DE')} ${updated.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}`; const _loc = LangManager.lang === 'de' ? 'de-DE' : 'en-GB';
const fullDate = `${updated.toLocaleDateString(_loc)} ${updated.toLocaleTimeString(_loc, { hour: '2-digit', minute: '2-digit' })}`;
metaUpdated.textContent = LangManager.t('time.stand', { time: App._timeAgo(updated) }); metaUpdated.textContent = LangManager.t('time.stand', { time: App._timeAgo(updated) });
metaUpdated.title = fullDate; metaUpdated.title = fullDate;
} else { } else {
@@ -951,13 +990,14 @@ const App = {
entries.forEach(e => { entries.forEach(e => {
const d = new Date(e.timestamp || 0); const d = new Date(e.timestamp || 0);
let key, label, ts; let key, label, ts;
const _dtLoc = LangManager.lang === 'de' ? 'de-DE' : 'en-GB';
if (granularity === 'hour') { if (granularity === 'hour') {
key = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}-${d.getHours()}`; key = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}-${d.getHours()}`;
label = d.toLocaleDateString('de-DE', { day: '2-digit', month: 'short' }) + ', ' + d.getHours().toString().padStart(2, '0') + ':00'; label = d.toLocaleDateString(_dtLoc, { day: '2-digit', month: 'short' }) + ', ' + d.getHours().toString().padStart(2, '0') + ':00';
ts = new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours()).getTime(); ts = new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours()).getTime();
} else { } else {
key = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`; key = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`;
label = d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: 'short' }); label = d.toLocaleDateString(_dtLoc, { weekday: 'short', day: '2-digit', month: 'short' });
ts = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 12).getTime(); ts = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 12).getTime();
} }
if (!bucketMap[key]) { if (!bucketMap[key]) {
@@ -1069,8 +1109,8 @@ const App = {
const yesterday = new Date(today.getTime() - 86400000); const yesterday = new Date(today.getTime() - 86400000);
const bucketDay = new Date(d.getFullYear(), d.getMonth(), d.getDate()); const bucketDay = new Date(d.getFullYear(), d.getMonth(), d.getDate());
let label; let label;
const dateStr = d.toLocaleDateString('de-DE', { day: '2-digit', month: 'short' });
const locale = LangManager.lang === 'de' ? 'de-DE' : 'en-GB'; const locale = LangManager.lang === 'de' ? 'de-DE' : 'en-GB';
const dateStr = d.toLocaleDateString(locale, { day: '2-digit', month: 'short' });
if (bucketDay.getTime() === today.getTime()) { if (bucketDay.getTime() === today.getTime()) {
label = LangManager.t('time.today') + ', ' + dateStr; label = LangManager.t('time.today') + ', ' + dateStr;
} else if (bucketDay.getTime() === yesterday.getTime()) { } else if (bucketDay.getTime() === yesterday.getTime()) {
@@ -1287,7 +1327,7 @@ const App = {
const dateField = (type === 'research' && article.published_at) const dateField = (type === 'research' && article.published_at)
? article.published_at : article.collected_at; ? article.published_at : article.collected_at;
const time = dateField const time = dateField
? new Date(dateField).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) ? new Date(dateField).toLocaleTimeString(LangManager.lang === 'de' ? 'de-DE' : 'en-GB', { hour: '2-digit', minute: '2-digit' })
: '--:--'; : '--:--';
const headline = article.headline_de || article.headline; const headline = article.headline_de || article.headline;
@@ -1329,7 +1369,7 @@ const App = {
*/ */
_renderSnapshotEntry(snapshot) { _renderSnapshotEntry(snapshot) {
const time = snapshot.created_at const time = snapshot.created_at
? new Date(snapshot.created_at).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) ? new Date(snapshot.created_at).toLocaleTimeString(LangManager.lang === 'de' ? 'de-DE' : 'en-GB', { hour: '2-digit', minute: '2-digit' })
: '--:--'; : '--:--';
const stats = []; const stats = [];
@@ -1546,7 +1586,7 @@ const App = {
try { try {
const st = await API.getGeoparseStatus(incidentId); const st = await API.getGeoparseStatus(incidentId);
if (st.status === 'running') { if (st.status === 'running') {
if (btn) btn.textContent = `${st.processed}/${st.total} Artikel...`; if (btn) btn.textContent = LangManager.t('misc.articles_progress', { done: st.processed, total: st.total });
} else { } else {
clearInterval(this._geoparsePolling); clearInterval(this._geoparsePolling);
this._geoparsePolling = null; this._geoparsePolling = null;
@@ -1667,8 +1707,9 @@ const App = {
list.innerHTML = logs.map(log => { list.innerHTML = logs.map(log => {
const started = new Date(log.started_at); const started = new Date(log.started_at);
const timeStr = started.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }) + ' ' + const _rlLoc = LangManager.lang === 'de' ? 'de-DE' : 'en-GB';
started.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); const timeStr = started.toLocaleDateString(_rlLoc, { day: '2-digit', month: '2-digit' }) + ' ' +
started.toLocaleTimeString(_rlLoc, { hour: '2-digit', minute: '2-digit' });
let detail = ''; let detail = '';
if (log.status === 'completed') { if (log.status === 'completed') {
@@ -1874,7 +1915,7 @@ const App = {
handleRefreshSummary(msg) { handleRefreshSummary(msg) {
const d = msg.data; const d = msg.data;
const title = d.incident_title || 'Lage'; const title = d.incident_title || LangManager.t('misc.incident');
// Abschluss-Animation auslösen wenn pending // Abschluss-Animation auslösen wenn pending
if (this._pendingComplete === msg.incident_id) { if (this._pendingComplete === msg.incident_id) {
@@ -2065,16 +2106,16 @@ const App = {
const stats = await API.getSourceStats(); const stats = await API.getSourceStats();
const srcCount = document.getElementById('stat-sources-count'); const srcCount = document.getElementById('stat-sources-count');
const artCount = document.getElementById('stat-articles-count'); const artCount = document.getElementById('stat-articles-count');
if (srcCount) srcCount.textContent = `${stats.total_sources} Quellen`; if (srcCount) srcCount.textContent = LangManager.t('sidebar.sources_count', { n: stats.total_sources });
if (artCount) artCount.textContent = `${stats.total_articles} Artikel`; if (artCount) artCount.textContent = LangManager.t('sidebar.articles_count', { n: stats.total_articles });
} catch { } catch {
// Fallback: aus Lagen berechnen // Fallback: aus Lagen berechnen
const totalArticles = this.incidents.reduce((sum, i) => sum + i.article_count, 0); const totalArticles = this.incidents.reduce((sum, i) => sum + i.article_count, 0);
const totalSources = this.incidents.reduce((sum, i) => sum + i.source_count, 0); const totalSources = this.incidents.reduce((sum, i) => sum + i.source_count, 0);
const srcCount = document.getElementById('stat-sources-count'); const srcCount = document.getElementById('stat-sources-count');
const artCount = document.getElementById('stat-articles-count'); const artCount = document.getElementById('stat-articles-count');
if (srcCount) srcCount.textContent = `${totalSources} Quellen`; if (srcCount) srcCount.textContent = LangManager.t('sidebar.sources_count', { n: totalSources });
if (artCount) artCount.textContent = `${totalArticles} Artikel`; if (artCount) artCount.textContent = LangManager.t('sidebar.articles_count', { n: totalArticles });
} }
}, },
@@ -2165,6 +2206,8 @@ const App = {
this._sourcesOnly = sources.filter(s => s.source_type !== 'excluded'); this._sourcesOnly = sources.filter(s => s.source_type !== 'excluded');
this._blacklistOnly = sources.filter(s => s.source_type === 'excluded'); this._blacklistOnly = sources.filter(s => s.source_type === 'excluded');
this._lastSourceStats = stats;
this._sourcesLoaded = true;
this.renderSourceStats(stats); this.renderSourceStats(stats);
this.renderSourceList(); this.renderSourceList();
} catch (err) { } catch (err) {
@@ -2575,7 +2618,7 @@ const App = {
document.getElementById('src-domain').value = this._discoveredData.domain || ''; document.getElementById('src-domain').value = this._discoveredData.domain || '';
document.getElementById('src-notes').value = ''; document.getElementById('src-notes').value = '';
const typeLabel = this._discoveredData.source_type === 'rss_feed' ? 'RSS-Feed' : 'Web-Quelle'; const typeLabel = this._discoveredData.source_type === 'rss_feed' ? LangManager.t('sources.rss_feed') : LangManager.t('sources.web_source');
document.getElementById('src-type-display').value = typeLabel; document.getElementById('src-type-display').value = typeLabel;
const rssGroup = document.getElementById('src-rss-url-group'); const rssGroup = document.getElementById('src-rss-url-group');
@@ -2603,13 +2646,12 @@ const App = {
document.getElementById('src-discovery-result').style.display = 'none'; document.getElementById('src-discovery-result').style.display = 'none';
if (result.added_count > 0) { if (result.added_count > 0) {
UI.showToast(`${result.domain}: ${result.added_count} Feeds hinzugefügt` + const key = result.skipped_count > 0 ? 'toast.discover_added_skipped' : 'toast.discover_added';
(result.skipped_count > 0 ? ` (${result.skipped_count} bereits vorhanden)` : ''), UI.showToast(LangManager.t(key, { domain: result.domain, count: result.added_count, skipped: result.skipped_count }), 'success');
'success');
} else if (result.skipped_count > 0) { } else if (result.skipped_count > 0) {
UI.showToast(`${result.domain}: Alle ${result.skipped_count} Feeds bereits vorhanden.`, 'info'); UI.showToast(LangManager.t('toast.discover_all_exist', { domain: result.domain, count: result.skipped_count }), 'info');
} else { } else {
UI.showToast(`${result.domain}: Keine relevanten Feeds gefunden.`, 'info'); UI.showToast(LangManager.t('toast.discover_none', { domain: result.domain }), 'info');
} }
this.toggleSourceForm(false); this.toggleSourceForm(false);
@@ -2653,7 +2695,7 @@ const App = {
document.getElementById('src-notes').value = source.notes || ''; document.getElementById('src-notes').value = source.notes || '';
document.getElementById('src-domain').value = source.domain || ''; document.getElementById('src-domain').value = source.domain || '';
const typeLabel = source.source_type === 'rss_feed' ? 'RSS-Feed' : 'Web-Quelle'; const typeLabel = source.source_type === 'rss_feed' ? LangManager.t('sources.rss_feed') : LangManager.t('sources.web_source');
document.getElementById('src-type-display').value = typeLabel; document.getElementById('src-type-display').value = typeLabel;
const rssGroup = document.getElementById('src-rss-url-group'); const rssGroup = document.getElementById('src-rss-url-group');
@@ -2745,6 +2787,15 @@ const App = {
this.updateSidebarStats(); this.updateSidebarStats();
// Update map tiles // Update map tiles
UI._applyMapTiles(); UI._applyMapTiles();
// Update accessibility panel labels
A11yManager._updateLabels();
// Update notification center labels
NotificationCenter._updateLabels();
// Update source management if visible
if (this._sourcesLoaded) {
this.renderSourceStats(this._lastSourceStats || { by_type: {}, total_articles: 0 });
this.renderSourceList();
}
}, },
}; };
@@ -2953,7 +3004,7 @@ function buildDetailedSourceOverview() {
// Nach Quelle gruppieren // Nach Quelle gruppieren
const sourceMap = {}; const sourceMap = {};
articles.forEach(a => { articles.forEach(a => {
const name = a.source || 'Unbekannt'; const name = a.source || LangManager.t('time.unknown');
if (!sourceMap[name]) sourceMap[name] = { articles: [], languages: new Set() }; if (!sourceMap[name]) sourceMap[name] = { articles: [], languages: new Set() };
sourceMap[name].articles.push(a); sourceMap[name].articles.push(a);
sourceMap[name].languages.add((a.language || 'de').toUpperCase()); sourceMap[name].languages.add((a.language || 'de').toUpperCase());
@@ -2973,7 +3024,7 @@ function buildDetailedSourceOverview() {
.join(''); .join('');
let html = `<div class="source-overview-header"> let html = `<div class="source-overview-header">
<span class="source-overview-stat">${articles.length} Artikel aus ${sources.length} Quellen</span> <span class="source-overview-stat">${LangManager.t('sources.articles_from_sources', { articles: articles.length, sources: sources.length })}</span>
<div class="source-lang-chips">${langChips}</div> <div class="source-lang-chips">${langChips}</div>
</div>`; </div>`;
@@ -2991,7 +3042,7 @@ function buildDetailedSourceOverview() {
data.articles.forEach(a => { data.articles.forEach(a => {
const headline = UI.escape(a.headline_de || a.headline || LangManager.t('map.no_title')); const headline = UI.escape(a.headline_de || a.headline || LangManager.t('map.no_title'));
const time = a.collected_at const time = a.collected_at
? new Date(a.collected_at).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) ? new Date(a.collected_at).toLocaleString(LangManager.lang === 'de' ? 'de-DE' : 'en-GB', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })
: ''; : '';
const langBadge = a.language && a.language !== 'de' const langBadge = a.language && a.language !== 'de'
? `<span class="lang-badge">${a.language.toUpperCase()}</span>` : ''; ? `<span class="lang-badge">${a.language.toUpperCase()}</span>` : '';
@@ -3045,8 +3096,8 @@ function updateSourcesHint() {
const hint = document.getElementById('sources-hint'); const hint = document.getElementById('sources-hint');
if (hint) { if (hint) {
hint.textContent = intl hint.textContent = intl
? 'DE + internationale Feeds (Reuters, BBC, Al Jazeera etc.)' ? LangManager.t('form.sources_hint_intl')
: 'Nur deutschsprachige Quellen (DE, AT, CH)'; : LangManager.t('form.sources_hint_de');
} }
} }
@@ -3056,11 +3107,11 @@ function toggleTypeDefaults() {
const refreshMode = document.getElementById('inc-refresh-mode'); const refreshMode = document.getElementById('inc-refresh-mode');
if (type === 'research') { if (type === 'research') {
hint.textContent = 'Nur WebSearch (Deep Research), manuelle Aktualisierung empfohlen'; hint.textContent = LangManager.t('form.type_hint_research');
refreshMode.value = 'manual'; refreshMode.value = 'manual';
toggleRefreshInterval(); toggleRefreshInterval();
} else { } else {
hint.textContent = 'RSS-Feeds + WebSearch, automatische Aktualisierung empfohlen'; hint.textContent = LangManager.t('form.type_hint_adhoc');
} }
} }
@@ -3129,7 +3180,7 @@ setInterval(() => {
} else if (remaining <= fiveMinutes && !App._sessionWarningShown) { } else if (remaining <= fiveMinutes && !App._sessionWarningShown) {
App._sessionWarningShown = true; App._sessionWarningShown = true;
const mins = Math.ceil(remaining / 60000); const mins = Math.ceil(remaining / 60000);
UI.showToast(`Session läuft in ${mins} Minute${mins !== 1 ? 'n' : ''} ab. Bitte erneut anmelden.`, 'warning', 15000); UI.showToast(LangManager.t('toast.session_expiring', { mins }), 'warning', 15000);
} }
} catch (e) { /* Token nicht parsbar */ } } catch (e) { /* Token nicht parsbar */ }
}, 60000); }, 60000);

Datei anzeigen

@@ -405,6 +405,25 @@ const TRANSLATIONS = {
// ── Miscellaneous ────────────────────────────────────── // ── Miscellaneous ──────────────────────────────────────
'misc.search_sources': { de: 'Quellen durchsuchen...', en: 'Search sources...' }, 'misc.search_sources': { de: 'Quellen durchsuchen...', en: 'Search sources...' },
'misc.incident': { de: 'Lage', en: 'Incident' },
'misc.articles_progress': { de: '{done}/{total} Artikel...', en: '{done}/{total} articles...' },
// ── Sidebar stats ─────────────────────────────────────
'sidebar.sources_count': { de: '{n} Quellen', en: '{n} sources' },
'sidebar.articles_count': { de: '{n} Artikel', en: '{n} articles' },
// ── Source discovery toasts ───────────────────────────
'toast.discover_added': { de: '{domain}: {count} Feeds hinzugefügt', en: '{domain}: {count} feeds added' },
'toast.discover_added_skipped': { de: '{domain}: {count} Feeds hinzugefügt ({skipped} bereits vorhanden)', en: '{domain}: {count} feeds added ({skipped} already exist)' },
'toast.discover_all_exist': { de: '{domain}: Alle {count} Feeds bereits vorhanden.', en: '{domain}: All {count} feeds already exist.' },
'toast.discover_none': { de: '{domain}: Keine relevanten Feeds gefunden.', en: '{domain}: No relevant feeds found.' },
// ── Source form hints ─────────────────────────────────
'form.sources_hint_intl': { de: 'DE + internationale Feeds (Reuters, BBC, Al Jazeera etc.)', en: 'DE + international feeds (Reuters, BBC, Al Jazeera etc.)' },
'form.sources_hint_de': { de: 'Nur deutschsprachige Quellen (DE, AT, CH)', en: 'German-language sources only (DE, AT, CH)' },
// ── Session warning ───────────────────────────────────
'toast.session_expiring': { de: 'Session läuft in {mins} Minute(n) ab. Bitte erneut anmelden.', en: 'Session expires in {mins} minute(s). Please log in again.' },
}; };
@@ -531,11 +550,11 @@ const LangManager = (() => {
* @returns {string} * @returns {string}
*/ */
function mapTileUrl() { function mapTileUrl() {
if (_lang === 'de') { // Immer deutsche OSM-Kacheln verwenden - tile.openstreetmap.org hat strikte Rate-Limits
// und zeigt bei Ueberschreitung graue Kacheln. Die DE-Tiles zeigen Ortsnamen
// die in beiden Sprachen lesbar sind.
return 'https://tile.openstreetmap.de/{z}/{x}/{y}.png'; return 'https://tile.openstreetmap.de/{z}/{x}/{y}.png';
} }
return 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
}
/* ---- expose ---- */ /* ---- expose ---- */
return { return {