Root cause: initI18n() runs before auth on login page, so /api/auth/me fails (401) and locale falls back to browser language. After login, showWelcome() renders with stale locale without re-checking server preference. French users always got French UI. Fix: after successful login, fetch /api/auth/me and call setLocale() if server language differs from current. This ensures the dashboard renders in the user's saved language preference.