fix: apply locale to DOM immediately on save — no reload needed
Add applyLocaleToDOM() which walks [data-i18n] elements and re-stamps their textContent from t(). Called after setLocale() in saveSettings() so the settings panel labels, checkboxes, and save button update live. Also called on boot after /api/settings resolves so Chinese persists without flicker on reload. - static/i18n.js: add applyLocaleToDOM() function - static/index.html: add data-i18n attributes to all settings panel static text nodes (labels, checkbox spans, save button) - static/panels.js: call applyLocaleToDOM() + syncTopbar() after save - static/boot.js: call applyLocaleToDOM() alongside setLocale() on boot
This commit is contained in:
@@ -285,5 +285,18 @@ function loadLocale() {
|
|||||||
setLocale(saved);
|
setLocale(saved);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-stamp all [data-i18n] elements in the DOM with the current locale.
|
||||||
|
* Safe to call at any time — missing keys fall back to English.
|
||||||
|
* Call after setLocale() to make static HTML text update without a reload.
|
||||||
|
*/
|
||||||
|
function applyLocaleToDOM() {
|
||||||
|
document.querySelectorAll('[data-i18n]').forEach(el => {
|
||||||
|
const key = el.getAttribute('data-i18n');
|
||||||
|
const val = t(key);
|
||||||
|
if (val && val !== key) el.textContent = val;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Apply saved locale immediately so there's no flash of English on reload.
|
// Apply saved locale immediately so there's no flash of English on reload.
|
||||||
loadLocale();
|
loadLocale();
|
||||||
|
|||||||
@@ -318,23 +318,23 @@
|
|||||||
<div class="settings-overlay" id="settingsOverlay" style="display:none">
|
<div class="settings-overlay" id="settingsOverlay" style="display:none">
|
||||||
<div class="settings-panel">
|
<div class="settings-panel">
|
||||||
<div class="settings-header">
|
<div class="settings-header">
|
||||||
<h3 style="margin:0;font-size:16px">Settings</h3>
|
<h3 style="margin:0;font-size:16px" data-i18n="settings_title">Settings</h3>
|
||||||
<button class="panel-icon-btn" onclick="_closeSettingsPanel()" title="Close">✕</button>
|
<button class="panel-icon-btn" onclick="_closeSettingsPanel()" title="Close">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-body">
|
<div class="settings-body">
|
||||||
<div class="settings-field">
|
<div class="settings-field">
|
||||||
<label for="settingsModel">Default Model</label>
|
<label for="settingsModel" data-i18n="settings_label_model">Default Model</label>
|
||||||
<select id="settingsModel" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px"></select>
|
<select id="settingsModel" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px"></select>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-field">
|
<div class="settings-field">
|
||||||
<label for="settingsSendKey">Send Key</label>
|
<label for="settingsSendKey" data-i18n="settings_label_send_key">Send Key</label>
|
||||||
<select id="settingsSendKey" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px">
|
<select id="settingsSendKey" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px">
|
||||||
<option value="enter">Enter (Shift+Enter for newline)</option>
|
<option value="enter">Enter (Shift+Enter for newline)</option>
|
||||||
<option value="ctrl+enter">Ctrl+Enter (Enter for newline)</option>
|
<option value="ctrl+enter">Ctrl+Enter (Enter for newline)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-field">
|
<div class="settings-field">
|
||||||
<label for="settingsTheme">Theme</label>
|
<label for="settingsTheme" data-i18n="settings_label_theme">Theme</label>
|
||||||
<select id="settingsTheme" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px" onchange="document.documentElement.dataset.theme=this.value;localStorage.setItem('hermes-theme',this.value)">
|
<select id="settingsTheme" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px" onchange="document.documentElement.dataset.theme=this.value;localStorage.setItem('hermes-theme',this.value)">
|
||||||
<option value="dark">Dark (default)</option>
|
<option value="dark">Dark (default)</option>
|
||||||
<option value="light">Light</option>
|
<option value="light">Light</option>
|
||||||
@@ -346,7 +346,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-field">
|
<div class="settings-field">
|
||||||
<label for="settingsLanguage">Language</label>
|
<label for="settingsLanguage" data-i18n="settings_label_language">Language</label>
|
||||||
<select id="settingsLanguage" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px"></select>
|
<select id="settingsLanguage" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px"></select>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-field">
|
<div class="settings-field">
|
||||||
@@ -366,42 +366,42 @@
|
|||||||
<div class="settings-field">
|
<div class="settings-field">
|
||||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
||||||
<input type="checkbox" id="settingsShowTokenUsage" style="width:15px;height:15px;accent-color:var(--accent)">
|
<input type="checkbox" id="settingsShowTokenUsage" style="width:15px;height:15px;accent-color:var(--accent)">
|
||||||
Show token usage after responses
|
<span data-i18n="settings_label_token_usage">Show token usage after responses</span>
|
||||||
</label>
|
</label>
|
||||||
<div style="font-size:11px;color:var(--muted);margin-top:4px">Displays input/output token count below each assistant reply. Also toggled with <code>/usage</code>.</div>
|
<div style="font-size:11px;color:var(--muted);margin-top:4px">Displays input/output token count below each assistant reply. Also toggled with <code>/usage</code>.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-field">
|
<div class="settings-field">
|
||||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
||||||
<input type="checkbox" id="settingsShowCliSessions" style="width:15px;height:15px;accent-color:var(--accent)">
|
<input type="checkbox" id="settingsShowCliSessions" style="width:15px;height:15px;accent-color:var(--accent)">
|
||||||
Show CLI sessions in sidebar
|
<span data-i18n="settings_label_cli_sessions">Show CLI sessions in sidebar</span>
|
||||||
</label>
|
</label>
|
||||||
<div style="font-size:11px;color:var(--muted);margin-top:4px">Merges sessions from the Hermes CLI (state.db) into the session list. Click a CLI session to import it and continue the conversation.</div>
|
<div style="font-size:11px;color:var(--muted);margin-top:4px">Merges sessions from the Hermes CLI (state.db) into the session list. Click a CLI session to import it and continue the conversation.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-field">
|
<div class="settings-field">
|
||||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
||||||
<input type="checkbox" id="settingsSyncInsights" style="width:15px;height:15px;accent-color:var(--accent)">
|
<input type="checkbox" id="settingsSyncInsights" style="width:15px;height:15px;accent-color:var(--accent)">
|
||||||
Sync usage to /insights
|
<span data-i18n="settings_label_sync_insights">Sync usage to /insights</span>
|
||||||
</label>
|
</label>
|
||||||
<div style="font-size:11px;color:var(--muted);margin-top:4px">Mirrors WebUI token usage to state.db so <code>hermes /insights</code> includes browser session data. Off by default.</div>
|
<div style="font-size:11px;color:var(--muted);margin-top:4px">Mirrors WebUI token usage to state.db so <code>hermes /insights</code> includes browser session data. Off by default.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-field">
|
<div class="settings-field">
|
||||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
||||||
<input type="checkbox" id="settingsCheckUpdates" style="width:15px;height:15px;accent-color:var(--accent)">
|
<input type="checkbox" id="settingsCheckUpdates" style="width:15px;height:15px;accent-color:var(--accent)">
|
||||||
Check for updates
|
<span data-i18n="settings_label_check_updates">Check for updates</span>
|
||||||
</label>
|
</label>
|
||||||
<div style="font-size:11px;color:var(--muted);margin-top:4px">Show a banner when newer versions of the WebUI or Agent are available. Runs a background git fetch periodically.</div>
|
<div style="font-size:11px;color:var(--muted);margin-top:4px">Show a banner when newer versions of the WebUI or Agent are available. Runs a background git fetch periodically.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-field">
|
<div class="settings-field">
|
||||||
<label for="settingsBotName">Assistant Name</label>
|
<label for="settingsBotName" data-i18n="settings_label_bot_name">Assistant Name</label>
|
||||||
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Display name for the assistant throughout the UI. Defaults to Hermes.</div>
|
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Display name for the assistant throughout the UI. Defaults to Hermes.</div>
|
||||||
<input type="text" id="settingsBotName" placeholder="Hermes" maxlength="64" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px;font-size:13px">
|
<input type="text" id="settingsBotName" placeholder="Hermes" maxlength="64" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px;font-size:13px">
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-field" style="border-top:1px solid var(--border);padding-top:12px;margin-top:8px">
|
<div class="settings-field" style="border-top:1px solid var(--border);padding-top:12px;margin-top:8px">
|
||||||
<label for="settingsPassword">Access Password</label>
|
<label for="settingsPassword" data-i18n="settings_label_password">Access Password</label>
|
||||||
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Enter a new password to set or change it. Leave blank to keep current setting.</div>
|
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Enter a new password to set or change it. Leave blank to keep current setting.</div>
|
||||||
<input type="password" id="settingsPassword" placeholder="Enter new password…" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px;font-size:13px">
|
<input type="password" id="settingsPassword" placeholder="Enter new password…" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px;font-size:13px">
|
||||||
</div>
|
</div>
|
||||||
<button class="sm-btn" onclick="saveSettings()" style="margin-top:12px;width:100%;padding:8px;font-weight:600">Save Settings</button>
|
<button class="sm-btn" onclick="saveSettings()" style="margin-top:12px;width:100%;padding:8px;font-weight:600" data-i18n="settings_save_btn">Save Settings</button>
|
||||||
<button class="sm-btn" id="btnDisableAuth" onclick="disableAuth()" style="margin-top:6px;width:100%;padding:8px;font-weight:600;color:#e8a030;border-color:rgba(232,160,48,.3);display:none">Disable Auth</button>
|
<button class="sm-btn" id="btnDisableAuth" onclick="disableAuth()" style="margin-top:6px;width:100%;padding:8px;font-weight:600;color:#e8a030;border-color:rgba(232,160,48,.3);display:none">Disable Auth</button>
|
||||||
<button class="sm-btn" id="btnSignOut" onclick="signOut()" style="margin-top:6px;width:100%;padding:8px;font-weight:600;color:var(--accent);border-color:rgba(233,69,96,.3);display:none">Sign Out</button>
|
<button class="sm-btn" id="btnSignOut" onclick="signOut()" style="margin-top:6px;width:100%;padding:8px;font-weight:600;color:var(--accent);border-color:rgba(233,69,96,.3);display:none">Sign Out</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1098,9 +1098,11 @@ async function saveSettings(andClose){
|
|||||||
window._botName=body.bot_name;
|
window._botName=body.bot_name;
|
||||||
if(typeof applyBotName==='function') applyBotName();
|
if(typeof applyBotName==='function') applyBotName();
|
||||||
if(typeof setLocale==='function') setLocale(language);
|
if(typeof setLocale==='function') setLocale(language);
|
||||||
|
if(typeof applyLocaleToDOM==='function') applyLocaleToDOM();
|
||||||
_settingsDirty=false; _settingsThemeOnOpen=theme;
|
_settingsDirty=false; _settingsThemeOnOpen=theme;
|
||||||
const bar=$('settingsUnsavedBar'); if(bar) bar.style.display='none';
|
const bar=$('settingsUnsavedBar'); if(bar) bar.style.display='none';
|
||||||
renderMessages();
|
renderMessages();
|
||||||
|
if(typeof syncTopbar==='function') syncTopbar();
|
||||||
if(typeof renderSessionList==='function') renderSessionList();
|
if(typeof renderSessionList==='function') renderSessionList();
|
||||||
showToast(t('settings_saved'));
|
showToast(t('settings_saved'));
|
||||||
$('settingsOverlay').style.display='none';
|
$('settingsOverlay').style.display='none';
|
||||||
|
|||||||
Reference in New Issue
Block a user