From a07e76c7401b4a0735110a301e7e395d95d9600d Mon Sep 17 00:00:00 2001 From: jubnl Date: Wed, 15 Apr 2026 03:04:25 +0200 Subject: [PATCH] fix(login): address review feedback on language dropdown PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix import path: use i18n barrel instead of TranslationContext directly - Encapsulate localStorage key behind hasStoredLanguage() helper in settingsStore - Fix pt-BR detection: only map pt-BR to br, pt-PT now returns null correctly - Add comment linking server SUPPORTED_LANG_CODES to canonical client source - Extract /api/config inline handler to routes/publicConfig.ts - Add aria-haspopup, aria-expanded, role=listbox/option, aria-selected to dropdown - Add 8 tests for detectBrowserLanguage (FE-COMP-I18N-016–023) - Add 3 tests for setLanguageTransient (FE-STORE-SETTINGS-015–017) --- client/src/i18n/TranslationContext.tsx | 6 ++- client/src/pages/LoginPage.tsx | 12 ++++- client/src/store/settingsStore.ts | 5 ++ client/tests/unit/i18n/index.test.ts | 51 +++++++++++++++++++ .../tests/unit/stores/settingsStore.test.ts | 31 +++++++++++ server/src/app.ts | 5 +- server/src/config.ts | 2 + server/src/routes/publicConfig.ts | 10 ++++ 8 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 server/src/routes/publicConfig.ts diff --git a/client/src/i18n/TranslationContext.tsx b/client/src/i18n/TranslationContext.tsx index f18dcf20..964a9af7 100644 --- a/client/src/i18n/TranslationContext.tsx +++ b/client/src/i18n/TranslationContext.tsx @@ -59,8 +59,10 @@ export function detectBrowserLanguage(): string | null { const exactMatch = supported.find(s => s.toLowerCase() === lang.toLowerCase()) if (exactMatch) return exactMatch - // Portuguese variants → our code is 'br' (pt-BR) - if (lang.toLowerCase().startsWith('pt')) return 'br' + // pt-BR has no exact match (our code is 'br', not 'pt-BR'), so map it explicitly. + // pt-PT and bare 'pt' are NOT mapped — they fall through to null and let the + // server default or 'en' fallback apply instead. + if (lang.toLowerCase() === 'pt-br') return 'br' // Prefix match (e.g. 'de-AT' → 'de', 'zh-CN' → 'zh') — case-insensitive const prefix = lang.split('-')[0].toLowerCase() diff --git a/client/src/pages/LoginPage.tsx b/client/src/pages/LoginPage.tsx index 8a309b62..4750531e 100644 --- a/client/src/pages/LoginPage.tsx +++ b/client/src/pages/LoginPage.tsx @@ -2,8 +2,9 @@ import React, { useState, useEffect, useMemo, useRef } from 'react' import { useNavigate, useLocation } from 'react-router-dom' import { useAuthStore } from '../store/authStore' import { useSettingsStore } from '../store/settingsStore' -import { SUPPORTED_LANGUAGES, useTranslation, detectBrowserLanguage } from '../i18n/TranslationContext' +import { SUPPORTED_LANGUAGES, useTranslation, detectBrowserLanguage } from '../i18n' import { authApi, configApi } from '../api/client' +import { hasStoredLanguage } from '../store/settingsStore' import { getApiErrorMessage } from '../types' import { Plane, Eye, EyeOff, Mail, Lock, MapPin, Calendar, Package, User, Globe, Zap, Users, Wallet, Map, CheckSquare, BookMarked, FolderOpen, Route, Shield, KeyRound, ChevronDown } from 'lucide-react' @@ -124,7 +125,7 @@ export default function LoginPage(): React.ReactElement { // 3. Server default (DEFAULT_LANGUAGE env var) // 4. 'en' → hardcoded fallback already in store useEffect(() => { - if (localStorage.getItem('app_language')) return + if (hasStoredLanguage()) return const detected = detectBrowserLanguage() if (detected) { @@ -396,6 +397,9 @@ export default function LoginPage(): React.ReactElement {