Files
MarekWoandClaude Opus 5 88ca23bccb 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>
2026-08-01 10:29:30 +02:00

129 lines
6.5 KiB
HTML

{% extends "contacts_base.html" %}
{% block title %}{{ t('contacts.add.title') }} - mc-webui{% endblock %}
{% block extra_head %}
<!-- html5-qrcode for QR scanning -->
<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>
{% endblock %}
{% block page_content %}
<div id="addPageContent" class="p-3">
<!-- Page Header -->
<div class="mb-3">
<h4 class="mb-2">
<i class="bi bi-person-plus"></i> {{ t('contacts.add.title') }}
</h4>
</div>
<!-- Action Buttons -->
<div class="d-flex gap-2 mb-3">
<button class="btn btn-outline-secondary btn-sm" onclick="navigateTo('/contacts/manage');">
<i class="bi bi-arrow-left"></i> {{ t('common.back') }}
</button>
</div>
<!-- Input Mode Tabs -->
<ul class="nav nav-tabs mb-3" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="tab-uri" data-bs-toggle="tab" data-bs-target="#pane-uri" type="button" role="tab">
<i class="bi bi-link-45deg"></i> URI
</button>
</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> {{ 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> {{ t('contacts.add.tab_manual') }}
</button>
</li>
</ul>
<div class="tab-content">
<!-- 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">{{ t('contacts.add.uri_label') }}</label>
<textarea class="form-control font-monospace" id="uriInput" rows="3"
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>{{ 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> {{ t('contacts.add.title') }}
</button>
</div>
<!-- QR Code Tab -->
<div class="tab-pane fade" id="pane-qr" role="tabpanel">
<!-- Camera Scanner -->
<div id="qrScannerContainer" class="mb-3">
<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> {{ 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> {{ t('contacts.add.stop_camera') }}
</button>
</div>
</div>
<!-- File Upload Fallback -->
<div class="mb-3">
<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>{{ 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">
<i class="bi bi-plus-circle"></i> {{ t('contacts.add.title') }}
</button>
</div>
<!-- Manual Entry Tab -->
<div class="tab-pane fade" id="pane-manual" role="tabpanel">
<div class="mb-3">
<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">{{ 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">{{ t('contacts.add.key_count', n=0) }}</small>
</div>
<div class="mb-3">
<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>
<option value="3">ROOM (Room Server)</option>
<option value="4">SENS (Sensor)</option>
</select>
</div>
<button class="btn btn-success" id="addManualBtn" disabled>
<i class="bi bi-plus-circle"></i> {{ t('contacts.add.title') }}
</button>
</div>
</div>
<!-- Status Messages -->
<div id="addStatus" class="mt-3 d-none"></div>
</div>
{% endblock %}