From cd9a3cb397f35bd133f45aff6d743f7ec1e87de2 Mon Sep 17 00:00:00 2001 From: MarekWo Date: Sat, 1 Aug 2026 09:54:02 +0200 Subject: [PATCH] feat(i18n): translate the remaining base.html modals (stage 7b) Manage Channels, region scope picker, Share Channel, Device Info, analyzer registry (edit + chooser), Observer MQTT broker editor, coordinate picker, quote dialog, map, update, backup and search. The Settings modal (base.html ~458-1147) is still untouched - stage 8. Where the two share markup, this commit anchors on the container id rather than the markup: the "Loading..." spinner appears six times in base.html and only three of them are in this slice. Also in app.js, on the same rule as 7a - the writer that replaces a string this slice keyed: the analyzer and broker modal titles (they swap Add/Edit on open), the map modal title, the coordinate picker label, and the database size line under Optimize. Three real misses came out of the browser diff, none of which the checker could see: - Cancel/Save in the analyzer and broker editors were never keyed. They only surfaced once because the diff dedupes by text, so a single report line stood for both modals. - The database size under Optimize is written by JS on open, so the translated server-rendered value was replaced a moment later. - All three Device Info tabs render their bodies from app.js. Excluded from the check and left whole for stage 9 rather than translating the Share tab alone. Left English on purpose: FTS5 query examples (hello OR world, hell*) - the syntax is typed verbatim in any language, only what each one means is translated - plus mqtt.example.com and the {packetHash} token. Catalog: 573 -> 651 keys, pl at 100%. Verified with a new i18n-stage7b-modals.js that opens each modal through the Bootstrap API instead of its real trigger, since several are only reachable from a row or message that may not exist on a given device. Co-Authored-By: Claude Opus 5 --- app/static/js/app.js | 16 ++-- app/templates/base.html | 185 +++++++++++++++++++-------------------- app/translations/en.json | 80 ++++++++++++++++- app/translations/pl.json | 80 ++++++++++++++++- docs/whatsnew.md | 2 +- 5 files changed, 259 insertions(+), 104 deletions(-) diff --git a/app/static/js/app.js b/app/static/js/app.js index 9ba540d..c131096 100644 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -248,7 +248,7 @@ function updateMapMarkers() { async function showAllContactsOnMap() { const modalEl = document.getElementById('mapModal'); const modal = new bootstrap.Modal(modalEl); - document.getElementById('mapModalTitle').textContent = 'All Contacts'; + document.getElementById('mapModalTitle').textContent = t('map.all_contacts'); // Show type filter panel const filterPanel = document.getElementById('mapTypeFilter'); @@ -2443,7 +2443,7 @@ function openCoordPicker() { const confirmBtn = document.getElementById('coordPickerConfirmBtn'); const label = document.getElementById('coordPickerLabel'); if (confirmBtn) confirmBtn.disabled = true; - if (label) label.textContent = 'Click on the map to select coordinates'; + if (label) label.textContent = t('coord.hint'); const modal = new bootstrap.Modal(modalEl); @@ -3529,13 +3529,13 @@ function openAnalyzerEditModal(id) { if (id) { const a = (window.analyzerCache.analyzers || []).find(x => x.id === id); if (!a) return; - titleEl.textContent = 'Edit analyzer'; + titleEl.textContent = t('analyzer.edit'); idEl.value = String(a.id); nameEl.value = a.name || ''; urlEl.value = a.url_template || ''; enabledEl.checked = !a.is_disabled; } else { - titleEl.textContent = 'Add analyzer'; + titleEl.textContent = t('analyzer.add'); idEl.value = ''; nameEl.value = ''; urlEl.value = ''; @@ -3878,7 +3878,7 @@ function openObserverBrokerModal(id) { if (id) { const b = (window.observerCache.brokers || []).find(x => x.id === id); if (!b) return; - titleEl.textContent = 'Edit broker'; + titleEl.textContent = t('observer.edit_broker'); idEl.value = String(b.id); nameEl.value = b.name || ''; hostEl.value = b.host || ''; @@ -3889,7 +3889,7 @@ function openObserverBrokerModal(id) { enabledEl.checked = !b.is_disabled; passHintEl.classList.toggle('d-none', !b.has_password); } else { - titleEl.textContent = 'Add broker'; + titleEl.textContent = t('observer.add_broker'); idEl.value = ''; nameEl.value = ''; hostEl.value = ''; @@ -7024,7 +7024,7 @@ async function loadDatabaseSize() { const response = await fetch('/api/db/size'); const data = await response.json(); if (data.success) { - statusEl.textContent = `Current size: ${_formatBytes(data.size)}`; + statusEl.textContent = t('backup.size', { size: _formatBytes(data.size) }); } else { statusEl.textContent = 'Size: unknown'; } @@ -7082,7 +7082,7 @@ async function optimizeDatabase() { if (status.success === true && status.size_after !== undefined) { const freed = status.freed > 0 ? `freed ${_formatBytes(status.freed)}` : 'no space to reclaim'; showNotification(`Optimized: ${freed} in ${status.elapsed_seconds}s`, 'success'); - if (statusEl) statusEl.textContent = `Current size: ${_formatBytes(status.size_after)}`; + if (statusEl) statusEl.textContent = t('backup.size', { size: _formatBytes(status.size_after) }); } else if (status.error) { showNotification('Optimize failed: ' + status.error, 'danger'); loadDatabaseSize(); diff --git a/app/templates/base.html b/app/templates/base.html index d238fdb..0ac0e26 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -271,43 +271,43 @@