[glm-grade=B+] feat(i18n): complete card/filter/action translation keys for 31 languages

Full language display names + RTL set (ar/fa/ur) on /i18n/languages;
card.internet/auth/tailscale/dashca, status pills, filter bar and
batch-operation strings added to every language dictionary. Frontend:
English now loads the server dictionary too (keys are semantic ids,
not fallback copy), failed loads keep existing DOM text instead of
exposing raw keys, isLoaded() gate for pre-load renders. Rebuilt
status/dist. Tests: i18n-cards 9/9, full suite 1837/1837.
This commit is contained in:
Krystie
2026-08-15 00:01:17 -07:00
parent bd40fb1c17
commit ef685e515e
11 changed files with 495 additions and 167 deletions
+29 -20
View File
@@ -8,7 +8,7 @@
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'sha256-dtGmAPWjcgykNC2GM60HzlzMwiAqHarVnnPf8NO0XrA='; style-src 'self' 'unsafe-inline'; img-src 'self' https://cdn.jsdelivr.net data:; connect-src 'self' https://api.open-meteo.com https://geocoding-api.open-meteo.com; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'sha256-LqwtGSCiBtjq9Bnb5wA3kQFrFRgZWtDWcswiK656gEk='; style-src 'self' 'unsafe-inline'; img-src 'self' https://cdn.jsdelivr.net data:; connect-src 'self' https://api.open-meteo.com https://geocoding-api.open-meteo.com; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'">
<link rel="icon" href="/assets/dashcaddy-favicon.ico" sizes="any">
<link rel="icon" type="image/png" sizes="192x192" href="/assets/icon-192.png">
@@ -245,9 +245,9 @@
<path d="M10.5 7.5c3-1.5 6-1.5 9 0M10.5 16.5c3 1.5 6 1.5 9 0" stroke="white" stroke-width="1"/>
</svg>
</div>
<span class="name">Internet</span>
<span class="name" data-i18n="card.internet">Internet</span>
<span class="spacer"></span>
<span id="internet-pill" class="badge off">OFF</span>
<span id="internet-pill" class="badge off" data-i18n-live-status="binary">OFF</span>
</div>
<div class="response-row">
<span id="internet-time" class="response-time">--</span>
@@ -267,15 +267,15 @@
<line x1="12" y1="16.5" x2="12" y2="18" stroke="#0b0f1a" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
<span class="name">Auth</span>
<span class="name" data-i18n="card.auth">Auth</span>
<span class="spacer"></span>
<span id="auth-pill" class="badge off">NO</span>
<span id="auth-pill" class="badge off" data-i18n-live-status="yesno">NO</span>
</div>
<div class="response-row">
<span id="auth-status-text" class="response-time" style="font-size: 0.7rem;">Not configured</span>
<span id="auth-status-text" class="response-time" style="font-size: 0.7rem;" data-i18n="card.auth.not_configured">Not configured</span>
</div>
<div class="btn-row">
<button id="auth-settings-btn">Settings</button>
<button id="auth-settings-btn" data-i18n="action.settings">Settings</button>
</div>
</div>
@@ -290,7 +290,7 @@
<path d="M12 13v4M9 19h6" stroke="#7D8FE3" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
<span class="name">Tailscale</span>
<span class="name" data-i18n="card.tailscale">Tailscale</span>
<span class="spacer"></span>
<span id="tailscale-pill" class="badge off"></span>
</div>
@@ -308,9 +308,9 @@
<div class="logo-wrap">
<span style="font-size: 28px; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;">🔐</span>
</div>
<span class="name">DashCA</span>
<span class="name" data-i18n="card.dashca">DashCA</span>
<span class="spacer"></span>
<span id="badge-ca" class="badge off">OFF</span>
<span id="badge-ca" class="badge off" data-i18n-live-status="binary">OFF</span>
</div>
<div class="response-row">
<span id="time-ca" class="response-time">--</span>
@@ -323,7 +323,7 @@
<button class="creds-btn" id="creds-btn-ca" title="Auto-login credentials">🔑</button>
<button class="options-btn" id="options-btn-ca" title="Edit service settings">⚙️</button>
<button class="delete-btn" id="delete-btn-ca" title="Delete this service">🗑️</button>
<button id="ca-open">Open</button>
<button id="ca-open" data-i18n="action.open">Open</button>
</div>
</div>
</div>
@@ -345,16 +345,16 @@
<!-- Service Filter Bar -->
<div id="service-filter-bar" style="display: flex; gap: 12px; align-items: center; margin-bottom: 16px; padding: 12px 16px; background: var(--card-base); border: 1px solid var(--border); border-radius: var(--radius); flex-wrap: wrap;">
<input type="text" id="service-filter-search" placeholder="🔍 Filter services..." style="flex: 1; min-width: 180px; padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 0.9rem;" />
<input type="text" id="service-filter-search" placeholder="🔍 Filter services..." data-i18n-placeholder="filter.services_placeholder" data-i18n-prefix="🔍 " style="flex: 1; min-width: 180px; padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 0.9rem;" />
<select id="service-filter-status" style="padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 0.9rem;">
<option value="all">All Status</option>
<option value="on">🟢 Online</option>
<option value="off">🔴 Offline</option>
<option value="all" data-i18n="filter.all_status">All Status</option>
<option value="on" data-i18n="filter.online" data-i18n-prefix="🟢 ">🟢 Online</option>
<option value="off" data-i18n="filter.offline" data-i18n-prefix="🔴 ">🔴 Offline</option>
</select>
<select id="service-filter-category" style="padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 0.9rem;">
<option value="all">All Categories</option>
<option value="all" data-i18n="filter.all_categories">All Categories</option>
</select>
<button id="batch-operations-btn" class="btn-sm" style="padding: 8px 12px;">☰ Batch Operations</button>
<button id="batch-operations-btn" class="btn-sm" style="padding: 8px 12px;" data-i18n="filter.batch_operations" data-i18n-prefix="☰ ">☰ Batch Operations</button>
<span id="service-filter-count" style="color: var(--muted); font-size: 0.85rem; white-space: nowrap;"></span>
</div>
@@ -760,14 +760,23 @@
var versionInfoClose = document.getElementById('version-info-close');
var latestUpdateCheck = null;
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, function(character) {
return {
'&': '&amp;', '<': '&lt;', '>': '&gt;',
'"': '&quot;', "'": '&#39;'
}[character];
});
}
function formatValue(value) {
if (value == null || value === '') return '—';
if (typeof value === 'object') return JSON.stringify(value);
return String(value);
if (typeof value === 'object') return escapeHtml(JSON.stringify(value));
return escapeHtml(value);
}
function renderInfoRow(label, value) {
return '<div class="version-info-row"><span class="version-info-label">' + label + '</span><span class="version-info-value">' + formatValue(value) + '</span></div>';
return '<div class="version-info-row"><span class="version-info-label">' + escapeHtml(label) + '</span><span class="version-info-value">' + formatValue(value) + '</span></div>';
}
function renderHistory(history) {