diff --git a/app/static/js/path-analyzer.js b/app/static/js/path-analyzer.js index a232881..4787b7b 100644 --- a/app/static/js/path-analyzer.js +++ b/app/static/js/path-analyzer.js @@ -196,15 +196,15 @@ function paFormatTime(msg) { function paCopyText(text, label) { navigator.clipboard.writeText(text).then( - () => showNotification(`${label} copied to clipboard`, 'success'), - () => showNotification(`Failed to copy ${label}`, 'danger') + () => showNotification(t('pa.toast.copied', { what: label }), 'success'), + () => showNotification(t('pa.toast.copy_failed', { what: label }), 'danger') ); } function paBuildEchoLine(msg, echo, echoIdx) { const line = document.createElement('div'); line.className = 'pa-echo-line'; - line.title = 'Click to copy route'; + line.title = t('pa.copy_route_title'); const dirBadge = document.createElement('span'); dirBadge.className = 'badge ' + (echo.direction === 'outgoing' ? 'text-bg-primary' : 'text-bg-secondary'); @@ -214,7 +214,7 @@ function paBuildEchoLine(msg, echo, echoIdx) { if (echo.hops === 0) { const direct = document.createElement('span'); direct.className = 'pa-direct'; - direct.textContent = 'Direct (flood, 0 hops)'; + direct.textContent = t('pa.direct_flood'); line.appendChild(direct); } else { echo.tokens.forEach((tok, i) => { @@ -229,7 +229,7 @@ function paBuildEchoLine(msg, echo, echoIdx) { chip.title = `Copy repeater hash ${tok}`; chip.addEventListener('click', (e) => { e.stopPropagation(); - paCopyText(tok, 'Repeater hash'); + paCopyText(tok, t('pa.repeater_hash')); }); line.appendChild(chip); }); @@ -244,7 +244,7 @@ function paBuildEchoLine(msg, echo, echoIdx) { if (echo.hops > 0) { const mapBtn = document.createElement('i'); mapBtn.className = 'bi bi-map pa-echo-mapbtn'; - mapBtn.title = 'Show this path on the map'; + mapBtn.title = t('pa.show_on_map_title'); mapBtn.addEventListener('click', (e) => { e.stopPropagation(); paMapSelection = { msgId: msg.id, echoIdx: echoIdx }; @@ -286,7 +286,9 @@ function paRenderTable() { tr.appendChild(tdChannel); const tdSender = document.createElement('td'); - tdSender.textContent = msg.is_own ? `${msg.sender || 'Me'} (own)` : (msg.sender || '—'); + tdSender.textContent = msg.is_own + ? t('pa.sender_own', { name: msg.sender || t('pa.me') }) + : (msg.sender || '—'); tr.appendChild(tdSender); const tdContent = document.createElement('td'); @@ -303,14 +305,14 @@ function paRenderTable() { const span = document.createElement('span'); span.className = 'pa-hash'; span.textContent = msg.packet_hash; - span.title = 'Click to copy'; + span.title = t('pa.copy_title'); span.addEventListener('click', (e) => { e.stopPropagation(); - paCopyText(msg.packet_hash, 'Packet hash'); + paCopyText(msg.packet_hash, t('pa.packet_hash')); }); tdHash.appendChild(span); } else { - tdHash.innerHTML = 'no path data'; + tdHash.innerHTML = `${tHtml('pa.no_path_data')}`; } tr.appendChild(tdHash); @@ -345,10 +347,10 @@ function paRenderTable() { const hashSpan = document.createElement('span'); hashSpan.className = 'pa-hash'; hashSpan.textContent = msg.packet_hash; - hashSpan.title = 'Click to copy'; + hashSpan.title = t('pa.copy_title'); hashSpan.addEventListener('click', (e) => { e.stopPropagation(); - paCopyText(msg.packet_hash, 'Packet hash'); + paCopyText(msg.packet_hash, t('pa.packet_hash')); }); hashLine.append('Hash: '); hashLine.appendChild(hashSpan); @@ -370,13 +372,13 @@ function paRenderTable() { const counter = document.getElementById('paCounter'); counter.textContent = paFiltersActive() - ? `${filtered.length} of ${paMessages.length} messages` - : `${paMessages.length} message${paMessages.length === 1 ? '' : 's'}`; + ? t('pa.counter.filtered', { shown: filtered.length, total: paMessages.length }) + : tn('pa.counter.messages', paMessages.length); // Empty state when filters exclude everything if (paMessages.length > 0) { if (filtered.length === 0) { - document.getElementById('paEmptyText').textContent = 'No messages match the current filters.'; + document.getElementById('paEmptyText').textContent = t('pa.empty_filtered'); paSetView('empty'); } else { paSetView('table'); @@ -495,13 +497,13 @@ function paRenderStats() { const counter = document.getElementById('paCounter'); counter.textContent = paFiltersActive() - ? `${rows.length} repeaters (${filtered.length} of ${paMessages.length} messages)` - : `${rows.length} repeaters (${paMessages.length} messages)`; + ? `${tn('pa.counter.repeaters', rows.length)} (${t('pa.counter.filtered', { shown: filtered.length, total: paMessages.length })})` + : `${tn('pa.counter.repeaters', rows.length)} (${tn('pa.counter.messages', paMessages.length)})`; if (rows.length === 0) { document.getElementById('paEmptyText').textContent = - filtered.length === 0 ? 'No messages match the current filters.' - : 'No routed echoes in the current selection.'; + filtered.length === 0 ? t('pa.empty_filtered') + : t('pa.empty_no_echoes'); paSetView('empty'); } else { paSetView('stats'); @@ -561,7 +563,7 @@ function paRenderRoutes() { for (const r of rows) { const tr = document.createElement('tr'); tr.className = 'pa-stats-row'; - tr.title = 'Filter messages by this segment'; + tr.title = t('pa.filter_by_segment_title'); const tdRoute = document.createElement('td'); const hashLine = document.createElement('div'); @@ -602,12 +604,12 @@ function paRenderRoutes() { const counter = document.getElementById('paCounter'); counter.textContent = paFiltersActive() - ? `${rows.length} segments (${filtered.length} of ${paMessages.length} messages)` - : `${rows.length} segments (${paMessages.length} messages)`; + ? `${tn('pa.counter.segments', rows.length)} (${t('pa.counter.filtered', { shown: filtered.length, total: paMessages.length })})` + : `${tn('pa.counter.segments', rows.length)} (${tn('pa.counter.messages', paMessages.length)})`; if (rows.length === 0) { document.getElementById('paEmptyText').textContent = - filtered.length === 0 ? 'No messages match the current filters.' + filtered.length === 0 ? t('pa.empty_filtered') : `No paths with at least ${n} hops in the current selection.`; paSetView('empty'); } else { @@ -850,8 +852,8 @@ function paBuildLegend(echo) { resetRow.className = 'text-end'; const resetBtn = document.createElement('button'); resetBtn.className = 'btn btn-sm btn-outline-warning py-0 pa-reset-picks'; - resetBtn.innerHTML = ' Reset picks'; - resetBtn.title = 'Clear all manual repeater assignments on this path'; + resetBtn.innerHTML = ` ${tHtml('pa.reset_picks')}`; + resetBtn.title = t('pa.reset_picks_title'); resetBtn.addEventListener('click', (e) => { e.stopPropagation(); echo.tokens.forEach(tok => { delete paPicks[tok]; }); @@ -876,7 +878,7 @@ function paBuildLegend(echo) { // Manually assigned - allow undoing just this hop const undo = document.createElement('i'); undo.className = 'bi bi-x-circle pa-unpick'; - undo.title = 'Undo this manual assignment'; + undo.title = t('pa.undo_pick_title'); undo.addEventListener('click', (e) => { e.stopPropagation(); delete paPicks[tok]; @@ -887,13 +889,13 @@ function paBuildLegend(echo) { } else if (candidates.length > 1) { const amb = document.createElement('span'); amb.className = 'pa-ambiguous'; - amb.textContent = `— ${candidates.length} candidates:`; + amb.textContent = '— ' + tn('pa.candidates', candidates.length) + ':'; row.appendChild(amb); candidates.forEach(c => { const chip = document.createElement('span'); chip.className = 'pa-pick-chip' + (paPicks[tok] === c.public_key ? ' pa-picked' : ''); chip.textContent = c.name || c.public_key.slice(0, 8); - chip.title = 'Use this contact for the hop'; + chip.title = t('pa.use_contact_title'); chip.addEventListener('click', (e) => { e.stopPropagation(); paPicks[tok] = c.public_key; @@ -904,7 +906,7 @@ function paBuildLegend(echo) { } else { const unk = document.createElement('span'); unk.className = 'pa-ambiguous'; - unk.textContent = '— unknown / no position'; + unk.textContent = t('pa.unknown_position'); row.appendChild(unk); } legend.appendChild(row); @@ -944,7 +946,7 @@ function paRenderMapView(fit = true) { } if (filtered.length === 0) { - list.innerHTML = '
No messages with routed echoes match the current filters.
'; + list.innerHTML = `
${tHtml('pa.map.no_routed')}
`; } for (const msg of filtered) { @@ -1032,8 +1034,8 @@ function paRenderMapView(fit = true) { const counter = document.getElementById('paCounter'); counter.textContent = paFiltersActive() - ? `${filtered.length} of ${paMessages.length} messages` - : `${filtered.length} routed message${filtered.length === 1 ? '' : 's'}`; + ? t('pa.counter.filtered', { shown: filtered.length, total: paMessages.length }) + : tn('pa.counter.routed', filtered.length); } function paClearMapSelection() { @@ -1086,12 +1088,12 @@ async function paLoadMessages() { }); } catch (e) { console.error('Failed to load messages:', e); - showNotification(`Failed to load messages: ${e.message}`, 'danger'); + showNotification(t('pa.toast.load_failed', { error: e.message }), 'danger'); paMessages = []; } if (paMessages.length === 0) { - document.getElementById('paEmptyText').textContent = 'No messages in the selected time range.'; + document.getElementById('paEmptyText').textContent = t('pa.empty_range'); paSetView('empty'); } else { paRender(); @@ -1118,7 +1120,7 @@ async function paApplyDeepLink() { return; } paDeepLink = null; - showNotification('This message is no longer in the analyzer data (max 7 days).', 'warning'); + showNotification(t('pa.toast.msg_gone'), 'warning'); return; } @@ -1129,7 +1131,7 @@ async function paApplyDeepLink() { e.hops > 0 && (e.path || '').toLowerCase() === (dl.path || '').toLowerCase()); if (echoIdx === -1) echoIdx = paShortestEchoIdx(msg); if (echoIdx === null) { - showNotification('This message has no routed echoes to draw.', 'warning'); + showNotification(t('pa.toast.no_echoes'), 'warning'); return; } diff --git a/app/templates/path-analyzer.html b/app/templates/path-analyzer.html index 1c9462f..e0e89b4 100644 --- a/app/templates/path-analyzer.html +++ b/app/templates/path-analyzer.html @@ -3,7 +3,7 @@ - Path Analyzer - mc-webui + {{ t('pa.title') }} - mc-webui