mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-08-07 09:12:57 +02:00
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:
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user