fix(i18n): finish the contacts templates and three Settings spinners

Stage 5a translated the contacts pages but the browser diff was only run
against /contacts/existing, so the other three kept most of their page
headers, action buttons and confirmation modals in English: page titles,
"Add Contact", "Pending Contacts", Refresh, Filters, Approve/Ignore and
both batch-confirmation dialogs, plus Advanced Filters, Preview Cleanup
and the cleanup confirmation on /contacts/manage. Most of the keys they
needed already existed from 5a - the template sites were simply never
wired to them.

Three spinners in the Settings modal (Regions, Analyzer, Observer lists)
were missed in 8b for the opposite reason: their containers are excluded
from the stage-8 browser check as JS-populated, but the spinner inside is
server-rendered, so excluding the container hid it.

Root cause of both, and the reason for the new tool: a browser diff only
sees what is visible on the page it is pointed at. Add
i18n-template-diff.py, which fetches each entry point's HTML in en and pl
and compares text nodes directly. No browser, no per-page script, and it
covers closed modals and every page in one run - it found all of the
above in a single pass. It cannot see anything JavaScript writes, so it
complements the Playwright scripts rather than replacing them.

Every server-rendered template in the project is now translated. The
whole-project run reports 11 identical strings, all correct: the device
name, version strings, the language names (which are supposed to match),
"1 hop" (identical in Polish) and the glossary terms Hash, HB, Repeater.

Catalog: 760 -> 781 keys, pl at 100%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-08-01 10:29:30 +02:00
parent 55737fa8c6
commit 88ca23bccb
7 changed files with 85 additions and 42 deletions
+2 -2
View File
@@ -2661,7 +2661,7 @@ function handleManualKeyInput() {
const input = document.getElementById('manualKey');
// Allow only hex characters
input.value = input.value.replace(/[^0-9a-fA-F]/g, '').toLowerCase();
document.getElementById('manualKeyCount').textContent = `${input.value.length} / 64 characters`;
document.getElementById('manualKeyCount').textContent = t('contacts.add.key_count', { n: input.value.length });
validateManualForm();
}
@@ -2729,7 +2729,7 @@ function resetAddForm(mode) {
} else if (mode === 'manual') {
document.getElementById('manualName').value = '';
document.getElementById('manualKey').value = '';
document.getElementById('manualKeyCount').textContent = '0 / 64 characters';
document.getElementById('manualKeyCount').textContent = t('contacts.add.key_count', { n: 0 });
document.getElementById('addManualBtn').disabled = true;
}
}
+3 -3
View File
@@ -1040,7 +1040,7 @@
<h6 class="mb-2">{{ t('settings.regions.registry') }}</h6>
<div id="regionsList" class="list-group mb-3">
<div class="text-center text-muted py-3 small">
<div class="spinner-border spinner-border-sm"></div> Loading...
<div class="spinner-border spinner-border-sm"></div> {{ t('common.loading') }}
</div>
</div>
<form id="addRegionForm" class="row g-2 mb-0">
@@ -1065,7 +1065,7 @@
<h6 class="mb-2">{{ t('settings.analyzer.services') }}</h6>
<div id="analyzersList" class="list-group mb-3">
<div class="text-center text-muted py-3 small">
<div class="spinner-border spinner-border-sm"></div> Loading...
<div class="spinner-border spinner-border-sm"></div> {{ t('common.loading') }}
</div>
</div>
<button type="button" class="btn btn-primary btn-sm" id="addAnalyzerBtn">
@@ -1103,7 +1103,7 @@
<h6 class="mb-2">{{ t('settings.observer.brokers') }}</h6>
<div id="observerBrokersList" class="list-group mb-3">
<div class="text-center text-muted py-3 small">
<div class="spinner-border spinner-border-sm"></div> Loading...
<div class="spinner-border spinner-border-sm"></div> {{ t('common.loading') }}
</div>
</div>
<button type="button" class="btn btn-primary btn-sm" id="addObserverBrokerBtn">
+10 -10
View File
@@ -1,6 +1,6 @@
{% extends "contacts_base.html" %}
{% block title %}Add Contact - mc-webui{% endblock %}
{% block title %}{{ t('contacts.add.title') }} - mc-webui{% endblock %}
{% block extra_head %}
<!-- html5-qrcode for QR scanning -->
@@ -12,7 +12,7 @@
<!-- Page Header -->
<div class="mb-3">
<h4 class="mb-2">
<i class="bi bi-person-plus"></i> Add Contact
<i class="bi bi-person-plus"></i> {{ t('contacts.add.title') }}
</h4>
</div>
@@ -32,12 +32,12 @@
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="tab-qr" data-bs-toggle="tab" data-bs-target="#pane-qr" type="button" role="tab">
<i class="bi bi-qr-code-scan"></i> QR Code
<i class="bi bi-qr-code-scan"></i> {{ t('channels.qr_alt') }}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="tab-manual" data-bs-toggle="tab" data-bs-target="#pane-manual" type="button" role="tab">
<i class="bi bi-pencil"></i> Manual
<i class="bi bi-pencil"></i> {{ t('contacts.add.tab_manual') }}
</button>
</li>
</ul>
@@ -59,7 +59,7 @@
<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
<i class="bi bi-plus-circle"></i> {{ t('contacts.add.title') }}
</button>
</div>
@@ -70,10 +70,10 @@
<div id="qrReader" style="width: 100%; max-width: 500px;"></div>
<div id="qrCameraButtons" class="d-flex gap-2 mt-2">
<button class="btn btn-primary btn-sm" id="startCameraBtn">
<i class="bi bi-camera-video"></i> Start Camera
<i class="bi bi-camera-video"></i> {{ t('contacts.add.start_camera') }}
</button>
<button class="btn btn-outline-secondary btn-sm d-none" id="stopCameraBtn">
<i class="bi bi-stop-circle"></i> Stop Camera
<i class="bi bi-stop-circle"></i> {{ t('contacts.add.stop_camera') }}
</button>
</div>
</div>
@@ -91,7 +91,7 @@
</div>
<div id="qrError" class="alert alert-danger d-none mb-3"></div>
<button class="btn btn-success d-none" id="addFromQrBtn">
<i class="bi bi-plus-circle"></i> Add Contact
<i class="bi bi-plus-circle"></i> {{ t('contacts.add.title') }}
</button>
</div>
@@ -105,7 +105,7 @@
<label for="manualKey" class="form-label">{{ t('contacts.add.pubkey') }}</label>
<input type="text" class="form-control font-monospace" id="manualKey"
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>
<small class="form-text text-muted" id="manualKeyCount">{{ t('contacts.add.key_count', n=0) }}</small>
</div>
<div class="mb-3">
<label for="manualType" class="form-label">{{ t('contacts.add.type') }}</label>
@@ -117,7 +117,7 @@
</select>
</div>
<button class="btn btn-success" id="addManualBtn" disabled>
<i class="bi bi-plus-circle"></i> Add Contact
<i class="bi bi-plus-circle"></i> {{ t('contacts.add.title') }}
</button>
</div>
</div>
+13 -12
View File
@@ -1,6 +1,6 @@
{% extends "contacts_base.html" %}
{% block title %}Contact Management Settings - mc-webui{% endblock %}
{% block title %}{{ t('contacts.manage.title') }} - mc-webui{% endblock %}
{% block page_content %}
<div id="managePageContent" class="p-3">
@@ -9,7 +9,7 @@
<!-- Add Contact Card -->
<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>
<h6><i class="bi bi-person-plus"></i> {{ t('contacts.add.title') }}</h6>
<small class="text-muted">{{ t('contacts.card.add_desc') }}</small>
</div>
<i class="bi bi-chevron-right text-muted"></i>
@@ -18,7 +18,7 @@
<!-- Pending Contacts Card -->
<div class="nav-card" onclick="navigateTo('/contacts/pending');">
<div>
<h6><i class="bi bi-hourglass-split"></i> Pending Contacts</h6>
<h6><i class="bi bi-hourglass-split"></i> {{ t('contacts.pending.title') }}</h6>
<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;">
@@ -29,7 +29,7 @@
<!-- Existing Contacts Card -->
<div class="nav-card" onclick="navigateTo('/contacts/existing');">
<div>
<h6><i class="bi bi-person-lines-fill"></i> Existing Contacts</h6>
<h6><i class="bi bi-person-lines-fill"></i> {{ t('contacts.existing.title') }}</h6>
<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;">
@@ -58,7 +58,7 @@
<!-- Advanced Filters (Collapsible) -->
<div class="mb-3">
<button class="btn btn-sm btn-outline-secondary w-100" type="button" data-bs-toggle="collapse" data-bs-target="#advancedFilters" aria-expanded="false" aria-controls="advancedFilters">
<i class="bi bi-sliders"></i> Advanced Filters
<i class="bi bi-sliders"></i> {{ t('contacts.filter.advanced') }}
</button>
</div>
@@ -103,7 +103,7 @@
<!-- Days of Inactivity -->
<div class="mb-3">
<label for="cleanupDays" class="form-label">Days of Inactivity (0 = ignore):</label>
<label for="cleanupDays" class="form-label">{{ t('contacts.cleanup.days') }}</label>
<input type="number" class="form-control" id="cleanupDays" value="0" min="0">
<small class="form-text text-muted">{{ t('contacts.cleanup.days_hint') }}</small>
</div>
@@ -120,7 +120,7 @@
data-bs-placement="top"
title="{{ t('contacts.cleanup.enable_hint') }}"></i>
</div>
<small class="text-muted">Status: <span id="autoCleanupStatusText">Loading...</span></small>
<small class="text-muted">{{ t('contacts.cleanup.status_label') }} <span id="autoCleanupStatusText">{{ t('common.loading') }}</span></small>
<!-- Cleanup Hour Selector -->
<div class="mt-2 d-flex align-items-center gap-2">
@@ -157,7 +157,7 @@
</div>
<button class="btn btn-warning" id="cleanupPreviewBtn">
<i class="bi bi-eye"></i> Preview Cleanup
<i class="bi bi-eye"></i> {{ t('contacts.cleanup.preview') }}
</button>
</div>
</div>
@@ -168,24 +168,25 @@
<div class="modal-content">
<div class="modal-header bg-warning">
<h5 class="modal-title" id="cleanupConfirmModalLabel">
<i class="bi bi-exclamation-triangle"></i> Confirm Contact Cleanup
<i class="bi bi-exclamation-triangle"></i> {{ t('contacts.cleanup.confirm_title') }}
</h5>
<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>
<!-- The count element is inside the catalog value; it must survive translation. -->
<p class="fw-bold">{{ t_html('contacts.cleanup.delete_confirm') }}</p>
<div id="cleanupContactList" class="list-group mb-3" style="max-height: 300px; overflow-y: auto;">
<!-- Contact list will be populated here -->
</div>
<div class="alert alert-warning mb-0">
<i class="bi bi-exclamation-circle"></i>
<strong>{{ t('common.warning') }}</strong> This action cannot be undone!
<strong>{{ t('common.warning') }}</strong> {{ t('contacts.cleanup.undo_warning') }}
</div>
</div>
<div class="modal-footer">
<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
<i class="bi bi-trash"></i> {{ t('contacts.cleanup.delete_btn') }}
</button>
</div>
</div>
+15 -15
View File
@@ -1,13 +1,13 @@
{% extends "contacts_base.html" %}
{% block title %}Pending Contacts - mc-webui{% endblock %}
{% block title %}{{ t('contacts.pending.title') }} - mc-webui{% endblock %}
{% block page_content %}
<div id="pendingPageContent" class="p-3">
<!-- Page Header -->
<div class="mb-3">
<h4 class="mb-2">
<i class="bi bi-hourglass-split"></i> Pending Contacts
<i class="bi bi-hourglass-split"></i> {{ t('contacts.pending.title') }}
<span class="badge bg-primary rounded-pill" id="pendingCountBadge" style="display: none; font-size: 0.8rem;">0</span>
</h4>
</div>
@@ -18,7 +18,7 @@
<i class="bi bi-arrow-left"></i> {{ t('common.back') }}
</button>
<button class="btn btn-outline-primary btn-sm" id="refreshPendingBtn">
<i class="bi bi-arrow-clockwise"></i> Refresh
<i class="bi bi-arrow-clockwise"></i> {{ t('common.refresh') }}
</button>
</div>
@@ -27,7 +27,7 @@
<div class="card">
<div class="card-body p-3">
<h6 class="mb-2">
<i class="bi bi-funnel"></i> Filters
<i class="bi bi-funnel"></i> {{ t('pa.filters') }}
<i class="bi bi-info-circle text-muted ms-1" role="button" tabindex="0"
data-bs-toggle="tooltip" data-bs-placement="right"
title="{{ t('contacts.pending.filter_hint') }}"></i>
@@ -46,10 +46,10 @@
<input type="text" class="form-control form-control-sm" id="pendingSearchInput"
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
<i class="bi bi-check-circle-fill"></i> {{ t('contacts.btn.approve') }}
</button>
<button class="btn btn-outline-secondary btn-sm flex-shrink-0" id="ignoreFilteredBtn">
<i class="bi bi-eye-slash"></i> Ignore
<i class="bi bi-eye-slash"></i> {{ t('contacts.btn.ignore') }}
</button>
</div>
</div>
@@ -59,7 +59,7 @@
<!-- Loading State -->
<div id="pendingLoading" class="text-center py-5" style="display: none;">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
<span class="visually-hidden">{{ t('common.loading') }}</span>
</div>
<p class="mt-3 text-muted">{{ t('contacts.pending.loading') }}</p>
</div>
@@ -89,12 +89,12 @@
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title" id="batchApprovalModalLabel">
<i class="bi bi-check-circle"></i> Confirm Batch Approval
<i class="bi bi-check-circle"></i> {{ t('contacts.pending.confirm_approve_title') }}
</h5>
<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>
<p class="mb-2">{{ t_html('contacts.pending.approve_confirm') }}</p>
<!-- List of contacts to approve -->
<div class="list-group mb-3" id="batchApprovalList" style="max-height: 300px; overflow-y: auto;">
@@ -102,13 +102,13 @@
</div>
<div class="alert alert-info mb-0">
<i class="bi bi-info-circle"></i> These contacts will be added to your device and can receive/send messages.
<i class="bi bi-info-circle"></i> {{ t('contacts.pending.approve_note') }}
</div>
</div>
<div class="modal-footer">
<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
<i class="bi bi-check-circle-fill"></i> {{ t('contacts.btn.approve_all') }}
</button>
</div>
</div>
@@ -121,24 +121,24 @@
<div class="modal-content">
<div class="modal-header bg-secondary text-white">
<h5 class="modal-title" id="batchIgnoreModalLabel">
<i class="bi bi-eye-slash"></i> Confirm Batch Ignore
<i class="bi bi-eye-slash"></i> {{ t('contacts.pending.confirm_ignore_title') }}
</h5>
<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>
<p class="mb-2">{{ t_html('contacts.pending.ignore_confirm') }}</p>
<div class="list-group mb-3" id="batchIgnoreList" style="max-height: 300px; overflow-y: auto;">
</div>
<div class="alert alert-warning mb-0">
<i class="bi bi-info-circle"></i> Ignored contacts will not trigger new pending requests. You can unignore them later from the Existing Contacts page.
<i class="bi bi-info-circle"></i> {{ t('contacts.pending.ignore_note') }}
</div>
</div>
<div class="modal-footer">
<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
<i class="bi bi-eye-slash"></i> {{ t('contacts.btn.ignore_all') }}
</button>
</div>
</div>
+21
View File
@@ -127,6 +127,7 @@
"contacts.add.adding": "Adding contact...",
"contacts.add.failed": "Failed to add contact.",
"contacts.add.key": "Key:",
"contacts.add.key_count": "{n} / 64 characters",
"contacts.add.name": "Name:",
"contacts.add.name_ph": "Contact name",
"contacts.add.preview": "Preview:",
@@ -134,7 +135,11 @@
"contacts.add.pubkey_ph": "e.g. a1b2c3d4...",
"contacts.add.qr_label": "Or upload a QR code image:",
"contacts.add.scanned": "Scanned:",
"contacts.add.start_camera": "Start Camera",
"contacts.add.stop_camera": "Stop Camera",
"contacts.add.success": "Contact added successfully!",
"contacts.add.tab_manual": "Manual",
"contacts.add.title": "Add Contact",
"contacts.add.type": "Contact Type:",
"contacts.add.type_short": "Type:",
"contacts.add.uri_hint": "Paste a meshcore:// URI from the MeshCore mobile app",
@@ -159,18 +164,25 @@
"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.confirm_title": "Confirm Contact Cleanup",
"contacts.cleanup.date_field": "Date Field:",
"contacts.cleanup.days": "Days of Inactivity (0 = ignore):",
"contacts.cleanup.days_hint": "Contacts inactive for more than this many days will be selected",
"contacts.cleanup.delete_btn": "Delete Contacts",
"contacts.cleanup.delete_confirm": "The following <span id=\"cleanupContactCount\">0</span> contact(s) will be permanently deleted:",
"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.preview": "Preview Cleanup",
"contacts.cleanup.run_at": "Run at:",
"contacts.cleanup.status_label": "Status:",
"contacts.cleanup.title": "Cleanup Contacts",
"contacts.cleanup.title_hint": "Remove contacts based on various filter criteria",
"contacts.cleanup.types": "Contact Types:",
"contacts.cleanup.undo_warning": "This action cannot be undone!",
"contacts.confirm.block": "Block this contact? Their messages will be hidden from chat.",
"contacts.copy_title": "Click to copy",
"contacts.delete.confirm": "Delete Contact",
@@ -182,6 +194,7 @@
"contacts.existing.none_hint": "Contacts will appear here once added",
"contacts.existing.search_ph": "Search by name or public key...",
"contacts.existing.title": "Existing Contacts",
"contacts.filter.advanced": "Advanced Filters",
"contacts.filter.all_sources": "All sources",
"contacts.filter.all_types": "All Types",
"contacts.filter.blocked": "Blocked",
@@ -195,14 +208,22 @@
"contacts.inactive": "Inactive",
"contacts.last_advert": "Last advert: {time}",
"contacts.last_advert_unknown": "Last advert: Unknown",
"contacts.manage.title": "Contact Management Settings",
"contacts.map.show": "Show:",
"contacts.map.title": "Map",
"contacts.never_seen": "Never seen",
"contacts.pending.approve_confirm": "You are about to approve <strong id=\"batchApprovalCount\">0</strong> contacts:",
"contacts.pending.approve_note": "These contacts will be added to your device and can receive/send messages.",
"contacts.pending.confirm_approve_title": "Confirm Batch Approval",
"contacts.pending.confirm_ignore_title": "Confirm Batch Ignore",
"contacts.pending.filter_hint": "Filter contacts by type or name, then approve or ignore them in bulk.",
"contacts.pending.ignore_confirm": "You are about to ignore <strong id=\"batchIgnoreCount\">0</strong> contacts:",
"contacts.pending.ignore_note": "Ignored contacts will not trigger new pending requests. You can unignore them later from the Existing Contacts page.",
"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.pending.title": "Pending Contacts",
"contacts.protected_cannot_block": "Cannot block protected contact",
"contacts.protected_cannot_delete": "Cannot delete protected contact",
"contacts.protected_cannot_ignore": "Cannot ignore protected contact",
+21
View File
@@ -139,6 +139,7 @@
"contacts.add.adding": "Dodawanie kontaktu...",
"contacts.add.failed": "Nie udało się dodać kontaktu.",
"contacts.add.key": "Klucz:",
"contacts.add.key_count": "{n} / 64 znaki",
"contacts.add.name": "Nazwa:",
"contacts.add.name_ph": "Nazwa kontaktu",
"contacts.add.preview": "Podgląd:",
@@ -146,7 +147,11 @@
"contacts.add.pubkey_ph": "np. a1b2c3d4...",
"contacts.add.qr_label": "Albo wgraj obraz z kodem QR:",
"contacts.add.scanned": "Zeskanowano:",
"contacts.add.start_camera": "Włącz kamerę",
"contacts.add.stop_camera": "Wyłącz kamerę",
"contacts.add.success": "Kontakt dodany pomyślnie!",
"contacts.add.tab_manual": "Ręcznie",
"contacts.add.title": "Dodaj kontakt",
"contacts.add.type": "Typ kontaktu:",
"contacts.add.type_short": "Typ:",
"contacts.add.uri_hint": "Wklej URI meshcore:// z aplikacji mobilnej MeshCore",
@@ -171,18 +176,25 @@
"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.confirm_title": "Potwierdź czyszczenie kontaktów",
"contacts.cleanup.date_field": "Pole daty:",
"contacts.cleanup.days": "Dni nieaktywności (0 = pomiń):",
"contacts.cleanup.days_hint": "Zaznaczone zostaną kontakty nieaktywne dłużej niż tyle dni",
"contacts.cleanup.delete_btn": "Usuń kontakty",
"contacts.cleanup.delete_confirm": "Następujące kontakty (<span id=\"cleanupContactCount\">0</span>) zostaną trwale usunięte:",
"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.preview": "Podgląd czyszczenia",
"contacts.cleanup.run_at": "Uruchom o:",
"contacts.cleanup.status_label": "Status:",
"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.cleanup.undo_warning": "Tej operacji nie można cofnąć!",
"contacts.confirm.block": "Zablokować ten kontakt? Jego wiadomości będą ukryte w czacie.",
"contacts.copy_title": "Kliknij, aby skopiować",
"contacts.delete.confirm": "Usuń kontakt",
@@ -194,6 +206,7 @@
"contacts.existing.none_hint": "Kontakty pojawią się tutaj po dodaniu",
"contacts.existing.search_ph": "Szukaj po nazwie lub kluczu publicznym...",
"contacts.existing.title": "Istniejące kontakty",
"contacts.filter.advanced": "Filtry zaawansowane",
"contacts.filter.all_sources": "Wszystkie źródła",
"contacts.filter.all_types": "Wszystkie typy",
"contacts.filter.blocked": "Zablokowane",
@@ -209,14 +222,22 @@
"contacts.inactive": "Nieaktywny",
"contacts.last_advert": "Ostatni advert: {time}",
"contacts.last_advert_unknown": "Ostatni advert: nieznany",
"contacts.manage.title": "Ustawienia zarządzania kontaktami",
"contacts.map.show": "Pokaż:",
"contacts.map.title": "Mapa",
"contacts.never_seen": "Nigdy niewidziany",
"contacts.pending.approve_confirm": "Zamierzasz zatwierdzić <strong id=\"batchApprovalCount\">0</strong> kontaktów:",
"contacts.pending.approve_note": "Te kontakty zostaną dodane do Twojego urządzenia i będą mogły odbierać i wysyłać wiadomości.",
"contacts.pending.confirm_approve_title": "Potwierdź zbiorcze zatwierdzenie",
"contacts.pending.confirm_ignore_title": "Potwierdź zbiorcze zignorowanie",
"contacts.pending.filter_hint": "Filtruj kontakty po typie lub nazwie, a następnie zatwierdź lub zignoruj je zbiorczo.",
"contacts.pending.ignore_confirm": "Zamierzasz zignorować <strong id=\"batchIgnoreCount\">0</strong> kontaktów:",
"contacts.pending.ignore_note": "Ignorowane kontakty nie będą generować nowych oczekujących zgłoszeń. Możesz cofnąć ignorowanie później na stronie Istniejące kontakty.",
"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.pending.title": "Oczekujące kontakty",
"contacts.protected_cannot_block": "Nie można zablokować chronionego kontaktu",
"contacts.protected_cannot_delete": "Nie można usunąć chronionego kontaktu",
"contacts.protected_cannot_ignore": "Nie można zignorować chronionego kontaktu",