[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:
+29
-20
@@ -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 {
|
||||
'&': '&', '<': '<', '>': '>',
|
||||
'"': '"', "'": '''
|
||||
}[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) {
|
||||
|
||||
Reference in New Issue
Block a user