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 @@
Checking for updates...
- +{{ t('update.checking') }}
+Updating...
+{{ t('update.updating') }}
- Reclaim space freed by message retention. Runs SQLite VACUUM;
- readers keep working but writes are paused for a few seconds.
+ {{ t_html('backup.optimize_hint') }}
hello world — messages containing both words"hello world" — exact phrasehello OR world — either wordhello NOT world — hello but not worldhell* — prefix match (hello,hellas...)hello world — {{ t('search.tip_and') }}"hello world" — {{ t('search.tip_phrase') }}hello OR world — {{ t('search.tip_or') }}hello NOT world — {{ t('search.tip_not') }}hell* — {{ t('search.tip_prefix') }}Search across all channel and direct messages
+{{ t('search.empty') }}
{packetHash}.",
+ "backup.create": "Create Backup",
+ "backup.current_size": "Current size: …",
+ "backup.optimize_hint": "Reclaim space freed by message retention. Runs SQLite VACUUM; readers keep working but writes are paused for a few seconds.",
+ "backup.optimize_now": "Optimize now",
+ "backup.optimize_title": "Optimize database",
+ "backup.size": "Current size: {size}",
+ "backup.title": "Database Backup",
+ "channels.add_new": "Add New Channel",
+ "channels.create_btn": "Create & Auto-generate Key",
+ "channels.create_title": "Create New Channel",
+ "channels.join_btn": "Join Channel",
+ "channels.join_existing": "Join Existing",
+ "channels.join_title": "Join Existing Channel",
+ "channels.key": "Channel Key:",
+ "channels.key_label": "Channel Key (32 hex chars)",
+ "channels.key_optional": "- optional for channels starting with #",
+ "channels.key_ph": "485af7e164459d280d8818d9c99fb30d (leave empty for # channels)",
+ "channels.name": "Channel Name",
+ "channels.name_hint": "Only letters, numbers, _ and -",
+ "channels.name_ph": "e.g., Malopolska",
+ "channels.qr_alt": "QR Code",
+ "channels.region_hint": "Only repeaters allowing the selected region will forward messages from this channel.",
+ "channels.region_scope_for": "Set Region Scope for",
+ "channels.scan_qr": "Scan QR Code",
+ "channels.share_hint": "Share this QR code or key with others to let them join this channel.",
+ "channels.share_title": "Share Channel",
+ "channels.your": "Your Channels",
"chat.channels": "Channels",
"chat.copy_route_title": "Tap to copy route",
"chat.emoji_title": "Insert emoji",
@@ -27,6 +60,7 @@
"common.connected": "Connected",
"common.connecting": "Connecting...",
"common.copied": "Copied!",
+ "common.copy": "Copy",
"common.days_ago": {
"one": "{count}d ago",
"other": "{count}d ago"
@@ -39,6 +73,7 @@
"common.deleting": "Deleting...",
"common.disabled": "Disabled",
"common.disconnected": "Disconnected",
+ "common.enabled": "Enabled",
"common.failed_with": "Failed: {error}",
"common.hours_ago": "{count}h ago",
"common.hours_ago_long": {
@@ -58,6 +93,7 @@
},
"common.move_down": "Move down",
"common.move_up": "Move up",
+ "common.name": "Name",
"common.network_error": "Network error",
"common.network_error_detail": "Network error: {error}",
"common.never": "Never",
@@ -214,6 +250,15 @@
"contacts.toast.settings_load_error": "Error loading settings",
"contacts.toast.settings_network_error": "Network error saving settings",
"contacts.toast.settings_save_failed": "Failed to save settings: {error}",
+ "coord.confirm": "Confirm",
+ "coord.hint": "Click on the map to select coordinates",
+ "coord.title": "Pick Coordinates",
+ "device.share_hint": "Click to generate share code",
+ "device.stats_hint": "Click to load stats",
+ "device.tab.info": "Info",
+ "device.tab.share": "Share",
+ "device.tab.stats": "Stats",
+ "device.title": "Device",
"dm.attempt": "Attempt {n}/{max}",
"dm.auto_retry": "Auto Retry",
"dm.auto_retry_title": "Auto Retry: resend DM if no ACK received",
@@ -275,6 +320,7 @@
"logs.loading": "Loading logs...",
"logs.pause_title": "Pause/Resume",
"logs.title": "System Log",
+ "map.all_contacts": "All Contacts",
"menu.advert": "Send Advert",
"menu.advert_desc": "Announce presence (normal)",
"menu.advert_title": "Send single advertisement (recommended for normal operation)",
@@ -317,6 +363,17 @@
"nav.mark_all_read_title": "Mark all as read",
"nav.select_channel_title": "Select channel",
"nav.tcp_title": "TCP connection",
+ "observer.add_broker": "Add broker",
+ "observer.edit_broker": "Edit broker",
+ "observer.host": "Host",
+ "observer.name_ph": "e.g. My MQTT server",
+ "observer.password": "Password",
+ "observer.password_hint": "Leave the password blank to keep the current one.",
+ "observer.port": "Port",
+ "observer.ports_hint": "Typical ports: 1883 (plain), 8883 (TLS).",
+ "observer.use_tls": "Use TLS",
+ "observer.username": "Username",
+ "observer.verify_tls": "Verify TLS certificate",
"pa.any_hb": "Any HB",
"pa.any_hops": "Any hops",
"pa.byte_2_3": "2/3-byte",
@@ -431,6 +488,10 @@
"pa.view.routes": "Routes",
"pa.view.routes_title": "Route segment statistics",
"pa.view_aria": "View",
+ "quote.full": "Full quote",
+ "quote.max_bytes": "Max bytes:",
+ "quote.title": "Quote message",
+ "quote.truncated": "Truncated",
"repeaters.add": "Add repeater",
"repeaters.add_loading": "Loading device contacts...",
"repeaters.add_modal_title": "Add Repeater",
@@ -655,5 +716,22 @@
"rptmgmt.tools.telemetry": "Telemetry",
"rptmgmt.tools.telemetry_desc": "Sensor channels (Cayenne LPP)",
"rptmgmt.tools_heading": "Management Tools",
- "rptmgmt.updated_just_now": "Updated just now"
+ "rptmgmt.updated_just_now": "Updated just now",
+ "search.empty": "Search across all channel and direct messages",
+ "search.fts5_ref": "Full FTS5 syntax reference",
+ "search.help_title": "Search syntax help",
+ "search.ph": "Search all messages...",
+ "search.special_chars": "Special characters (. , - :) should be wrapped in quotes.",
+ "search.tip_and": "messages containing both words",
+ "search.tip_not": "hello but not world",
+ "search.tip_or": "either word",
+ "search.tip_phrase": "exact phrase",
+ "search.tip_prefix": "prefix match (hello,hellas...)",
+ "search.tips": "Search tips:",
+ "update.checking": "Checking for updates...",
+ "update.now": "Update Now",
+ "update.reload": "Reload Page",
+ "update.title": "Update mc-webui",
+ "update.updating": "Updating...",
+ "update.whats_new": "What's new?"
}
diff --git a/app/translations/pl.json b/app/translations/pl.json
index 51145dd..443ca70 100644
--- a/app/translations/pl.json
+++ b/app/translations/pl.json
@@ -1,4 +1,37 @@
{
+ "analyzer.add": "Dodaj analizator",
+ "analyzer.choose": "Wybierz analizator",
+ "analyzer.edit": "Edytuj analizator",
+ "analyzer.name_ph": "np. Mój analizator",
+ "analyzer.url": "Szablon URL",
+ "analyzer.url_hint": "Musi zawierać {packetHash}.",
+ "backup.create": "Utwórz kopię",
+ "backup.current_size": "Bieżący rozmiar: …",
+ "backup.optimize_hint": "Odzyskaj miejsce zwolnione przez retencję wiadomości. Uruchamia SQLite VACUUM; odczyty działają, ale zapisy są wstrzymane na kilka sekund.",
+ "backup.optimize_now": "Optymalizuj teraz",
+ "backup.optimize_title": "Optymalizuj bazę danych",
+ "backup.size": "Bieżący rozmiar: {size}",
+ "backup.title": "Kopia zapasowa bazy danych",
+ "channels.add_new": "Dodaj nowy kanał",
+ "channels.create_btn": "Utwórz i wygeneruj klucz",
+ "channels.create_title": "Utwórz nowy kanał",
+ "channels.join_btn": "Dołącz do kanału",
+ "channels.join_existing": "Dołącz do istniejącego",
+ "channels.join_title": "Dołącz do istniejącego kanału",
+ "channels.key": "Klucz kanału:",
+ "channels.key_label": "Klucz kanału (32 znaki hex)",
+ "channels.key_optional": "- opcjonalny dla kanałów zaczynających się od #",
+ "channels.key_ph": "485af7e164459d280d8818d9c99fb30d (zostaw puste dla kanałów #)",
+ "channels.name": "Nazwa kanału",
+ "channels.name_hint": "Tylko litery, cyfry, _ i -",
+ "channels.name_ph": "np. Malopolska",
+ "channels.qr_alt": "Kod QR",
+ "channels.region_hint": "Tylko repeatery zezwalające na wybrany region będą przekazywać wiadomości z tego kanału.",
+ "channels.region_scope_for": "Ustaw zakres regionu dla",
+ "channels.scan_qr": "Zeskanuj kod QR",
+ "channels.share_hint": "Udostępnij ten kod QR lub klucz innym, aby mogli dołączyć do tego kanału.",
+ "channels.share_title": "Udostępnij kanał",
+ "channels.your": "Twoje kanały",
"chat.channels": "Kanały",
"chat.copy_route_title": "Dotknij, aby skopiować trasę",
"chat.emoji_title": "Wstaw emoji",
@@ -27,6 +60,7 @@
"common.connected": "Połączono",
"common.connecting": "Łączenie...",
"common.copied": "Skopiowano!",
+ "common.copy": "Kopiuj",
"common.days_ago": {
"one": "{count} dzień temu",
"few": "{count} dni temu",
@@ -43,6 +77,7 @@
"common.deleting": "Usuwanie...",
"common.disabled": "Wyłączone",
"common.disconnected": "Rozłączono",
+ "common.enabled": "Włączone",
"common.failed_with": "Niepowodzenie: {error}",
"common.hours_ago": "{count} godz. temu",
"common.hours_ago_long": {
@@ -68,6 +103,7 @@
},
"common.move_down": "Przesuń w dół",
"common.move_up": "Przesuń w górę",
+ "common.name": "Nazwa",
"common.network_error": "Błąd sieci",
"common.network_error_detail": "Błąd sieci: {error}",
"common.never": "Nigdy",
@@ -232,6 +268,15 @@
"contacts.toast.settings_load_error": "Błąd wczytywania ustawień",
"contacts.toast.settings_network_error": "Błąd sieci przy zapisie ustawień",
"contacts.toast.settings_save_failed": "Nie udało się zapisać ustawień: {error}",
+ "coord.confirm": "Zatwierdź",
+ "coord.hint": "Kliknij na mapie, aby wybrać współrzędne",
+ "coord.title": "Wybierz współrzędne",
+ "device.share_hint": "Kliknij, aby wygenerować kod udostępniania",
+ "device.stats_hint": "Kliknij, aby wczytać statystyki",
+ "device.tab.info": "Informacje",
+ "device.tab.share": "Udostępnij",
+ "device.tab.stats": "Statystyki",
+ "device.title": "Urządzenie",
"dm.attempt": "Próba {n}/{max}",
"dm.auto_retry": "Auto ponawianie",
"dm.auto_retry_title": "Auto ponawianie: wyślij DM ponownie, jeśli nie przyjdzie ACK",
@@ -299,6 +344,7 @@
"logs.loading": "Ładowanie dziennika...",
"logs.pause_title": "Wstrzymaj/Wznów",
"logs.title": "Dziennik systemowy",
+ "map.all_contacts": "Wszystkie kontakty",
"menu.advert": "Wyślij advert",
"menu.advert_desc": "Ogłoś obecność (normalnie)",
"menu.advert_title": "Wyślij pojedynczy advert (zalecane przy normalnej pracy)",
@@ -341,6 +387,17 @@
"nav.mark_all_read_title": "Oznacz wszystko jako przeczytane",
"nav.select_channel_title": "Wybierz kanał",
"nav.tcp_title": "Połączenie TCP",
+ "observer.add_broker": "Dodaj brokera",
+ "observer.edit_broker": "Edytuj brokera",
+ "observer.host": "Host",
+ "observer.name_ph": "np. Mój serwer MQTT",
+ "observer.password": "Hasło",
+ "observer.password_hint": "Zostaw hasło puste, aby zachować bieżące.",
+ "observer.port": "Port",
+ "observer.ports_hint": "Typowe porty: 1883 (bez szyfrowania), 8883 (TLS).",
+ "observer.use_tls": "Użyj TLS",
+ "observer.username": "Nazwa użytkownika",
+ "observer.verify_tls": "Weryfikuj certyfikat TLS",
"pa.any_hb": "Dowolne HB",
"pa.any_hops": "Dowolne hopy",
"pa.byte_2_3": "2/3 bajty",
@@ -471,6 +528,10 @@
"pa.view.routes": "Trasy",
"pa.view.routes_title": "Statystyki segmentów tras",
"pa.view_aria": "Widok",
+ "quote.full": "Pełny cytat",
+ "quote.max_bytes": "Maks. bajtów:",
+ "quote.title": "Zacytuj wiadomość",
+ "quote.truncated": "Skrócony",
"repeaters.add": "Dodaj repeater",
"repeaters.add_loading": "Wczytywanie kontaktów z urządzenia...",
"repeaters.add_modal_title": "Dodaj repeater",
@@ -711,5 +772,22 @@
"rptmgmt.tools.telemetry": "Telemetria",
"rptmgmt.tools.telemetry_desc": "Kanały czujników (Cayenne LPP)",
"rptmgmt.tools_heading": "Narzędzia zarządzania",
- "rptmgmt.updated_just_now": "Zaktualizowano przed chwilą"
+ "rptmgmt.updated_just_now": "Zaktualizowano przed chwilą",
+ "search.empty": "Szukaj we wszystkich wiadomościach kanałowych i prywatnych",
+ "search.fts5_ref": "Pełna dokumentacja składni FTS5",
+ "search.help_title": "Pomoc do składni wyszukiwania",
+ "search.ph": "Szukaj we wszystkich wiadomościach...",
+ "search.special_chars": "Znaki specjalne (. , - :) należy ująć w cudzysłów.",
+ "search.tip_and": "wiadomości zawierające oba słowa",
+ "search.tip_not": "hello, ale bez world",
+ "search.tip_or": "dowolne z tych słów",
+ "search.tip_phrase": "dokładna fraza",
+ "search.tip_prefix": "dopasowanie przedrostka (hello, hellas...)",
+ "search.tips": "Wskazówki wyszukiwania:",
+ "update.checking": "Sprawdzanie aktualizacji...",
+ "update.now": "Aktualizuj teraz",
+ "update.reload": "Przeładuj stronę",
+ "update.title": "Aktualizacja mc-webui",
+ "update.updating": "Aktualizowanie...",
+ "update.whats_new": "Co nowego?"
}
diff --git a/docs/whatsnew.md b/docs/whatsnew.md
index e38223e..3fc0dd7 100644
--- a/docs/whatsnew.md
+++ b/docs/whatsnew.md
@@ -12,7 +12,7 @@ For deep technical notes, see [architecture.md](architecture.md). For the full g
### Features
-- **The interface can be translated, and Polish has started.** mc-webui was written English-only, with every label and message baked into the code. There is now a translation system behind it, and a **Language** setting at the top of Settings → Appearance. So far it covers the groundwork plus the panels that open in their own window — **System Log**, **Console**, **My Repeaters**, **Path Analyzer**, **Contacts** and **Direct Messages**. The main chat window and the Settings dialog follow next, so for now parts of the interface are still English whichever language you pick. Your choice applies to the browser you set it in and also becomes the default for anyone else opening the server without a preference of their own.
+- **The interface can be translated, and Polish has started.** mc-webui was written English-only, with every label and message baked into the code. There is now a translation system behind it, and a **Language** setting at the top of Settings → Appearance. So far it covers the main window — menu, chat, and its dialogs — plus every panel that opens in its own window: **System Log**, **Console**, **My Repeaters**, **Path Analyzer**, **Contacts** and **Direct Messages**. The **Settings** dialog and many of the short messages the app shows while it works are still English; they follow next. Your choice applies to the browser you set it in and also becomes the default for anyone else opening the server without a preference of their own.
- **You can add a language yourself, without waiting for a release.** A language is a single file. Copy `en.json` from `app/translations/`, translate the values, and drop it into a `translations` folder inside your config directory — the same place the database lives. Refresh the page and it appears in the Language list, named however you named it, with no rebuild and no restart. Anything you leave untranslated falls back to English, so a half-finished translation is perfectly usable. A file you drop in also overrides one that ships with the app, so you can correct the built-in Polish on your own server. English and Polish ship in the box; everything else is open to whoever wants to write it. See [translations.md](translations.md), which explains the format and — importantly — which words to leave alone: mesh terms like flood, hop, advert, RSSI and the repeater roles stay English in every language, because that is what the firmware, the CLI and the forums all use. The Console and the log lines themselves stay English for the same reason.
- **Times and dates behave the same in every language.** Only the words are translated — "Yesterday" becomes "Wczoraj", "5 min ago" becomes "5 min temu". The clock and the number formatting keep following your browser's own settings, so switching the menus to English will not suddenly turn your 24-hour clock into "9:53 AM". One display of large numbers on the repeater statistics page had been hard-coded to American thousands separators; it now follows your locale like everything else.