Translate the user-facing dashboard into 7 languages
Adds app/static/i18n.js: a flat key -> string table for en/it/es/fr/de/ru/zh, loaded before app.js so t() is available everywhere. No build step and no fetch, consistent with the rest of these static pages. Language comes from localStorage, then navigator.language, then en. Static markup is translated by attribute (data-i18n and its -html/-placeholder/ -title/-aria-label/-alt variants); anything rendered from server data goes through t() in app.js and is re-rendered by onLanguageChange(). An element belongs to one mechanism or the other, never both, or the two overwrite each other — which is why #bet-btn has no data-i18n: its label carries the admin-configurable bet amount, so renderBetButton() owns it and reads the amount from /rounds/current instead of hardcoding "10 PLM" in seven files. The switcher sits in the chain-bar rather than the navbar because the navbar is hidden until login, which would leave the landing page and the login form untranslatable for exactly the users who need to switch. It uses language names rather than flag emoji: flags don't render on every platform and don't map one-to-one onto languages. withLoading now snapshots innerHTML instead of textContent — several of these buttons wrap an <svg> plus a <span data-i18n>, both of which a textContent round-trip flattened away, permanently losing the icon and the translation hook. It re-applies translations to the restored subtree in case the language changed while the request was in flight. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -342,6 +342,21 @@ body::before {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.chain-status-pill { display: inline-flex; align-items: center; gap: 7px; font-weight: 600; color: var(--color-foreground); }
|
||||
.chain-bar-right { display: inline-flex; align-items: center; gap: 10px; flex-shrink: 0; }
|
||||
|
||||
/* Language switcher: a plain <select> styled down to look like the muted text
|
||||
around it, so it reads as part of the status strip rather than as a form
|
||||
control. Text labels, not flag emoji — flags don't render on every platform
|
||||
and don't map one-to-one onto languages anyway. */
|
||||
select.lang-switcher {
|
||||
font: inherit; font-size: 0.78rem; color: var(--color-muted-foreground);
|
||||
background: none; border: none; box-shadow: none; padding: 2px 4px;
|
||||
border-radius: 6px; cursor: pointer;
|
||||
-webkit-appearance: none; appearance: none;
|
||||
}
|
||||
select.lang-switcher:hover { color: var(--color-foreground); background: var(--color-surface-inset); }
|
||||
select.lang-switcher:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
|
||||
select.lang-switcher option { color: var(--color-foreground); background: var(--color-surface); }
|
||||
.status-dot {
|
||||
width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
|
||||
background: var(--color-muted-foreground);
|
||||
|
||||
Reference in New Issue
Block a user