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:
copilot-swe-agent[bot]
2026-03-14 10:30:50 +00:00
parent c3e389d957
commit 1416d901d0
5 changed files with 178 additions and 11 deletions
+22 -2
View File
@@ -18,7 +18,7 @@ import logging
from hashlib import md5
from typing import Annotated
from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile, status
from fastapi import APIRouter, Depends, File, HTTPException, Request, Response, UploadFile, status
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
@@ -156,7 +156,9 @@ async def get_profile(request: Request, db: DbSession) -> ProfileResponse:
@router.patch("", response_model=ProfileResponse)
@require_login
async def update_profile(body: ProfileUpdateRequest, request: Request, db: DbSession) -> ProfileResponse:
async def update_profile(
body: ProfileUpdateRequest, request: Request, response: Response, db: DbSession
) -> ProfileResponse:
"""Update the current user's editable profile settings."""
user_id = _get_user_id(request)
profile = _get_or_create_profile(db, user_id)
@@ -171,6 +173,24 @@ async def update_profile(body: ProfileUpdateRequest, request: Request, db: DbSes
)
profile.preferred_language = lang or None # type: ignore[assignment]
# Keep session and cookie in sync so detect_language() picks up
# the new preference immediately (without a DB round-trip).
if hasattr(request, "session"):
if lang:
request.session["preferred_language"] = lang
else:
request.session.pop("preferred_language", None)
if lang:
response.set_cookie(
key="docuelevate_lang",
value=lang,
max_age=30 * 24 * 60 * 60,
httponly=False,
samesite="lax",
)
else:
response.delete_cookie(key="docuelevate_lang")
# Validate theme
if body.preferred_theme is not None:
theme = body.preferred_theme.lower().strip()