feat(i18n): translate the app chrome and main chat window (stage 7a)

Covers the navbar, the offcanvas main menu, and index.html: the channel
sidebar, filter bar, composer, status bar and the FAB column, plus the
six fullscreen panel modals, whose headers now reuse each panel's own
title key (dm.title, contacts.title, console.title, repeaters.title,
pa.title, logs.title) instead of repeating the words.

The Settings modal (base.html ~458-1147) and the other modals in
base.html are untouched - stages 8 and 7b.

Also translated in app.js: updateStatus(), updateLastRefresh(), the FAB
collapse toggle, the filter bar's "no matches" panel and
updateRegionIndicator(). The rest of app.js stays for stage 9; the line
drawn here is "does it overwrite a string index.html just got a key
for". Without them the status bar renders Polish server-side and flips
back to English a second later, which would make the slice impossible to
verify - a miss and a stage-9 leftover look identical.

"Filter messages" (menu) and "Filter Messages" (FAB) keep separate keys
even though the Polish is identical, so this commit changes no English
text, only where it lives.

Catalog: 526 -> 573 keys, pl at 100%.

Verified with i18n-diff.js and a new i18n-stage7a-chrome.js that opens
the offcanvas menu at both viewports - like the DM modals, a closed
offcanvas is invisible to the plain diff. Two tooling fixes came out of
it, both in F:\tmp\pwverify:

- i18n-diff.js checked visibility on the text node's parent only, so
  every closed modal's contents landed in the report: 275 flagged lines
  on the main chat, 13 after walking the ancestor chain. It was noise,
  never a false pass, but it is the kind of noise that trains you to
  skim the list.
- browser.js now centralises the Chrome launch. The translate bubble was
  held off by one flag copy-pasted into each script; it is now flags plus
  a persistent profile with translation disabled in Preferences, which is
  the part that survives Chrome renaming the feature.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-08-01 09:39:52 +02:00
parent 3b34dd9646
commit e11debbda4
5 changed files with 192 additions and 98 deletions
+10 -10
View File
@@ -4146,12 +4146,12 @@ function updateRegionIndicator() {
nameEl.textContent = scope.name;
el.classList.remove('bg-light', 'text-secondary', 'border');
el.classList.add('bg-info', 'text-dark');
el.title = 'Click to change region for this channel';
el.title = t('chat.region_title');
} else {
nameEl.textContent = 'No region';
nameEl.textContent = t('chat.region_none');
el.classList.remove('bg-info', 'text-dark');
el.classList.add('bg-light', 'text-secondary', 'border');
el.title = 'Click to set a region for this channel';
el.title = t('chat.region_set_title');
}
}
@@ -4334,9 +4334,9 @@ function updateStatus(status) {
const statusEl = document.getElementById('statusText');
const icons = {
connected: '<i class="bi bi-circle-fill status-connected"></i> Connected',
disconnected: '<i class="bi bi-circle-fill status-disconnected"></i> Disconnected',
connecting: '<i class="bi bi-circle-fill status-connecting"></i> Connecting...'
connected: `<i class="bi bi-circle-fill status-connected"></i> ${tHtml('common.connected')}`,
disconnected: `<i class="bi bi-circle-fill status-disconnected"></i> ${tHtml('common.disconnected')}`,
connecting: `<i class="bi bi-circle-fill status-connecting"></i> ${tHtml('common.connecting')}`
};
statusEl.innerHTML = icons[status] || icons.connecting;
@@ -4348,7 +4348,7 @@ function updateStatus(status) {
function updateLastRefresh() {
const now = new Date();
const timeStr = now.toLocaleTimeString();
document.getElementById('lastRefresh').textContent = `Updated: ${timeStr}`;
document.getElementById('lastRefresh').textContent = t('chat.updated', { time: timeStr });
}
/**
@@ -6339,13 +6339,13 @@ function initializeFabToggle() {
// Restore collapsed state
if (localStorage.getItem('mc-webui-fab-collapsed') === '1') {
container.classList.add('collapsed');
toggle.title = 'Show buttons';
toggle.title = t('chat.fab.show');
}
toggle.addEventListener('click', () => {
container.classList.toggle('collapsed');
const isCollapsed = container.classList.contains('collapsed');
toggle.title = isCollapsed ? 'Show buttons' : 'Hide buttons';
toggle.title = isCollapsed ? t('chat.fab.show') : t('chat.fab.hide');
localStorage.setItem('mc-webui-fab-collapsed', isCollapsed ? '1' : '0');
});
@@ -6620,7 +6620,7 @@ function applyFilter(query) {
noMatchesDiv.className = 'filter-no-matches';
noMatchesDiv.innerHTML = `
<i class="bi bi-search"></i>
<p>No messages match "${escapeHtml(currentFilterQuery)}"</p>
<p>${tHtml('chat.filter.no_matches', { query: currentFilterQuery })}</p>
`;
container.appendChild(noMatchesDiv);
}
+48 -48
View File
@@ -63,13 +63,13 @@
<small class="text-white-50 d-none d-sm-inline">- {{ device_name }}</small>
{% endif %}
{% if transport_type == 'ble' %}
<span class="badge bg-info ms-1 d-none d-sm-inline" title="Bluetooth Low Energy">BLE</span>
<span class="badge bg-info ms-1 d-none d-sm-inline" title="{{ t('nav.ble_title') }}">BLE</span>
{% elif transport_type == 'tcp' %}
<span class="badge bg-warning text-dark ms-1 d-none d-sm-inline" title="TCP connection">TCP</span>
<span class="badge bg-warning text-dark ms-1 d-none d-sm-inline" title="{{ t('nav.tcp_title') }}">TCP</span>
{% endif %}
</span>
<div class="d-flex align-items-center gap-2">
<div id="notificationBell" class="btn btn-outline-light position-relative navbar-touch-btn" style="cursor: pointer;" onclick="markAllChannelsRead()" title="Mark all as read">
<div id="notificationBell" class="btn btn-outline-light position-relative navbar-touch-btn" style="cursor: pointer;" onclick="markAllChannelsRead()" title="{{ t('nav.mark_all_read_title') }}">
<i class="bi bi-bell"></i>
</div>
<div class="position-relative channel-mobile-selector" id="channelSelectorWrapper" style="min-width: 80px; max-width: 140px;">
@@ -78,11 +78,11 @@
class="form-control form-select navbar-touch-select"
placeholder="Public"
autocomplete="off"
title="Select channel"
title="{{ t('nav.select_channel_title') }}"
style="cursor: pointer;">
<div id="channelSelectorDropdown" class="channel-selector-dropdown" style="display: none;"></div>
</div>
<button class="btn btn-outline-light navbar-touch-btn" data-bs-toggle="offcanvas" data-bs-target="#mainMenu" title="Menu">
<button class="btn btn-outline-light navbar-touch-btn" data-bs-toggle="offcanvas" data-bs-target="#mainMenu" title="{{ t('menu.title') }}">
<i class="bi bi-list"></i>
</button>
</div>
@@ -92,7 +92,7 @@
<!-- Offcanvas Menu -->
<div class="offcanvas offcanvas-end" tabindex="-1" id="mainMenu">
<div class="offcanvas-header">
<h5 class="offcanvas-title"><i class="bi bi-menu-button-wide"></i> Menu</h5>
<h5 class="offcanvas-title"><i class="bi bi-menu-button-wide"></i> {{ t('menu.title') }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
</div>
<div class="px-3 pb-2 text-muted small border-bottom">
@@ -102,9 +102,9 @@
<span class="badge bg-secondary ms-1" id="branchBadge">{{ git_branch }}</span>
<!-- Exact build: what pins a deployment down, and what the
update poller compares - keep it as its own element -->
<span id="versionText" class="d-block opacity-75" title="Exact build">{{ version }}</span>
<span id="versionText" class="d-block opacity-75" title="{{ t('menu.exact_build_title') }}">{{ version }}</span>
</span>
<button id="checkUpdateBtn" class="btn btn-sm btn-outline-secondary py-0 px-1" title="Check for updates">
<button id="checkUpdateBtn" class="btn btn-sm btn-outline-secondary py-0 px-1" title="{{ t('menu.check_updates_title') }}">
<i class="bi bi-arrow-repeat" id="checkUpdateIcon"></i>
</button>
</div>
@@ -116,26 +116,26 @@
<!-- The Android wrapper has no pull-to-refresh of its own, so
this is the manual way out of a list that fell behind -->
<button id="refreshBtn" class="list-group-item list-group-item-action d-flex align-items-center gap-3" type="button"
data-bs-dismiss="offcanvas" title="Reload messages from the server">
data-bs-dismiss="offcanvas" title="{{ t('menu.refresh_title') }}">
<i class="bi bi-arrow-clockwise" style="font-size: 1.5rem;"></i>
<div class="flex-grow-1">
<div>Refresh</div>
<small class="d-block text-muted">Reload messages from server</small>
<div>{{ t('common.refresh') }}</div>
<small class="d-block text-muted">{{ t('menu.refresh_desc') }}</small>
</div>
</button>
<button id="menu-filter" class="list-group-item list-group-item-action d-flex align-items-center gap-3 d-none" type="button">
<i class="bi bi-funnel" style="font-size: 1.5rem;"></i>
<div class="flex-grow-1">
<div>Filter messages</div>
<small class="d-block text-muted">Filter current chat</small>
<div>{{ t('menu.filter') }}</div>
<small class="d-block text-muted">{{ t('menu.filter_desc') }}</small>
</div>
</button>
<button id="menu-search" class="list-group-item list-group-item-action d-flex align-items-center gap-3 d-none" type="button"
data-bs-toggle="modal" data-bs-target="#searchModal" data-bs-dismiss="offcanvas">
<i class="bi bi-search" style="font-size: 1.5rem;"></i>
<div class="flex-grow-1">
<div>Search messages</div>
<small class="d-block text-muted">Search all messages</small>
<div>{{ t('menu.search') }}</div>
<small class="d-block text-muted">{{ t('menu.search_desc') }}</small>
</div>
</button>
<button id="menu-dm" class="list-group-item list-group-item-action d-flex align-items-center gap-3 d-none" type="button"
@@ -143,10 +143,10 @@
<i class="bi bi-envelope-fill" style="font-size: 1.5rem;"></i>
<div class="flex-grow-1">
<div class="d-flex justify-content-between align-items-center">
<span>Direct Messages</span>
<span>{{ t('dm.title') }}</span>
<span class="badge rounded-pill bg-danger d-none menu-badge-dm">0</span>
</div>
<small class="d-block text-muted">Private conversations</small>
<small class="d-block text-muted">{{ t('menu.dm_desc') }}</small>
</div>
</button>
<button id="menu-contacts" class="list-group-item list-group-item-action d-flex align-items-center gap-3 d-none" type="button"
@@ -154,107 +154,107 @@
<i class="bi bi-person-lines-fill" style="font-size: 1.5rem;"></i>
<div class="flex-grow-1">
<div class="d-flex justify-content-between align-items-center">
<span>Contact Management</span>
<span>{{ t('contacts.title') }}</span>
<span class="badge rounded-pill bg-warning d-none menu-badge-contacts">0</span>
</div>
<small class="d-block text-muted">Manage known contacts</small>
<small class="d-block text-muted">{{ t('menu.contacts_desc') }}</small>
</div>
</button>
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3" data-bs-toggle="modal" data-bs-target="#channelsModal" data-bs-dismiss="offcanvas">
<i class="bi bi-broadcast-pin" style="font-size: 1.5rem;"></i>
<span>Manage Channels</span>
<span>{{ t('menu.channels') }}</span>
</button>
<div class="list-group-item">
<div class="d-flex align-items-center gap-3 mb-2">
<i class="bi bi-calendar3" style="font-size: 1.5rem;"></i>
<label for="dateSelector" class="form-label mb-0">Message History</label>
<label for="dateSelector" class="form-label mb-0">{{ t('menu.history') }}</label>
</div>
<select id="dateSelector" class="form-select" title="Select date">
<option value="">Today (Live)</option>
<select id="dateSelector" class="form-select" title="{{ t('menu.select_date_title') }}">
<option value="">{{ t('menu.today_live') }}</option>
<!-- Archive dates loaded dynamically via JavaScript -->
</select>
</div>
<!-- Network -->
<div class="list-group-item py-2 mt-2">
<small class="text-muted fw-bold text-uppercase">Network</small>
<small class="text-muted fw-bold text-uppercase">{{ t('menu.section.network') }}</small>
</div>
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3" id="advertBtn" title="Send single advertisement (recommended for normal operation)">
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3" id="advertBtn" title="{{ t('menu.advert_title') }}">
<i class="bi bi-megaphone" style="font-size: 1.5rem;"></i>
<div>
<span>Send Advert</span>
<small class="d-block text-muted">Announce presence (normal)</small>
<span>{{ t('menu.advert') }}</span>
<small class="d-block text-muted">{{ t('menu.advert_desc') }}</small>
</div>
</button>
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3 text-warning" id="floodadvBtn" title="Flood advertisement - use sparingly! High airtime usage.">
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3 text-warning" id="floodadvBtn" title="{{ t('menu.flood_advert_title') }}">
<i class="bi bi-broadcast" style="font-size: 1.5rem;"></i>
<div>
<span>Flood Advert</span>
<small class="d-block text-muted">Network recovery only!</small>
<span>{{ t('menu.flood_advert') }}</span>
<small class="d-block text-muted">{{ t('menu.flood_advert_desc') }}</small>
</div>
</button>
<!-- Tools -->
<div class="list-group-item py-2 mt-2">
<small class="text-muted fw-bold text-uppercase">Tools</small>
<small class="text-muted fw-bold text-uppercase">{{ t('menu.section.tools') }}</small>
</div>
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3"
id="mapBtn" title="Show all contacts with GPS on map">
id="mapBtn" title="{{ t('menu.map_title') }}">
<i class="bi bi-map" style="font-size: 1.5rem;"></i>
<div>
<span>Map</span>
<small class="d-block text-muted">All contacts with GPS</small>
<span>{{ t('menu.map') }}</span>
<small class="d-block text-muted">{{ t('menu.map_desc') }}</small>
</div>
</button>
<button id="consoleBtn" class="list-group-item list-group-item-action d-flex align-items-center gap-3" data-bs-toggle="modal" data-bs-target="#consoleModal" data-bs-dismiss="offcanvas">
<i class="bi bi-terminal" style="font-size: 1.5rem;"></i>
<div>
<span>Console</span>
<small class="d-block text-muted">Direct meshcli commands</small>
<span>{{ t('console.title') }}</span>
<small class="d-block text-muted">{{ t('menu.console_desc') }}</small>
</div>
</button>
<button id="repeatersBtn" class="list-group-item list-group-item-action d-flex align-items-center gap-3" data-bs-toggle="modal" data-bs-target="#repeatersModal" data-bs-dismiss="offcanvas">
<i class="bi bi-diagram-3" style="font-size: 1.5rem;"></i>
<div>
<span>My Repeaters</span>
<small class="d-block text-muted">Repeater administration</small>
<span>{{ t('repeaters.title') }}</span>
<small class="d-block text-muted">{{ t('menu.repeaters_desc') }}</small>
</div>
</button>
<button id="pathAnalyzerBtn" class="list-group-item list-group-item-action d-flex align-items-center gap-3" data-bs-toggle="modal" data-bs-target="#pathAnalyzerModal" data-bs-dismiss="offcanvas">
<i class="bi bi-signpost-split" style="font-size: 1.5rem;"></i>
<div>
<span>Path Analyzer</span>
<small class="d-block text-muted">Routing path analysis</small>
<span>{{ t('pa.title') }}</span>
<small class="d-block text-muted">{{ t('menu.pa_desc') }}</small>
</div>
</button>
<!-- System -->
<div class="list-group-item py-2 mt-2">
<small class="text-muted fw-bold text-uppercase">System</small>
<small class="text-muted fw-bold text-uppercase">{{ t('menu.section.system') }}</small>
</div>
<button id="deviceInfoBtn" class="list-group-item list-group-item-action d-flex align-items-center gap-3" data-bs-toggle="modal" data-bs-target="#deviceInfoModal" data-bs-dismiss="offcanvas">
<i class="bi bi-cpu" style="font-size: 1.5rem;"></i>
<span>Device Info</span>
<span>{{ t('menu.device_info') }}</span>
</button>
<button id="logsBtn" class="list-group-item list-group-item-action d-flex align-items-center gap-3" data-bs-toggle="modal" data-bs-target="#logsModal" data-bs-dismiss="offcanvas">
<i class="bi bi-journal-text" style="font-size: 1.5rem;"></i>
<div>
<span>System Log</span>
<small class="d-block text-muted">Real-time application logs</small>
<span>{{ t('logs.title') }}</span>
<small class="d-block text-muted">{{ t('menu.logs_desc') }}</small>
</div>
</button>
<button id="menu-settings" class="list-group-item list-group-item-action d-flex align-items-center gap-3 d-none" data-bs-toggle="modal" data-bs-target="#settingsModal" data-bs-dismiss="offcanvas">
<i class="bi bi-gear" style="font-size: 1.5rem;"></i>
<div>
<span>Settings</span>
<small class="d-block text-muted">Application settings</small>
<span>{{ t('common.settings') }}</span>
<small class="d-block text-muted">{{ t('menu.settings_desc') }}</small>
</div>
</button>
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3" data-bs-toggle="modal" data-bs-target="#backupModal" data-bs-dismiss="offcanvas">
<i class="bi bi-database-down" style="font-size: 1.5rem;"></i>
<div>
<span>Backup</span>
<small class="d-block text-muted">Database backup & restore</small>
<span>{{ t('menu.backup') }}</span>
<small class="d-block text-muted">{{ t('menu.backup_desc') }}</small>
</div>
</button>
</div>
+40 -40
View File
@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Chat - mc-webui{% endblock %}
{% block title %}{{ t('chat.title') }} - mc-webui{% endblock %}
{% block extra_head %}
<!-- Emoji Picker (local) -->
@@ -14,7 +14,7 @@
<!-- Channel Sidebar (visible on lg+ screens) -->
<div id="channelSidebar" class="channel-sidebar">
<div class="channel-sidebar-header">
<i class="bi bi-broadcast-pin"></i> Channels
<i class="bi bi-broadcast-pin"></i> {{ t('chat.channels') }}
</div>
<div class="channel-sidebar-list" id="channelSidebarList">
<!-- Populated by JavaScript -->
@@ -28,20 +28,20 @@
<div id="filterBar" class="filter-bar">
<div class="filter-bar-inner">
<div class="filter-input-wrapper">
<input type="text" id="filterInput" class="filter-bar-input" placeholder="Filter messages..." autocomplete="off">
<input type="text" id="filterInput" class="filter-bar-input" placeholder="{{ t('chat.filter.ph') }}" autocomplete="off">
<!-- Filter mentions autocomplete popup -->
<div id="filterMentionsPopup" class="mentions-popup filter-mentions-popup hidden">
<div class="mentions-list" id="filterMentionsList"></div>
</div>
</div>
<button type="button" id="filterMeBtn" class="filter-bar-btn filter-bar-btn-me" title="Filter my messages">
<button type="button" id="filterMeBtn" class="filter-bar-btn filter-bar-btn-me" title="{{ t('chat.filter.me_title') }}">
<i class="bi bi-person-fill"></i>
</button>
<span id="filterMatchCount" class="filter-match-count"></span>
<button type="button" id="filterClearBtn" class="filter-bar-btn filter-bar-btn-clear" title="Clear">
<button type="button" id="filterClearBtn" class="filter-bar-btn filter-bar-btn-clear" title="{{ t('chat.filter.clear_title') }}">
<i class="bi bi-x"></i>
</button>
<button type="button" id="filterCloseBtn" class="filter-bar-btn filter-bar-btn-close" title="Close">
<button type="button" id="filterCloseBtn" class="filter-bar-btn filter-bar-btn-close" title="{{ t('common.close') }}">
<i class="bi bi-x-lg"></i>
</button>
</div>
@@ -51,14 +51,14 @@
<!-- Messages will be loaded here via JavaScript -->
<div class="text-center text-muted py-5">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
<span class="visually-hidden">{{ t('common.loading') }}</span>
</div>
<p class="mt-3">Loading messages...</p>
<p class="mt-3">{{ t('chat.loading_messages') }}</p>
</div>
</div>
</div>
<!-- Scroll to bottom button -->
<button id="scrollToBottomBtn" class="scroll-to-bottom-btn" title="Scroll to bottom">
<button id="scrollToBottomBtn" class="scroll-to-bottom-btn" title="{{ t('chat.scroll_bottom_title') }}">
<i class="bi bi-chevron-double-down"></i>
</button>
</div>
@@ -70,12 +70,12 @@
<textarea
id="messageInput"
class="form-control"
placeholder="Type a message..."
placeholder="{{ t('chat.input_ph') }}"
rows="2"
maxlength="500"
required
></textarea>
<button type="button" class="btn btn-outline-secondary" id="emojiBtn" title="Insert emoji">
<button type="button" class="btn btn-outline-secondary" id="emojiBtn" title="{{ t('chat.emoji_title') }}">
<i class="bi bi-emoji-smile"></i>
</button>
<button type="submit" class="btn btn-primary px-4" id="sendBtn">
@@ -99,14 +99,14 @@
<div class="p-2 small text-muted d-flex justify-content-between align-items-center">
<div class="d-flex gap-2 align-items-center">
<span id="statusText">
<i class="bi bi-circle-fill text-secondary"></i> Connecting...
<i class="bi bi-circle-fill text-secondary"></i> {{ t('common.connecting') }}
</span>
<span id="regionIndicator" class="badge bg-info text-dark d-none" role="button"
title="Click to change region for this channel">
title="{{ t('chat.region_title') }}">
<i class="bi bi-pin-map"></i> <span id="regionIndicatorName"></span>
</span>
</div>
<span id="lastRefresh">Updated: Never</span>
<span id="lastRefresh">{{ t('chat.updated', time=t('common.never')) }}</span>
</div>
</div>
</div>
@@ -115,46 +115,46 @@
<!-- Floating Action Buttons -->
<div class="fab-container" id="fabContainer">
<button class="fab fab-toggle" id="fabToggle" title="Hide buttons">
<button class="fab fab-toggle" id="fabToggle" title="{{ t('chat.fab.hide') }}">
<i class="bi bi-chevron-right"></i>
</button>
<button class="fab fab-filter" id="filterFab" title="Filter Messages">
<button class="fab fab-filter" id="filterFab" title="{{ t('chat.filter.fab_title') }}">
<i class="bi bi-funnel-fill"></i>
</button>
<button class="fab fab-search" id="globalSearchBtn" data-bs-toggle="modal" data-bs-target="#searchModal" title="Search Messages">
<button class="fab fab-search" id="globalSearchBtn" data-bs-toggle="modal" data-bs-target="#searchModal" title="{{ t('chat.fab.search_title') }}">
<i class="bi bi-search"></i>
</button>
<button class="fab fab-dm" data-bs-toggle="modal" data-bs-target="#dmModal" title="Direct Messages">
<button class="fab fab-dm" data-bs-toggle="modal" data-bs-target="#dmModal" title="{{ t('dm.title') }}">
<i class="bi bi-envelope-fill"></i>
</button>
<button class="fab fab-contacts" data-bs-toggle="modal" data-bs-target="#contactsModal" title="Contact Management">
<button class="fab fab-contacts" data-bs-toggle="modal" data-bs-target="#contactsModal" title="{{ t('contacts.title') }}">
<i class="bi bi-person-fill"></i>
</button>
<button class="fab fab-settings" data-bs-toggle="modal" data-bs-target="#settingsModal" title="Settings">
<button class="fab fab-settings" data-bs-toggle="modal" data-bs-target="#settingsModal" title="{{ t('common.settings') }}">
<i class="bi bi-gear-fill"></i>
</button>
<button class="fab fab-advert d-none" id="fab-advert" title="Send Advert">
<button class="fab fab-advert d-none" id="fab-advert" title="{{ t('menu.advert') }}">
<i class="bi bi-megaphone"></i>
</button>
<button class="fab fab-floodadvert d-none" id="fab-floodadvert" title="Flood Advert">
<button class="fab fab-floodadvert d-none" id="fab-floodadvert" title="{{ t('menu.flood_advert') }}">
<i class="bi bi-broadcast"></i>
</button>
<button class="fab fab-map d-none" id="fab-map" title="Map">
<button class="fab fab-map d-none" id="fab-map" title="{{ t('menu.map') }}">
<i class="bi bi-map"></i>
</button>
<button class="fab fab-console d-none" id="fab-console" data-bs-toggle="modal" data-bs-target="#consoleModal" title="Console">
<button class="fab fab-console d-none" id="fab-console" data-bs-toggle="modal" data-bs-target="#consoleModal" title="{{ t('console.title') }}">
<i class="bi bi-terminal"></i>
</button>
<button class="fab fab-repeaters d-none" id="fab-repeaters" data-bs-toggle="modal" data-bs-target="#repeatersModal" title="My Repeaters">
<button class="fab fab-repeaters d-none" id="fab-repeaters" data-bs-toggle="modal" data-bs-target="#repeatersModal" title="{{ t('repeaters.title') }}">
<i class="bi bi-diagram-3"></i>
</button>
<button class="fab fab-pathanalyzer d-none" id="fab-pathanalyzer" data-bs-toggle="modal" data-bs-target="#pathAnalyzerModal" title="Path Analyzer">
<button class="fab fab-pathanalyzer d-none" id="fab-pathanalyzer" data-bs-toggle="modal" data-bs-target="#pathAnalyzerModal" title="{{ t('pa.title') }}">
<i class="bi bi-signpost-split"></i>
</button>
<button class="fab fab-deviceinfo d-none" id="fab-deviceinfo" data-bs-toggle="modal" data-bs-target="#deviceInfoModal" title="Device Info">
<button class="fab fab-deviceinfo d-none" id="fab-deviceinfo" data-bs-toggle="modal" data-bs-target="#deviceInfoModal" title="{{ t('menu.device_info') }}">
<i class="bi bi-cpu"></i>
</button>
<button class="fab fab-syslog d-none" id="fab-syslog" data-bs-toggle="modal" data-bs-target="#logsModal" title="System Log">
<button class="fab fab-syslog d-none" id="fab-syslog" data-bs-toggle="modal" data-bs-target="#logsModal" title="{{ t('logs.title') }}">
<i class="bi bi-journal-text"></i>
</button>
</div>
@@ -164,9 +164,9 @@
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title"><i class="bi bi-envelope"></i> Direct Messages</h5>
<h5 class="modal-title"><i class="bi bi-envelope"></i> {{ t('dm.title') }}</h5>
<button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">
<i class="bi bi-x-lg"></i> Close
<i class="bi bi-x-lg"></i> {{ t('common.close') }}
</button>
</div>
<div class="modal-body p-0">
@@ -181,9 +181,9 @@
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title"><i class="bi bi-person-check"></i> Contact Management</h5>
<h5 class="modal-title"><i class="bi bi-person-check"></i> {{ t('contacts.title') }}</h5>
<button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">
<i class="bi bi-x-lg"></i> Close
<i class="bi bi-x-lg"></i> {{ t('common.close') }}
</button>
</div>
<div class="modal-body p-0">
@@ -198,9 +198,9 @@
<div class="modal-dialog modal-fullscreen">
<div class="modal-content" style="background-color: #1a1a2e;">
<div class="modal-header" style="background-color: #16213e; border-bottom: 1px solid #0f3460;">
<h5 class="modal-title text-white"><i class="bi bi-terminal"></i> mc-webui Console</h5>
<h5 class="modal-title text-white"><i class="bi bi-terminal"></i> mc-webui {{ t('console.title') }}</h5>
<button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">
<i class="bi bi-x-lg"></i> Close
<i class="bi bi-x-lg"></i> {{ t('common.close') }}
</button>
</div>
<div class="modal-body p-0">
@@ -215,9 +215,9 @@
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title"><i class="bi bi-diagram-3"></i> My Repeaters</h5>
<h5 class="modal-title"><i class="bi bi-diagram-3"></i> {{ t('repeaters.title') }}</h5>
<button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">
<i class="bi bi-x-lg"></i> Close
<i class="bi bi-x-lg"></i> {{ t('common.close') }}
</button>
</div>
<div class="modal-body p-0">
@@ -232,9 +232,9 @@
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header pathanalyzer-header text-white">
<h5 class="modal-title"><i class="bi bi-signpost-split"></i> Path Analyzer</h5>
<h5 class="modal-title"><i class="bi bi-signpost-split"></i> {{ t('pa.title') }}</h5>
<button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">
<i class="bi bi-x-lg"></i> Close
<i class="bi bi-x-lg"></i> {{ t('common.close') }}
</button>
</div>
<div class="modal-body p-0">
@@ -249,9 +249,9 @@
<div class="modal-dialog modal-fullscreen">
<div class="modal-content" style="background-color: #1a1a2e;">
<div class="modal-header" style="background-color: #16213e; border-bottom: 1px solid #0f3460;">
<h5 class="modal-title text-white"><i class="bi bi-journal-text"></i> System Log</h5>
<h5 class="modal-title text-white"><i class="bi bi-journal-text"></i> {{ t('logs.title') }}</h5>
<button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">
<i class="bi bi-x-lg"></i> Close
<i class="bi bi-x-lg"></i> {{ t('common.close') }}
</button>
</div>
<div class="modal-body p-0">
+47
View File
@@ -1,15 +1,23 @@
{
"chat.channels": "Channels",
"chat.copy_route_title": "Tap to copy route",
"chat.emoji_title": "Insert emoji",
"chat.fab.hide": "Hide buttons",
"chat.fab.search_title": "Search Messages",
"chat.fab.show": "Show buttons",
"chat.filter.clear_title": "Clear",
"chat.filter.fab_title": "Filter Messages",
"chat.filter.me_title": "Filter my messages",
"chat.filter.no_matches": "No messages match \"{query}\"",
"chat.filter.ph": "Filter messages...",
"chat.input_ph": "Type a message...",
"chat.loading_messages": "Loading messages...",
"chat.region_none": "No region",
"chat.region_set_title": "Click to set a region for this channel",
"chat.region_title": "Click to change region for this channel",
"chat.route_hops": "Hops: {count}",
"chat.scroll_bottom_title": "Scroll to bottom",
"chat.title": "Chat",
"chat.updated": "Updated: {time}",
"common.add": "Add",
"common.back": "Back",
@@ -267,9 +275,48 @@
"logs.loading": "Loading logs...",
"logs.pause_title": "Pause/Resume",
"logs.title": "System Log",
"menu.advert": "Send Advert",
"menu.advert_desc": "Announce presence (normal)",
"menu.advert_title": "Send single advertisement (recommended for normal operation)",
"menu.backup": "Backup",
"menu.backup_desc": "Database backup & restore",
"menu.channels": "Manage Channels",
"menu.check_updates_title": "Check for updates",
"menu.console_desc": "Direct meshcli commands",
"menu.contacts_desc": "Manage known contacts",
"menu.device_info": "Device Info",
"menu.dm_desc": "Private conversations",
"menu.exact_build_title": "Exact build",
"menu.filter": "Filter messages",
"menu.filter_desc": "Filter current chat",
"menu.flood_advert": "Flood Advert",
"menu.flood_advert_desc": "Network recovery only!",
"menu.flood_advert_title": "Flood advertisement - use sparingly! High airtime usage.",
"menu.history": "Message History",
"menu.logs_desc": "Real-time application logs",
"menu.map": "Map",
"menu.map_desc": "All contacts with GPS",
"menu.map_title": "Show all contacts with GPS on map",
"menu.pa_desc": "Routing path analysis",
"menu.refresh_desc": "Reload messages from server",
"menu.refresh_title": "Reload messages from the server",
"menu.repeaters_desc": "Repeater administration",
"menu.search": "Search messages",
"menu.search_desc": "Search all messages",
"menu.section.network": "Network",
"menu.section.system": "System",
"menu.section.tools": "Tools",
"menu.select_date_title": "Select date",
"menu.settings_desc": "Application settings",
"menu.title": "Menu",
"menu.today_live": "Today (Live)",
"meta.language_english_name": "English",
"meta.language_name": "English",
"meta.translator": "mc-webui",
"nav.ble_title": "Bluetooth Low Energy",
"nav.mark_all_read_title": "Mark all as read",
"nav.select_channel_title": "Select channel",
"nav.tcp_title": "TCP connection",
"pa.any_hb": "Any HB",
"pa.any_hops": "Any hops",
"pa.byte_2_3": "2/3-byte",
+47
View File
@@ -1,15 +1,23 @@
{
"chat.channels": "Kanały",
"chat.copy_route_title": "Dotknij, aby skopiować trasę",
"chat.emoji_title": "Wstaw emoji",
"chat.fab.hide": "Ukryj przyciski",
"chat.fab.search_title": "Szukaj w wiadomościach",
"chat.fab.show": "Pokaż przyciski",
"chat.filter.clear_title": "Wyczyść",
"chat.filter.fab_title": "Filtruj wiadomości",
"chat.filter.me_title": "Filtruj moje wiadomości",
"chat.filter.no_matches": "Brak wiadomości pasujących do „{query}”",
"chat.filter.ph": "Filtruj wiadomości...",
"chat.input_ph": "Napisz wiadomość...",
"chat.loading_messages": "Wczytywanie wiadomości...",
"chat.region_none": "Bez regionu",
"chat.region_set_title": "Kliknij, aby ustawić region dla tego kanału",
"chat.region_title": "Kliknij, aby zmienić region dla tego kanału",
"chat.route_hops": "Hopy: {count}",
"chat.scroll_bottom_title": "Przewiń na dół",
"chat.title": "Czat",
"chat.updated": "Aktualizacja: {time}",
"common.add": "Dodaj",
"common.back": "Wstecz",
@@ -291,9 +299,48 @@
"logs.loading": "Ładowanie dziennika...",
"logs.pause_title": "Wstrzymaj/Wznów",
"logs.title": "Dziennik systemowy",
"menu.advert": "Wyślij advert",
"menu.advert_desc": "Ogłoś obecność (normalnie)",
"menu.advert_title": "Wyślij pojedynczy advert (zalecane przy normalnej pracy)",
"menu.backup": "Kopia zapasowa",
"menu.backup_desc": "Kopia i przywracanie bazy danych",
"menu.channels": "Zarządzaj kanałami",
"menu.check_updates_title": "Sprawdź aktualizacje",
"menu.console_desc": "Bezpośrednie komendy meshcli",
"menu.contacts_desc": "Zarządzaj znanymi kontaktami",
"menu.device_info": "Informacje o urządzeniu",
"menu.dm_desc": "Rozmowy prywatne",
"menu.exact_build_title": "Dokładny build",
"menu.filter": "Filtruj wiadomości",
"menu.filter_desc": "Filtruj bieżący czat",
"menu.flood_advert": "Flood advert",
"menu.flood_advert_desc": "Tylko do odbudowy sieci!",
"menu.flood_advert_title": "Flood advert — używaj oszczędnie! Duże zużycie czasu antenowego.",
"menu.history": "Historia wiadomości",
"menu.logs_desc": "Logi aplikacji w czasie rzeczywistym",
"menu.map": "Mapa",
"menu.map_desc": "Wszystkie kontakty z GPS",
"menu.map_title": "Pokaż na mapie wszystkie kontakty z GPS",
"menu.pa_desc": "Analiza ścieżek routingu",
"menu.refresh_desc": "Wczytaj wiadomości ponownie z serwera",
"menu.refresh_title": "Wczytaj wiadomości ponownie z serwera",
"menu.repeaters_desc": "Administracja repeaterami",
"menu.search": "Szukaj w wiadomościach",
"menu.search_desc": "Przeszukaj wszystkie wiadomości",
"menu.section.network": "Sieć",
"menu.section.system": "System",
"menu.section.tools": "Narzędzia",
"menu.select_date_title": "Wybierz datę",
"menu.settings_desc": "Ustawienia aplikacji",
"menu.title": "Menu",
"menu.today_live": "Dziś (na żywo)",
"meta.language_english_name": "Polish",
"meta.language_name": "Polski",
"meta.translator": "mc-webui",
"nav.ble_title": "Bluetooth Low Energy",
"nav.mark_all_read_title": "Oznacz wszystko jako przeczytane",
"nav.select_channel_title": "Wybierz kanał",
"nav.tcp_title": "Połączenie TCP",
"pa.any_hb": "Dowolne HB",
"pa.any_hops": "Dowolne hopy",
"pa.byte_2_3": "2/3 bajty",