diff --git a/app/static/js/contacts.js b/app/static/js/contacts.js index b722f0b..4f32a49 100644 --- a/app/static/js/contacts.js +++ b/app/static/js/contacts.js @@ -407,7 +407,7 @@ function applyCleanupSettingsToUI(settings) { if (statusText) { if (settings.enabled) { const hourStr = hour.toString().padStart(2, '0'); - statusText.textContent = `Enabled (runs daily at ${hourStr}:00 ${cleanupTimezone})`; + statusText.textContent = t('contacts.cleanup.auto_enabled', { time: `${hourStr}:00`, tz: cleanupTimezone }); statusText.classList.remove('text-muted'); statusText.classList.add('text-success'); } else { @@ -532,7 +532,7 @@ async function saveCleanupSettings(enabled) { if (data.settings.enabled) { const savedHour = data.settings.hour !== undefined ? data.settings.hour : 1; const hourStr = savedHour.toString().padStart(2, '0'); - statusText.textContent = `Enabled (runs daily at ${hourStr}:00 ${cleanupTimezone})`; + statusText.textContent = t('contacts.cleanup.auto_enabled', { time: `${hourStr}:00`, tz: cleanupTimezone }); statusText.classList.remove('text-muted'); statusText.classList.add('text-success'); } else { @@ -553,7 +553,7 @@ async function saveCleanupSettings(enabled) { if (autoCleanupSettings.enabled) { const prevHour = autoCleanupSettings.hour !== undefined ? autoCleanupSettings.hour : 1; const hourStr = prevHour.toString().padStart(2, '0'); - statusText.textContent = `Enabled (runs daily at ${hourStr}:00 ${cleanupTimezone})`; + statusText.textContent = t('contacts.cleanup.auto_enabled', { time: `${hourStr}:00`, tz: cleanupTimezone }); } else { statusText.textContent = t('common.disabled'); } @@ -740,9 +740,9 @@ async function handleCleanupConfirm() { if (modal) modal.hide(); // Show success message - let message = `Cleanup completed: ${data.deleted_count} deleted`; + let message = t('contacts.cleanup.completed', { count: data.deleted_count }); if (data.failed_count > 0) { - message += `, ${data.failed_count} failed`; + message += t('contacts.cleanup.completed_failed', { count: data.failed_count }); } showToast(message, data.failed_count > 0 ? 'warning' : 'success'); @@ -763,7 +763,7 @@ async function handleCleanupConfirm() { } } catch (error) { console.error('Error during cleanup:', error); - showToast('Network error during cleanup', 'danger'); + showToast(t('contacts.cleanup.network_error'), 'danger'); } finally { // Re-enable button confirmBtn.disabled = false; @@ -1272,8 +1272,8 @@ function renderPendingList(contacts) { emptyDiv.className = 'empty-state'; emptyDiv.innerHTML = ` -
No contacts match filters
- Try changing your filter criteria +${tHtml('contacts.filter.no_match')}
+ ${tHtml('contacts.filter.no_match_hint')} `; listEl.appendChild(emptyDiv); return; @@ -1336,7 +1336,7 @@ function createContactCard(contact, index) { lastAdvertDiv = document.createElement('div'); lastAdvertDiv.className = 'text-muted small'; const relativeTime = formatRelativeTime(contact.last_advert); - lastAdvertDiv.textContent = `Last seen: ${relativeTime}`; + lastAdvertDiv.textContent = t('repeaters.map.last_seen', { time: relativeTime }); } // Action buttons @@ -1559,7 +1559,7 @@ async function batchApproveContacts() { // Update button with progress if (confirmBtn) { - confirmBtn.innerHTML = ` Approving ${i + 1}/${filteredPendingContacts.length}...`; + confirmBtn.innerHTML = ` ${tHtml('contacts.progress.approving', { done: i + 1, total: filteredPendingContacts.length })}`; } try { @@ -1667,7 +1667,7 @@ async function batchIgnoreContacts() { const contact = filteredPendingContacts[i]; if (confirmBtn) { - confirmBtn.innerHTML = ` Ignoring ${i + 1}/${filteredPendingContacts.length}...`; + confirmBtn.innerHTML = ` ${tHtml('contacts.progress.ignoring', { done: i + 1, total: filteredPendingContacts.length })}`; } try { @@ -2445,7 +2445,7 @@ async function confirmDelete() { // ============================================================================= async function pushContactToDevice(contact) { - if (!confirm(`Push "${contact.name}" to device?`)) return; + if (!confirm(t('contacts.confirm.push', { name: contact.name }))) return; try { const response = await fetch(`/api/contacts/${contact.public_key}/push-to-device`, { @@ -2454,7 +2454,7 @@ async function pushContactToDevice(contact) { }); const data = await response.json(); if (data.success) { - showToast(data.message || `${contact.name} pushed to device`, 'success'); + showToast(data.message || t('contacts.toast.pushed', { name: contact.name }), 'success'); setTimeout(() => loadExistingContacts(), 500); } else { showToast(data.error || t('contacts.toast.push_failed'), 'danger'); @@ -2465,7 +2465,7 @@ async function pushContactToDevice(contact) { } async function moveContactToCache(contact) { - if (!confirm(`Move "${contact.name}" from device to cache?`)) return; + if (!confirm(t('contacts.confirm.to_cache', { name: contact.name }))) return; try { const response = await fetch(`/api/contacts/${contact.public_key}/move-to-cache`, { @@ -2474,7 +2474,7 @@ async function moveContactToCache(contact) { }); const data = await response.json(); if (data.success) { - showToast(data.message || `${contact.name} moved to cache`, 'success'); + showToast(data.message || t('contacts.toast.to_cache', { name: contact.name }), 'success'); setTimeout(() => loadExistingContacts(), 500); } else { showToast(data.error || t('contacts.toast.move_failed'), 'danger'); @@ -2644,7 +2644,7 @@ function onQrCodeSuccess(decodedText) { return; } - showQrError('QR code does not contain a valid meshcore:// URI.'); + showQrError(t('contacts.add.qr_invalid_uri')); } function showQrError(msg) { diff --git a/app/static/js/path-analyzer.js b/app/static/js/path-analyzer.js index 4787b7b..7da2408 100644 --- a/app/static/js/path-analyzer.js +++ b/app/static/js/path-analyzer.js @@ -226,7 +226,7 @@ function paBuildEchoLine(msg, echo, echoIdx) { const chip = document.createElement('span'); chip.className = 'pa-chip'; chip.textContent = tok; - chip.title = `Copy repeater hash ${tok}`; + chip.title = t('pa.copy_hash_title', { hash: tok }); chip.addEventListener('click', (e) => { e.stopPropagation(); paCopyText(tok, t('pa.repeater_hash')); @@ -457,7 +457,7 @@ function paRenderStats() { for (const s of rows) { const tr = document.createElement('tr'); tr.className = 'pa-stats-row'; - tr.title = `Filter messages by ${s.token}`; + tr.title = t('pa.filter_by_title', { token: s.token }); const tdToken = document.createElement('td'); tdToken.innerHTML = `${s.token}`; @@ -576,7 +576,7 @@ function paRenderRoutes() { const names = r.tokens.map(tok => { const cands = paMatchContacts(tok); if (cands.length === 1) return cands[0].name || '—'; - return cands.length > 1 ? `ambiguous (${cands.length})` : '—'; + return cands.length > 1 ? t('pa.ambiguous', { count: cands.length }) : '—'; }); if (names.some(nm => nm !== '—')) { const nameLine = document.createElement('div'); @@ -610,7 +610,7 @@ function paRenderRoutes() { if (rows.length === 0) { document.getElementById('paEmptyText').textContent = filtered.length === 0 ? t('pa.empty_filtered') - : `No paths with at least ${n} hops in the current selection.`; + : t('pa.no_paths_min_hops', { count: n }); paSetView('empty'); } else { paSetView('routes'); @@ -674,8 +674,8 @@ function paAddMapToggles() { ctl.onAdd = () => { const box = L.DomUtil.create('div', 'pa-map-toggles'); box.innerHTML = - '' + - ''; + `` + + ``; L.DomEvent.disableClickPropagation(box); L.DomEvent.disableScrollPropagation(box); return box; @@ -747,7 +747,7 @@ function paDrawEcho(msg, echo, primary, seen, altColor) { if (primary) { L.circleMarker([origin.adv_lat, origin.adv_lon], { radius: 7, color: '#198754', weight: 2, fillOpacity: 0.8 - }).bindPopup(`${origin.name}${tok}`)
+ .bindPopup(`${contact.name}${tok}` })}`)
.bindTooltip(contact.name, { permanent: true, direction: 'right', offset: [13, 0], className: 'pa-hop-label' })
.addTo(layer);
}
@@ -771,7 +771,7 @@ function paDrawEcho(msg, echo, primary, seen, altColor) {
candidates.forEach(c => {
L.circleMarker([c.adv_lat, c.adv_lon], {
radius: 6, color: '#d39e00', weight: 2, fillOpacity: 0.5, dashArray: '3'
- }).bindPopup(`${c.name}${tok}`).addTo(layer);
+ }).bindPopup(`${c.name}${tok}` })}`).addTo(layer);
});
}
gapPending = true;
@@ -783,7 +783,7 @@ function paDrawEcho(msg, echo, primary, seen, altColor) {
// only once - an alternative then shows exactly where it diverges instead of
// hiding under the primary route.
const snr = (echo.snr === null || echo.snr === undefined) ? '?' : `${Number(echo.snr).toFixed(1)} dB`;
- const popup = `Alternative pathRequesting status from the repeater…
+${tHtml('rptmgmt.status.loading')}
${esc((data && data.error) || t('rptmgmt.status_failed'))}
Requesting telemetry from the repeater…
- Multi-hop paths can take up to a minute.
${tHtml('rptmgmt.tel.loading')}
+ ${tHtml('rptmgmt.tel.loading_hint')}
${esc((data && data.error) || t('rptmgmt.telemetry_failed'))}
Requesting neighbours from the repeater…
- Long lists are fetched in pages and can take a while.
${tHtml('rptmgmt.neigh.loading')}
+ ${tHtml('rptmgmt.neigh.loading_hint')}
${esc((data && data.error) || t('rptmgmt.neighbours_failed'))}
- Settings are read live from the repeater. Expand a section to load it — - every field is one mesh round-trip, so a section can take a few seconds. -
+${tHtml('rptmgmt.set.intro')}