feat(i18n): translate the Console panel chrome (stage 2)

14 keys: title, connection status, the help hint, history and clear tooltips,
the command placeholder, the scroll-to-latest button, and the two error wrappers.

Everything the Console actually prints stays English, which is most of what is on
screen: the `help` screen, command names, device replies, `(no output)`'s
surroundings. data.output and data.error are composed by the backend and the
firmware, so only the wrapper around them is translated — "Error: {error}"
becomes "Błąd: {error}" with the English message intact. The 'help' inside the
hint text is the literal command name and is marked as such in the template.

"polecenie" over "komenda" for command, matching how Polish software normally
renders CLI terminology.

Also adds F:\tmp\pwverify\i18n-diff.js — a reusable check that renders a page in
both languages and reports every visible string that came out identical. Each hit
is either a missed extraction or something meant to stay English, so it turns
"switch to Polish and hunt for English words" into a list to review rather than a
squint. It pays for itself from stage 3 on, where the slices get large.

Its output for this slice: /console has exactly one identical string, the user's
own device name; /logs has eight, all Python logger names in the module filter.
Both correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-07-31 18:02:35 +02:00
parent 05f502ee53
commit fd32cb414f
4 changed files with 50 additions and 18 deletions
+10 -10
View File
@@ -60,7 +60,7 @@ function connectWebSocket() {
updateStatus('disconnected');
enableInput(false);
// Transient session event — show inline but don't persist to transcript
addMessage('Disconnected', 'error', false);
addMessage(t('console.status.disconnected'), 'error', false);
// Clear pending command indicator
if (pendingCommandDiv) {
@@ -91,12 +91,12 @@ function connectWebSocket() {
pendingCommandDiv = null;
}
// Display response
// Display response. data.output and data.error are CLI output composed by
// the device/backend — English by design, only the wrapper is translated.
if (data.success) {
const output = data.output || '(no output)';
addMessage(output, 'response');
addMessage(data.output || t('console.no_output'), 'response');
} else {
addMessage(`Error: ${data.error}`, 'error');
addMessage(t('console.error_prefix', { error: data.error }), 'error');
}
scrollToBottom();
});
@@ -104,7 +104,7 @@ function connectWebSocket() {
} catch (error) {
console.error('Failed to create WebSocket connection:', error);
updateStatus('disconnected');
addMessage('Failed to connect: ' + error.message, 'error', false);
addMessage(t('console.connect_failed', { error: error.message }), 'error', false);
}
}
@@ -245,15 +245,15 @@ function updateStatus(status) {
switch (status) {
case 'connected':
text.textContent = 'Connected';
text.textContent = t('console.status.connected');
text.className = 'text-success';
break;
case 'disconnected':
text.textContent = 'Disconnected';
text.textContent = t('console.status.disconnected');
text.className = 'text-danger';
break;
case 'connecting':
text.textContent = 'Connecting...';
text.textContent = t('console.status.connecting');
text.className = 'text-warning';
break;
}
@@ -391,7 +391,7 @@ function populateHistoryDropdown() {
historyMenu.innerHTML = '';
if (serverHistory.length === 0) {
historyMenu.innerHTML = '<div class="history-empty">No commands in history</div>';
historyMenu.innerHTML = `<div class="history-empty">${tHtml('console.history_empty')}</div>`;
return;
}
+10 -8
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>Console - mc-webui</title>
<title>{{ t('console.title') }} - mc-webui</title>
{% include "_head_i18n.html" %}
@@ -309,16 +309,18 @@
<small class="device-name">{{ device_name }}</small>
<div class="d-flex align-items-center gap-2">
<span class="status-dot" id="statusDot"></span>
<small class="text-muted" id="statusText">Connecting...</small>
<small class="text-muted" id="statusText">{{ t('console.status.connecting') }}</small>
</div>
</div>
<!-- Messages Area -->
<div class="messages-wrap">
<div class="console-messages" id="consoleMessages">
<div class="console-message system">Type 'help' for available commands.</div>
{# 'help' is the literal command name — never translated. #}
<div class="console-message system">{{ t('console.help_hint') }}</div>
</div>
<button type="button" class="scroll-bottom-btn" id="scrollBottomBtn" title="Scroll to latest" aria-label="Scroll to latest">
<button type="button" class="scroll-bottom-btn" id="scrollBottomBtn"
title="{{ t('console.scroll_bottom_title') }}" aria-label="{{ t('console.scroll_bottom_aria') }}">
<i class="bi bi-arrow-down"></i>
</button>
</div>
@@ -328,21 +330,21 @@
<form id="consoleForm" class="d-flex gap-2">
<!-- History dropdown -->
<div class="history-dropdown">
<button type="button" class="btn history-btn" id="historyBtn" title="Command history" disabled>
<button type="button" class="btn history-btn" id="historyBtn" title="{{ t('console.history_title') }}" disabled>
<i class="bi bi-clock-history"></i>
</button>
<div class="history-menu" id="historyMenu">
<div class="history-empty">No commands in history</div>
<div class="history-empty">{{ t('console.history_empty') }}</div>
</div>
</div>
<!-- Clear output transcript -->
<button type="button" class="btn history-btn" id="clearOutputBtn" title="Clear output history">
<button type="button" class="btn history-btn" id="clearOutputBtn" title="{{ t('console.clear_title') }}">
<i class="bi bi-trash"></i>
</button>
<input type="text"
id="commandInput"
class="form-control console-input"
placeholder="Enter command..."
placeholder="{{ t('console.input_ph') }}"
autocomplete="off"
autocapitalize="off"
spellcheck="false"
+15
View File
@@ -8,6 +8,21 @@
"common.minutes_ago": "{count} min ago",
"common.yesterday": "Yesterday",
"console.clear_title": "Clear output history",
"console.connect_failed": "Failed to connect: {error}",
"console.error_prefix": "Error: {error}",
"console.help_hint": "Type 'help' for available commands.",
"console.history_empty": "No commands in history",
"console.history_title": "Command history",
"console.input_ph": "Enter command...",
"console.no_output": "(no output)",
"console.scroll_bottom_aria": "Scroll to latest",
"console.scroll_bottom_title": "Scroll to latest",
"console.status.connected": "Connected",
"console.status.connecting": "Connecting...",
"console.status.disconnected": "Disconnected",
"console.title": "Console",
"logs.clear_title": "Clear display",
"logs.entries": {
"one": "{count} entry",
+15
View File
@@ -10,6 +10,21 @@
"common.minutes_ago": "{count} min temu",
"common.yesterday": "Wczoraj",
"console.clear_title": "Wyczyść zapis konsoli",
"console.connect_failed": "Nie udało się połączyć: {error}",
"console.error_prefix": "Błąd: {error}",
"console.help_hint": "Wpisz 'help', aby zobaczyć dostępne polecenia.",
"console.history_empty": "Brak poleceń w historii",
"console.history_title": "Historia poleceń",
"console.input_ph": "Wpisz polecenie...",
"console.no_output": "(brak wyniku)",
"console.scroll_bottom_aria": "Przewiń do najnowszych",
"console.scroll_bottom_title": "Przewiń do najnowszych",
"console.status.connected": "Połączono",
"console.status.connecting": "Łączenie...",
"console.status.disconnected": "Rozłączono",
"console.title": "Konsola",
"logs.clear_title": "Wyczyść widok",
"logs.entries": {
"one": "{count} wpis",