fix(i18n): persist language preference server-side, fix dropdown rendering and avatar 404
- Fix language dropdown in base.html by moving tojson data out of HTML attribute into a script tag (prevents quote conflicts breaking Alpine.js) - Fix avatar placeholder 404 by correcting filename reference from avatar-placeholder.svg to default-avatar.svg - Add session hydration from DB in _inject_global_context() so detect_language() uses the stored preference on every request - Sync session and cookie in PATCH /api/profile when language changes - Reload page after language change in profile to reflect new locale - Add tests for session/cookie sync and DB hydration Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -216,12 +216,18 @@
|
||||
aria-live="polite"></span>
|
||||
</a>
|
||||
|
||||
<!-- Language selector data (kept outside the HTML attribute to avoid quote conflicts with tojson) -->
|
||||
<script>
|
||||
window.__langSuggested = {{ suggested_languages | tojson }};
|
||||
window.__langAll = {{ supported_languages | tojson }};
|
||||
</script>
|
||||
|
||||
<!-- Language selector dropdown -->
|
||||
<div x-data="{
|
||||
langOpen: false,
|
||||
search: '',
|
||||
suggested: {{ suggested_languages | tojson }},
|
||||
all: {{ supported_languages | tojson }},
|
||||
suggested: window.__langSuggested,
|
||||
all: window.__langAll,
|
||||
get filtered() {
|
||||
if (!this.search.trim()) return this.suggested;
|
||||
const q = this.search.trim().toLowerCase();
|
||||
|
||||
@@ -327,7 +327,7 @@
|
||||
function profileSettings() {
|
||||
return {
|
||||
// ── State ──────────────────────────────────────────────────────────────
|
||||
avatarUrl: '/static/images/avatar-placeholder.svg',
|
||||
avatarUrl: '/static/images/default-avatar.svg',
|
||||
hasCustomAvatar: false,
|
||||
isLocalUser: false,
|
||||
saving: false,
|
||||
@@ -349,6 +349,8 @@ function profileSettings() {
|
||||
},
|
||||
|
||||
// ── Init ───────────────────────────────────────────────────────────────
|
||||
_initialLanguage: '',
|
||||
|
||||
async init() {
|
||||
try {
|
||||
const res = await fetch('/api/profile');
|
||||
@@ -361,6 +363,7 @@ function profileSettings() {
|
||||
this.form.contact_email = data.contact_email || '';
|
||||
this.form.preferred_language = data.preferred_language || '';
|
||||
this.form.preferred_theme = data.preferred_theme || 'system';
|
||||
this._initialLanguage = this.form.preferred_language;
|
||||
} catch (_e) {
|
||||
// Silently ignore — user might not be logged in (rare for this page)
|
||||
}
|
||||
@@ -378,6 +381,7 @@ function profileSettings() {
|
||||
async saveProfile() {
|
||||
this.saving = true;
|
||||
this._hideBanner();
|
||||
const previousLang = this._initialLanguage || '';
|
||||
try {
|
||||
const csrfToken = this._getCSRFToken();
|
||||
|
||||
@@ -394,6 +398,11 @@ function profileSettings() {
|
||||
this._showBanner(data.detail || 'Failed to save settings.', true);
|
||||
return;
|
||||
}
|
||||
// If the UI language changed, reload so the page renders in the new locale
|
||||
if (this.form.preferred_language !== previousLang) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
this._showBanner('Profile settings saved successfully.', false);
|
||||
// Update display name across the nav (refresh the avatar/name)
|
||||
if (data.avatar_url) {
|
||||
|
||||
Reference in New Issue
Block a user