i18n: translate 6 HTML Jinja2 templates with _() calls

Replace all hardcoded English visible text with {{ _("key") }} calls in:
- queue_dashboard.html: headings, stat cards, table headers, empty states
- api_tokens.html: form labels, table headers, status badges, aria-labels
- similarity_dashboard.html: headings, stat labels, controls, empty states
- backup.html: buttons, config labels, table, retention section, confirm dialog
- shared_links.html: form labels/options, table headers, status badges
- duplicates.html: tabs, role tags, near-dup form, explanation bullets

Follows i18n rules: placeholder/aria-label/title attrs translated,
JS strings in <script> left as-is, Alpine.js bindings use Jinja2
server-side evaluation pattern for dynamic translated strings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-11 12:22:46 +00:00
parent fdff970e59
commit 64577f2d92
8 changed files with 260 additions and 280 deletions
+19 -21
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Document Similarity - DocuElevate{% endblock %}
{% block title %}{{ _("similarity.page_title") }}{% endblock %}
{% block head_extra %}
<style>
@@ -97,45 +97,43 @@
<main id="main-content" class="sim-container px-4 py-8">
<h1 class="text-2xl font-bold mb-2">
<i class="fas fa-project-diagram text-blue-500" aria-hidden="true"></i>
Document Similarity
{{ _("similarity.heading") }}
</h1>
<p class="text-gray-500 mb-6 text-sm">
Pairs of documents with high semantic similarity, ranked by score.
Embeddings are computed during document ingestion; a background task
also backfills any files that were processed before this feature was enabled.
{{ _("similarity.subtitle") }}
</p>
<!-- Embedding coverage stats -->
<div class="stats-bar">
<div class="stat-card">
<div class="stat-value">{{ total_files }}</div>
<div class="stat-label">Total Files</div>
<div class="stat-label">{{ _("similarity.stat_total_files") }}</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: #2563eb;">{{ files_with_embedding }}</div>
<div class="stat-label">With Embedding</div>
<div class="stat-label">{{ _("similarity.stat_with_embedding") }}</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: {% if files_missing_embedding > 0 %}#d97706{% else %}#059669{% endif %};">
{{ files_missing_embedding }}
</div>
<div class="stat-label">Missing Embedding</div>
<div class="stat-label">{{ _("similarity.stat_missing_embedding") }}</div>
</div>
<div class="stat-card">
<div class="stat-value text-sm" style="word-break: break-all;">{{ embedding_model }}</div>
<div class="stat-label">Embedding Model</div>
<div class="stat-label">{{ _("similarity.stat_embedding_model") }}</div>
</div>
</div>
{% if files_missing_embedding > 0 %}
<div style="background: #fffff0; border: 1px solid #fefcbf; border-radius: 0.5rem; padding: 0.75rem 1rem; margin-bottom: 1.5rem; font-size: 0.85rem; color: #975a16;">
<i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
<strong>{{ files_missing_embedding }}</strong> file(s) have OCR text but no embedding yet.
The background task will compute them automatically every 5 minutes, or you can
<strong>{{ files_missing_embedding }}</strong> {{ _("similarity.files_missing_text") }}
{{ _("similarity.backfill_auto") }}
<button onclick="triggerBackfill()" id="backfill-btn"
style="background: #d97706; color: white; border: none; padding: 0.2rem 0.6rem; border-radius: 0.25rem; cursor: pointer; font-size: 0.8rem; min-height: 44px; min-width: 44px;"
aria-label="Trigger embedding computation for all files missing embeddings">
trigger it now
aria-label="{{ _('similarity.trigger_aria') }}">
{{ _("similarity.trigger_now") }}
</button>.
</div>
{% endif %}
@@ -144,12 +142,12 @@
<div class="controls">
<form id="pairsForm" onsubmit="loadPairs(event)" class="controls-form">
<div class="flex flex-col gap-1">
<label for="pairThreshold">Min. similarity</label>
<label for="pairThreshold">{{ _("similarity.min_similarity_label") }}</label>
<input type="number" id="pairThreshold" min="0" max="1" step="0.05"
value="{{ default_threshold }}" style="min-height:44px;">
</div>
<div class="flex flex-col gap-1">
<label for="pairLimit">Per page</label>
<label for="pairLimit">{{ _("similarity.per_page_label") }}</label>
<select id="pairLimit" style="min-height:44px;">
<option value="25">25</option>
<option value="50" selected>50</option>
@@ -159,7 +157,7 @@
<button type="submit"
style="min-height:44px; background-color: #2563eb; color: white; font-weight: 700;
padding: 0 1.25rem; border: none; border-radius: 0.5rem; cursor: pointer;">
<i class="fas fa-search" aria-hidden="true"></i> Find Pairs
<i class="fas fa-search" aria-hidden="true"></i> {{ _("similarity.find_pairs_btn") }}
</button>
</form>
</div>
@@ -167,23 +165,23 @@
<!-- Results area -->
<div id="pairs-loading" style="text-align: center; padding: 2rem; color: #718096;">
<i class="fas fa-spinner fa-spin" aria-hidden="true" style="font-size: 1.5rem; margin-bottom: 0.5rem;"></i>
<p>Scanning for similar document pairs…</p>
<p>{{ _("similarity.scanning") }}</p>
</div>
<div id="pairs-content" style="display: none;" aria-live="polite"></div>
<div id="pairs-empty" style="display: none;" class="empty-state">
<i class="fas fa-check-circle text-green-400" aria-hidden="true"></i>
<p class="font-semibold text-lg text-gray-700">No similar pairs found</p>
<p class="font-semibold text-lg text-gray-700">{{ _("similarity.no_pairs_heading") }}</p>
<p class="text-sm mt-1" id="pairs-empty-detail">
No document pairs exceed the similarity threshold.
{{ _("similarity.no_pairs_detail") }}
</p>
</div>
<div id="pairs-error" style="display: none;" class="empty-state">
<i class="fas fa-exclamation-triangle text-red-400" aria-hidden="true"></i>
<p class="font-semibold text-lg text-gray-700" id="pairs-error-msg">Failed to load pairs.</p>
<p class="font-semibold text-lg text-gray-700" id="pairs-error-msg">{{ _("similarity.load_error") }}</p>
</div>
<!-- Pagination -->
<nav id="pairs-pagination" class="pagination" style="display: none;" aria-label="Similarity pairs pagination"></nav>
<nav id="pairs-pagination" class="pagination" style="display: none;" aria-label="{{ _('similarity.pagination_aria') }}"></nav>
</main>
{% endblock %}