feat(i18n): translate the contacts page templates (stage 5a)

54 keys across contacts_base.html and the four contacts-* pages: the shared
delete-confirm modal and toast, the map modal, the three nav cards, the whole
cleanup form, the filter bars, and the add-contact URI/QR/manual forms.

Node types stay English throughout — the COM/REP/ROOM/SENS codes and their
"(Companion)" / "(Room Server)" expansions. The codes are also what the JS filters
compare against, so translating the labels would be a step toward breaking the
type filter.

contacts.js is the other half of this stage and follows separately; it is 2771
lines and carries roughly twice the string count of these five templates.

The hit-count guard earned its keep again: it refused to touch contacts-add.html
and contacts-pending.html because "Name:", "Key:", "Type:", "Cancel" and the
close aria-label each appear two or three times. All were genuinely the same
label reused, so they share a key — but the script stopped rather than guessing,
and the three files it had already processed were left correctly applied.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-07-31 19:20:20 +02:00
parent fa0e3894d5
commit 3668d0e063
7 changed files with 170 additions and 66 deletions
+17 -17
View File
@@ -46,17 +46,17 @@
<!-- URI Paste Tab -->
<div class="tab-pane fade show active" id="pane-uri" role="tabpanel">
<div class="mb-3">
<label for="uriInput" class="form-label">MeshCore URI:</label>
<label for="uriInput" class="form-label">{{ t('contacts.add.uri_label') }}</label>
<textarea class="form-control font-monospace" id="uriInput" rows="3"
placeholder="meshcore://contact/add?name=...&public_key=...&type=..."></textarea>
<small class="form-text text-muted">Paste a meshcore:// URI from the MeshCore mobile app</small>
placeholder="{{ t('contacts.add.uri_ph') }}"></textarea>
<small class="form-text text-muted">{{ t('contacts.add.uri_hint') }}</small>
</div>
<!-- URI Preview -->
<div id="uriPreview" class="alert alert-info d-none mb-3">
<strong>Preview:</strong>
<div><span class="text-muted">Name:</span> <span id="uriPreviewName"></span></div>
<div><span class="text-muted">Key:</span> <span id="uriPreviewKey" class="font-monospace small" style="word-break: break-all;"></span></div>
<div><span class="text-muted">Type:</span> <span id="uriPreviewType"></span></div>
<strong>{{ t('contacts.add.preview') }}</strong>
<div><span class="text-muted">{{ t('contacts.add.name') }}</span> <span id="uriPreviewName"></span></div>
<div><span class="text-muted">{{ t('contacts.add.key') }}</span> <span id="uriPreviewKey" class="font-monospace small" style="word-break: break-all;"></span></div>
<div><span class="text-muted">{{ t('contacts.add.type_short') }}</span> <span id="uriPreviewType"></span></div>
</div>
<button class="btn btn-success" id="addFromUriBtn" disabled>
<i class="bi bi-plus-circle"></i> Add Contact
@@ -79,15 +79,15 @@
</div>
<!-- File Upload Fallback -->
<div class="mb-3">
<label for="qrFileInput" class="form-label">Or upload a QR code image:</label>
<label for="qrFileInput" class="form-label">{{ t('contacts.add.qr_label') }}</label>
<input type="file" class="form-control" id="qrFileInput" accept="image/*">
</div>
<!-- QR Result -->
<div id="qrResult" class="alert alert-success d-none mb-3">
<strong>Scanned:</strong>
<div><span class="text-muted">Name:</span> <span id="qrResultName"></span></div>
<div><span class="text-muted">Key:</span> <span id="qrResultKey" class="font-monospace small" style="word-break: break-all;"></span></div>
<div><span class="text-muted">Type:</span> <span id="qrResultType"></span></div>
<strong>{{ t('contacts.add.scanned') }}</strong>
<div><span class="text-muted">{{ t('contacts.add.name') }}</span> <span id="qrResultName"></span></div>
<div><span class="text-muted">{{ t('contacts.add.key') }}</span> <span id="qrResultKey" class="font-monospace small" style="word-break: break-all;"></span></div>
<div><span class="text-muted">{{ t('contacts.add.type_short') }}</span> <span id="qrResultType"></span></div>
</div>
<div id="qrError" class="alert alert-danger d-none mb-3"></div>
<button class="btn btn-success d-none" id="addFromQrBtn">
@@ -98,17 +98,17 @@
<!-- Manual Entry Tab -->
<div class="tab-pane fade" id="pane-manual" role="tabpanel">
<div class="mb-3">
<label for="manualName" class="form-label">Name:</label>
<input type="text" class="form-control" id="manualName" placeholder="Contact name" maxlength="32">
<label for="manualName" class="form-label">{{ t('contacts.add.name') }}</label>
<input type="text" class="form-control" id="manualName" placeholder="{{ t('contacts.add.name_ph') }}" maxlength="32">
</div>
<div class="mb-3">
<label for="manualKey" class="form-label">Public Key (64 hex chars):</label>
<label for="manualKey" class="form-label">{{ t('contacts.add.pubkey') }}</label>
<input type="text" class="form-control font-monospace" id="manualKey"
placeholder="e.g. a1b2c3d4..." maxlength="64" pattern="[0-9a-fA-F]{64}">
placeholder="{{ t('contacts.add.pubkey_ph') }}" maxlength="64" pattern="[0-9a-fA-F]{64}">
<small class="form-text text-muted" id="manualKeyCount">0 / 64 characters</small>
</div>
<div class="mb-3">
<label for="manualType" class="form-label">Contact Type:</label>
<label for="manualType" class="form-label">{{ t('contacts.add.type') }}</label>
<select class="form-select" id="manualType">
<option value="1" selected>COM (Companion)</option>
<option value="2">REP (Repeater)</option>
+11 -11
View File
@@ -24,23 +24,23 @@
<!-- Search Toolbar -->
<div class="search-toolbar">
<input type="text" class="form-control" id="searchInput" placeholder="Search by name or public key...">
<input type="text" class="form-control" id="searchInput" placeholder="{{ t('contacts.existing.search_ph') }}">
</div>
<!-- Filter and Sort Toolbar -->
<div class="filter-sort-toolbar">
<!-- Source Filter -->
<select class="form-select" id="sourceFilter">
<option value="ALL">All sources</option>
<option value="DEVICE">On device</option>
<option value="CACHE">Cache only</option>
<option value="IGNORED">Ignored</option>
<option value="BLOCKED">Blocked</option>
<option value="ALL">{{ t('contacts.filter.all_sources') }}</option>
<option value="DEVICE">{{ t('contacts.filter.on_device') }}</option>
<option value="CACHE">{{ t('contacts.filter.cache_only') }}</option>
<option value="IGNORED">{{ t('contacts.filter.ignored') }}</option>
<option value="BLOCKED">{{ t('contacts.filter.blocked') }}</option>
</select>
<!-- Type Filter -->
<select class="form-select" id="typeFilter">
<option value="ALL">All Types</option>
<option value="ALL">{{ t('contacts.filter.all_types') }}</option>
<option value="COM">COM</option>
<option value="REP">REP</option>
<option value="ROOM">ROOM</option>
@@ -61,14 +61,14 @@
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3 text-muted">Loading contacts...</p>
<p class="mt-3 text-muted">{{ t('contacts.existing.loading') }}</p>
</div>
<!-- Empty State -->
<div id="existingEmpty" class="empty-state" style="display: none;">
<i class="bi bi-inbox"></i>
<p class="mb-0">No contacts found</p>
<small class="text-muted">Contacts will appear here once added</small>
<p class="mb-0">{{ t('contacts.existing.none') }}</p>
<small class="text-muted">{{ t('contacts.existing.none_hint') }}</small>
</div>
<!-- Existing Contacts List (Full-screen) -->
@@ -79,7 +79,7 @@
<!-- Error State -->
<div id="existingError" class="alert alert-danger" style="display: none;" role="alert">
<i class="bi bi-exclamation-triangle"></i>
<span id="existingErrorMessage">Failed to load contacts</span>
<span id="existingErrorMessage">{{ t('contacts.existing.load_failed') }}</span>
</div>
</div>
{% endblock %}
+18 -18
View File
@@ -10,7 +10,7 @@
<div class="nav-card" onclick="navigateTo('/contacts/add');" style="border-left: 4px solid #198754;">
<div>
<h6><i class="bi bi-person-plus"></i> Add Contact</h6>
<small class="text-muted">Add from URI, QR code, or manual entry</small>
<small class="text-muted">{{ t('contacts.card.add_desc') }}</small>
</div>
<i class="bi bi-chevron-right text-muted"></i>
</div>
@@ -19,7 +19,7 @@
<div class="nav-card" onclick="navigateTo('/contacts/pending');">
<div>
<h6><i class="bi bi-hourglass-split"></i> Pending Contacts</h6>
<small class="text-muted">Contacts awaiting manual approval</small>
<small class="text-muted">{{ t('contacts.card.pending_desc') }}</small>
</div>
<span class="badge bg-primary rounded-pill" id="pendingBadge" style="font-size: 1.1rem;">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
@@ -30,7 +30,7 @@
<div class="nav-card" onclick="navigateTo('/contacts/existing');">
<div>
<h6><i class="bi bi-person-lines-fill"></i> Existing Contacts</h6>
<small class="text-muted">Manage all stored contacts</small>
<small class="text-muted">{{ t('contacts.card.existing_desc') }}</small>
</div>
<span class="badge counter-badge counter-ok rounded-pill" id="existingBadge" style="font-size: 1.1rem;">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
@@ -42,17 +42,17 @@
<div class="cleanup-section">
<h6>
<i class="bi bi-trash"></i>
<span>Cleanup Contacts</span>
<span>{{ t('contacts.cleanup.title') }}</span>
<i class="bi bi-info-circle info-icon"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Remove contacts based on various filter criteria"></i>
title="{{ t('contacts.cleanup.title_hint') }}"></i>
</h6>
<!-- Basic Filters (Always visible) -->
<div class="mb-3">
<label for="cleanupNameFilter" class="form-label">Name Filter (optional):</label>
<input type="text" class="form-control" id="cleanupNameFilter" placeholder="Enter partial name to search...">
<label for="cleanupNameFilter" class="form-label">{{ t('contacts.cleanup.name_filter') }}</label>
<input type="text" class="form-control" id="cleanupNameFilter" placeholder="{{ t('contacts.cleanup.name_filter_ph') }}">
</div>
<!-- Advanced Filters (Collapsible) -->
@@ -65,7 +65,7 @@
<div class="collapse" id="advancedFilters">
<!-- Contact Types -->
<div class="mb-3">
<label class="form-label">Contact Types:</label>
<label class="form-label">{{ t('contacts.cleanup.types') }}</label>
<div class="d-flex flex-wrap gap-2">
<div class="form-check">
<input class="form-check-input cleanup-type-filter" type="checkbox" value="1" id="cleanupTypeCOM" checked>
@@ -88,15 +88,15 @@
<!-- Date Field -->
<div class="mb-3">
<label class="form-label">Date Field:</label>
<label class="form-label">{{ t('contacts.cleanup.date_field') }}</label>
<div class="d-flex gap-3">
<div class="form-check">
<input class="form-check-input" type="radio" name="cleanupDateField" id="cleanupDateLastAdvert" value="last_advert" checked>
<label class="form-check-label" for="cleanupDateLastAdvert">Last Advert</label>
<label class="form-check-label" for="cleanupDateLastAdvert">{{ t('contacts.cleanup.last_advert') }}</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="cleanupDateField" id="cleanupDateLastMod" value="lastmod">
<label class="form-check-label" for="cleanupDateLastMod">Last Modified</label>
<label class="form-check-label" for="cleanupDateLastMod">{{ t('contacts.cleanup.last_modified') }}</label>
</div>
</div>
</div>
@@ -105,7 +105,7 @@
<div class="mb-3">
<label for="cleanupDays" class="form-label">Days of Inactivity (0 = ignore):</label>
<input type="number" class="form-control" id="cleanupDays" value="0" min="0">
<small class="form-text text-muted">Contacts inactive for more than this many days will be selected</small>
<small class="form-text text-muted">{{ t('contacts.cleanup.days_hint') }}</small>
</div>
<!-- Auto-Cleanup Toggle -->
@@ -113,18 +113,18 @@
<div class="form-check form-switch d-flex align-items-center gap-2">
<input class="form-check-input" type="checkbox" role="switch" id="autoCleanupSwitch" style="cursor: pointer; min-width: 3rem; min-height: 1.5rem;">
<label class="form-check-label" for="autoCleanupSwitch" style="cursor: pointer; font-weight: 500;">
<span id="autoCleanupLabel">Enable Auto-Cleanup</span>
<span id="autoCleanupLabel">{{ t('contacts.cleanup.enable') }}</span>
</label>
<i class="bi bi-info-circle info-icon"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="When enabled, contacts matching the above criteria will be automatically deleted daily at the specified hour"></i>
title="{{ t('contacts.cleanup.enable_hint') }}"></i>
</div>
<small class="text-muted">Status: <span id="autoCleanupStatusText">Loading...</span></small>
<!-- Cleanup Hour Selector -->
<div class="mt-2 d-flex align-items-center gap-2">
<label for="cleanupHour" class="form-label mb-0 small">Run at:</label>
<label for="cleanupHour" class="form-label mb-0 small">{{ t('contacts.cleanup.run_at') }}</label>
<select class="form-select form-select-sm" id="cleanupHour" style="width: auto;" disabled>
<option value="0">00:00</option>
<option value="1" selected>01:00</option>
@@ -170,7 +170,7 @@
<h5 class="modal-title" id="cleanupConfirmModalLabel">
<i class="bi bi-exclamation-triangle"></i> Confirm Contact Cleanup
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ t('common.close') }}"></button>
</div>
<div class="modal-body">
<p class="fw-bold">The following <span id="cleanupContactCount">0</span> contact(s) will be permanently deleted:</p>
@@ -179,11 +179,11 @@
</div>
<div class="alert alert-warning mb-0">
<i class="bi bi-exclamation-circle"></i>
<strong>Warning:</strong> This action cannot be undone!
<strong>{{ t('common.warning') }}</strong> This action cannot be undone!
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t('common.cancel') }}</button>
<button type="button" class="btn btn-danger" id="confirmCleanupBtn">
<i class="bi bi-trash"></i> Delete Contacts
</button>
+10 -10
View File
@@ -30,7 +30,7 @@
<i class="bi bi-funnel"></i> Filters
<i class="bi bi-info-circle text-muted ms-1" role="button" tabindex="0"
data-bs-toggle="tooltip" data-bs-placement="right"
title="Filter contacts by type or name, then approve or ignore them in bulk."></i>
title="{{ t('contacts.pending.filter_hint') }}"></i>
</h6>
<!-- Type Filter Badges -->
@@ -44,7 +44,7 @@
<!-- Search + Batch Action Buttons -->
<div class="d-flex gap-2">
<input type="text" class="form-control form-control-sm" id="pendingSearchInput"
placeholder="Search..." style="min-width: 0;">
placeholder="{{ t('logs.filter.search_ph') }}" style="min-width: 0;">
<button class="btn btn-success btn-sm flex-shrink-0" id="addFilteredBtn">
<i class="bi bi-check-circle-fill"></i> Approve
</button>
@@ -61,14 +61,14 @@
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3 text-muted">Loading pending contacts...</p>
<p class="mt-3 text-muted">{{ t('contacts.pending.loading') }}</p>
</div>
<!-- Empty State -->
<div id="pendingEmpty" class="empty-state" style="display: none;">
<i class="bi bi-check-circle"></i>
<p class="mb-0">No pending requests</p>
<small class="text-muted">New contact requests will appear here when manual approval is enabled</small>
<p class="mb-0">{{ t('contacts.pending.none') }}</p>
<small class="text-muted">{{ t('contacts.pending.none_hint') }}</small>
</div>
<!-- Pending Contacts List (Full-screen) -->
@@ -79,7 +79,7 @@
<!-- Error State -->
<div id="pendingError" class="alert alert-danger" style="display: none;" role="alert">
<i class="bi bi-exclamation-triangle"></i>
<span id="pendingErrorMessage">Failed to load pending contacts</span>
<span id="pendingErrorMessage">{{ t('contacts.pending.load_failed') }}</span>
</div>
</div>
@@ -91,7 +91,7 @@
<h5 class="modal-title" id="batchApprovalModalLabel">
<i class="bi bi-check-circle"></i> Confirm Batch Approval
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="{{ t('common.close') }}"></button>
</div>
<div class="modal-body">
<p class="mb-2">You are about to approve <strong id="batchApprovalCount">0</strong> contacts:</p>
@@ -106,7 +106,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t('common.cancel') }}</button>
<button type="button" class="btn btn-success" id="confirmBatchApprovalBtn">
<i class="bi bi-check-circle-fill"></i> Approve All
</button>
@@ -123,7 +123,7 @@
<h5 class="modal-title" id="batchIgnoreModalLabel">
<i class="bi bi-eye-slash"></i> Confirm Batch Ignore
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="{{ t('common.close') }}"></button>
</div>
<div class="modal-body">
<p class="mb-2">You are about to ignore <strong id="batchIgnoreCount">0</strong> contacts:</p>
@@ -136,7 +136,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t('common.cancel') }}</button>
<button type="button" class="btn btn-outline-secondary" id="confirmBatchIgnoreBtn">
<i class="bi bi-eye-slash"></i> Ignore All
</button>
+10 -10
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>{% block title %}Contact Management - mc-webui{% endblock %}</title>
<title>{% block title %}{{ t('contacts.title') }} - mc-webui{% endblock %}</title>
<!-- Theme: apply saved preference before CSS loads to prevent flash -->
<script>
@@ -257,22 +257,22 @@
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="deleteContactModalLabel">
<i class="bi bi-exclamation-triangle"></i> Confirm Delete
<i class="bi bi-exclamation-triangle"></i> {{ t('contacts.delete.title') }}
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="{{ t('common.close') }}"></button>
</div>
<div class="modal-body">
<p class="mb-2">Are you sure you want to delete this contact?</p>
<p class="mb-2">{{ t('contacts.delete.question') }}</p>
<div class="alert alert-warning mb-0">
<strong id="deleteContactName"></strong><br>
<small class="font-monospace" id="deleteContactKey"></small>
</div>
<p class="text-muted small mt-2 mb-0">This action cannot be undone.</p>
<p class="text-muted small mt-2 mb-0">{{ t('common.cannot_undo') }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t('common.cancel') }}</button>
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">
<i class="bi bi-trash"></i> Delete Contact
<i class="bi bi-trash"></i> {{ t('contacts.delete.confirm') }}
</button>
</div>
</div>
@@ -283,7 +283,7 @@
<div class="toast-container position-fixed top-0 start-0 p-3" data-toast-container>
<div id="contactToast" class="toast" role="alert">
<div class="toast-header">
<strong class="me-auto">Contact Management</strong>
<strong class="me-auto">{{ t('contacts.title') }}</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body"></div>
@@ -295,14 +295,14 @@
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-geo-alt"></i> <span id="mapModalTitle">Map</span></h5>
<h5 class="modal-title"><i class="bi bi-geo-alt"></i> <span id="mapModalTitle">{{ t('contacts.map.title') }}</span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0">
<!-- Type filter (hidden for single contact view) -->
<div id="mapTypeFilter" class="d-none px-3 py-2 border-bottom bg-light">
<div class="d-flex flex-wrap gap-2 align-items-center">
<span class="small text-muted me-1">Show:</span>
<span class="small text-muted me-1">{{ t('contacts.map.show') }}</span>
<span class="map-filter-badge active" data-type="1" id="mapFilterCOM">COM</span>
<span class="map-filter-badge active" data-type="2" id="mapFilterREP">REP</span>
<span class="map-filter-badge active" data-type="3" id="mapFilterROOM">ROOM</span>
+52
View File
@@ -1,6 +1,7 @@
{
"common.add": "Add",
"common.cancel": "Cancel",
"common.cannot_undo": "This action cannot be undone.",
"common.close": "Close",
"common.days_ago": {
"one": "{count}d ago",
@@ -16,6 +17,7 @@
"common.remove": "Remove",
"common.save": "Save",
"common.try_again": "Try again",
"common.warning": "Warning:",
"common.yesterday": "Yesterday",
"console.clear_title": "Clear output history",
"console.connect_failed": "Failed to connect: {error}",
@@ -31,6 +33,56 @@
"console.status.connecting": "Connecting...",
"console.status.disconnected": "Disconnected",
"console.title": "Console",
"contacts.add.key": "Key:",
"contacts.add.name": "Name:",
"contacts.add.name_ph": "Contact name",
"contacts.add.preview": "Preview:",
"contacts.add.pubkey": "Public Key (64 hex chars):",
"contacts.add.pubkey_ph": "e.g. a1b2c3d4...",
"contacts.add.qr_label": "Or upload a QR code image:",
"contacts.add.scanned": "Scanned:",
"contacts.add.type": "Contact Type:",
"contacts.add.type_short": "Type:",
"contacts.add.uri_hint": "Paste a meshcore:// URI from the MeshCore mobile app",
"contacts.add.uri_label": "MeshCore URI:",
"contacts.add.uri_ph": "meshcore://contact/add?name=...&public_key=...&type=...",
"contacts.card.add_desc": "Add from URI, QR code, or manual entry",
"contacts.card.existing_desc": "Manage all stored contacts",
"contacts.card.pending_desc": "Contacts awaiting manual approval",
"contacts.cleanup.date_field": "Date Field:",
"contacts.cleanup.days_hint": "Contacts inactive for more than this many days will be selected",
"contacts.cleanup.enable": "Enable Auto-Cleanup",
"contacts.cleanup.enable_hint": "When enabled, contacts matching the above criteria will be automatically deleted daily at the specified hour",
"contacts.cleanup.last_advert": "Last Advert",
"contacts.cleanup.last_modified": "Last Modified",
"contacts.cleanup.name_filter": "Name Filter (optional):",
"contacts.cleanup.name_filter_ph": "Enter partial name to search...",
"contacts.cleanup.run_at": "Run at:",
"contacts.cleanup.title": "Cleanup Contacts",
"contacts.cleanup.title_hint": "Remove contacts based on various filter criteria",
"contacts.cleanup.types": "Contact Types:",
"contacts.delete.confirm": "Delete Contact",
"contacts.delete.question": "Are you sure you want to delete this contact?",
"contacts.delete.title": "Confirm Delete",
"contacts.existing.load_failed": "Failed to load contacts",
"contacts.existing.loading": "Loading contacts...",
"contacts.existing.none": "No contacts found",
"contacts.existing.none_hint": "Contacts will appear here once added",
"contacts.existing.search_ph": "Search by name or public key...",
"contacts.filter.all_sources": "All sources",
"contacts.filter.all_types": "All Types",
"contacts.filter.blocked": "Blocked",
"contacts.filter.cache_only": "Cache only",
"contacts.filter.ignored": "Ignored",
"contacts.filter.on_device": "On device",
"contacts.map.show": "Show:",
"contacts.map.title": "Map",
"contacts.pending.filter_hint": "Filter contacts by type or name, then approve or ignore them in bulk.",
"contacts.pending.load_failed": "Failed to load pending contacts",
"contacts.pending.loading": "Loading pending contacts...",
"contacts.pending.none": "No pending requests",
"contacts.pending.none_hint": "New contact requests will appear here when manual approval is enabled",
"contacts.title": "Contact Management",
"logs.clear_title": "Clear display",
"logs.entries": {
"one": "{count} entry",
+52
View File
@@ -1,6 +1,7 @@
{
"common.add": "Dodaj",
"common.cancel": "Anuluj",
"common.cannot_undo": "Tej operacji nie można cofnąć.",
"common.close": "Zamknij",
"common.days_ago": {
"one": "{count} dzień temu",
@@ -18,6 +19,7 @@
"common.remove": "Usuń",
"common.save": "Zapisz",
"common.try_again": "Spróbuj ponownie",
"common.warning": "Uwaga:",
"common.yesterday": "Wczoraj",
"console.clear_title": "Wyczyść zapis konsoli",
"console.connect_failed": "Nie udało się połączyć: {error}",
@@ -33,6 +35,56 @@
"console.status.connecting": "Łączenie...",
"console.status.disconnected": "Rozłączono",
"console.title": "Konsola",
"contacts.add.key": "Klucz:",
"contacts.add.name": "Nazwa:",
"contacts.add.name_ph": "Nazwa kontaktu",
"contacts.add.preview": "Podgląd:",
"contacts.add.pubkey": "Klucz publiczny (64 znaki hex):",
"contacts.add.pubkey_ph": "np. a1b2c3d4...",
"contacts.add.qr_label": "Albo wgraj obraz z kodem QR:",
"contacts.add.scanned": "Zeskanowano:",
"contacts.add.type": "Typ kontaktu:",
"contacts.add.type_short": "Typ:",
"contacts.add.uri_hint": "Wklej URI meshcore:// z aplikacji mobilnej MeshCore",
"contacts.add.uri_label": "MeshCore URI:",
"contacts.add.uri_ph": "meshcore://contact/add?name=...&public_key=...&type=...",
"contacts.card.add_desc": "Dodaj z URI, kodu QR lub ręcznie",
"contacts.card.existing_desc": "Zarządzaj wszystkimi zapisanymi kontaktami",
"contacts.card.pending_desc": "Kontakty oczekujące na ręczne zatwierdzenie",
"contacts.cleanup.date_field": "Pole daty:",
"contacts.cleanup.days_hint": "Zaznaczone zostaną kontakty nieaktywne dłużej niż tyle dni",
"contacts.cleanup.enable": "Włącz automatyczne czyszczenie",
"contacts.cleanup.enable_hint": "Gdy włączone, kontakty spełniające powyższe kryteria będą usuwane codziennie o wskazanej godzinie",
"contacts.cleanup.last_advert": "Ostatni advert",
"contacts.cleanup.last_modified": "Ostatnia zmiana",
"contacts.cleanup.name_filter": "Filtr nazwy (opcjonalnie):",
"contacts.cleanup.name_filter_ph": "Wpisz fragment nazwy do wyszukania...",
"contacts.cleanup.run_at": "Uruchom o:",
"contacts.cleanup.title": "Czyszczenie kontaktów",
"contacts.cleanup.title_hint": "Usuń kontakty według różnych kryteriów",
"contacts.cleanup.types": "Typy kontaktów:",
"contacts.delete.confirm": "Usuń kontakt",
"contacts.delete.question": "Czy na pewno chcesz usunąć ten kontakt?",
"contacts.delete.title": "Potwierdź usunięcie",
"contacts.existing.load_failed": "Nie udało się wczytać kontaktów",
"contacts.existing.loading": "Wczytywanie kontaktów...",
"contacts.existing.none": "Nie znaleziono kontaktów",
"contacts.existing.none_hint": "Kontakty pojawią się tutaj po dodaniu",
"contacts.existing.search_ph": "Szukaj po nazwie lub kluczu publicznym...",
"contacts.filter.all_sources": "Wszystkie źródła",
"contacts.filter.all_types": "Wszystkie typy",
"contacts.filter.blocked": "Zablokowane",
"contacts.filter.cache_only": "Tylko w pamięci",
"contacts.filter.ignored": "Ignorowane",
"contacts.filter.on_device": "Na urządzeniu",
"contacts.map.show": "Pokaż:",
"contacts.map.title": "Mapa",
"contacts.pending.filter_hint": "Filtruj kontakty po typie lub nazwie, a następnie zatwierdź lub zignoruj je zbiorczo.",
"contacts.pending.load_failed": "Nie udało się wczytać oczekujących kontaktów",
"contacts.pending.loading": "Wczytywanie oczekujących kontaktów...",
"contacts.pending.none": "Brak oczekujących zgłoszeń",
"contacts.pending.none_hint": "Nowe zgłoszenia kontaktów pojawią się tutaj, gdy włączone jest ręczne zatwierdzanie",
"contacts.title": "Zarządzanie kontaktami",
"logs.clear_title": "Wyczyść widok",
"logs.entries": {
"one": "{count} wpis",