i18n: style the settings select + add a topbar lang toggle

Two issues addressed:

1. The /settings language <select> was unstyled — .settings-select and
   .settings-status classes didn't exist, so the dropdown rendered
   with full native browser chrome and clashed visually with the rest
   of the panel. Added a terminal-aesthetic select: transparent
   background, 1px var(--border), custom chevron via crossed
   linear-gradients, accent border on focus/hover. Disabled options
   (ES/FR/DE 'coming soon') render in --dim.

2. Added a compact EN | IT pill in the topbar next to the theme
   toggle, mirroring the .tone-toggle visual rhythm. Shown only when
   a user is signed in (admins skipped). Optimistic UI: clicking
   flips the pill immediately, PATCHes /api/settings/language, and
   reverts on failure. On /log specifically the page reloads so the
   user sees the localized version of the strategic log right away.

The /settings dropdown still surfaces all five languages (with ES/FR/DE
disabled) for visibility; the topbar pill keeps to the two active
languages to stay compact.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Giorgio Gilestro 2026-05-27 18:14:23 +02:00
parent 50ac6b9366
commit fb71854238
2 changed files with 107 additions and 0 deletions

View file

@ -134,6 +134,34 @@
if (el && window.htmx) window.htmx.trigger(el, 'tone-changed');
});
};
window.cassandraSetLang = async function (newLang) {
var pill = document.getElementById('lang-toggle');
if (!pill) return;
var prev = pill.dataset.lang;
if (prev === newLang) return;
// Optimistic update — flip the pill immediately so the click feels
// responsive. Revert on PATCH failure.
pill.dataset.lang = newLang;
try {
var r = await fetch('/api/settings/language', {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
credentials: 'same-origin',
body: JSON.stringify({lang: newLang}),
});
if (!r.ok) throw new Error('HTTP ' + r.status);
// Reload localized panels so the user immediately sees content
// in the new language (strategic log, dashboard header, etc.).
if (window.location.pathname === '/log' ||
window.location.pathname.startsWith('/log/')) {
window.location.reload();
}
} catch (e) {
pill.dataset.lang = prev;
console.warn('language switch failed:', e);
}
};
</script>
<script>
// Render any <time datetime="..."> in the browser's local timezone.
@ -180,6 +208,16 @@
<span class="theme-toggle__label"></span>
</button>
{% set cu = request.state.current_user if request.state and request.state.current_user is defined else None %}
{% if cu and cu.user %}
<div id="lang-toggle" class="lang-toggle" data-lang="{{ cu.user.lang or 'en' }}"
role="group" aria-label="AI output language"
title="Language the AI uses for the log, digest and portfolio commentary">
<button type="button" data-value="en"
onclick="cassandraSetLang('en')">EN</button>
<button type="button" data-value="it"
onclick="cassandraSetLang('it')">IT</button>
</div>
{% endif %}
{% if cu and (cu.user or cu.is_admin) %}
<div class="user-menu">
<button type="button" id="user-menu-toggle" class="user-chip"