mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
fix(login): address PR review feedback
- Use apiClient instead of raw fetch() in configApi.getPublicConfig - Validate DEFAULT_LANGUAGE against supported codes on server startup - Log warning instead of silently swallowing fetch errors in LoginPage - Case-insensitive browser language matching in detectBrowserLanguage - Guard against undefined navigator in detectBrowserLanguage - Validate language code in setLanguageTransient before applying - Import directly from TranslationContext instead of barrel index
This commit is contained in:
@@ -380,7 +380,7 @@ export const weatherApi = {
|
||||
|
||||
export const configApi = {
|
||||
getPublicConfig: (): Promise<{ defaultLanguage: string }> =>
|
||||
fetch('/api/config').then(r => r.json()),
|
||||
apiClient.get('/config').then(r => r.data),
|
||||
}
|
||||
|
||||
export const settingsApi = {
|
||||
|
||||
@@ -54,19 +54,24 @@ export function isRtlLanguage(language: string): boolean {
|
||||
// Detects the user's preferred language from the browser/OS settings and maps
|
||||
// it to one of the supported language codes. Returns null if no match is found.
|
||||
export function detectBrowserLanguage(): string | null {
|
||||
const browserLangs = navigator.languages?.length ? navigator.languages : [navigator.language]
|
||||
if (typeof navigator === 'undefined') return null
|
||||
const browserLangs = navigator.languages?.length
|
||||
? navigator.languages
|
||||
: navigator.language ? [navigator.language] : []
|
||||
const supported = SUPPORTED_LANGUAGES.map(l => l.value)
|
||||
|
||||
for (const lang of browserLangs) {
|
||||
// Exact match (e.g. 'de', 'zh-TW')
|
||||
if (supported.includes(lang)) return lang
|
||||
// Exact match (e.g. 'de', 'zh-TW') — case-insensitive
|
||||
const exactMatch = supported.find(s => s.toLowerCase() === lang.toLowerCase())
|
||||
if (exactMatch) return exactMatch
|
||||
|
||||
// Portuguese variants → our code is 'br' (pt-BR)
|
||||
if (lang.startsWith('pt')) return 'br'
|
||||
if (lang.toLowerCase().startsWith('pt')) return 'br'
|
||||
|
||||
// Prefix match (e.g. 'de-AT' → 'de', 'zh-CN' → 'zh')
|
||||
const prefix = lang.split('-')[0]
|
||||
if (supported.includes(prefix)) return prefix
|
||||
// Prefix match (e.g. 'de-AT' → 'de', 'zh-CN' → 'zh') — case-insensitive
|
||||
const prefix = lang.split('-')[0].toLowerCase()
|
||||
const prefixMatch = supported.find(s => s.toLowerCase() === prefix)
|
||||
if (prefixMatch) return prefixMatch
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -2,7 +2,7 @@ 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'
|
||||
import { SUPPORTED_LANGUAGES, useTranslation, detectBrowserLanguage } from '../i18n/TranslationContext'
|
||||
import { authApi, configApi } from '../api/client'
|
||||
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'
|
||||
@@ -134,7 +134,7 @@ export default function LoginPage(): React.ReactElement {
|
||||
|
||||
configApi.getPublicConfig()
|
||||
.then(({ defaultLanguage }) => { if (defaultLanguage) setLanguageTransient(defaultLanguage) })
|
||||
.catch(() => {})
|
||||
.catch((err) => console.warn('Failed to fetch default language config:', err))
|
||||
}, [setLanguageTransient])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -64,6 +64,8 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
|
||||
// Used for automatic detection (browser/server default) — only explicit user
|
||||
// choices via the UI should be persisted.
|
||||
setLanguageTransient: (lang: string) => {
|
||||
const supported = ['de', 'en', 'es', 'fr', 'hu', 'nl', 'br', 'cs', 'pl', 'ru', 'zh', 'zh-TW', 'it', 'ar']
|
||||
if (!supported.includes(lang)) return
|
||||
set((state) => ({ settings: { ...state.settings, language: lang } }))
|
||||
},
|
||||
|
||||
|
||||
@@ -102,4 +102,9 @@ export const ENCRYPTION_KEY = _encryptionKey;
|
||||
// DEFAULT_LANGUAGE sets the language shown on the login page before the user
|
||||
// selects one. Only applies when the user has no saved language preference.
|
||||
// Supported values: de, en, es, fr, hu, nl, br, cs, pl, ru, zh, zh-TW, it, ar
|
||||
export const DEFAULT_LANGUAGE = process.env.DEFAULT_LANGUAGE || 'en';
|
||||
const SUPPORTED_LANG_CODES = ['de', 'en', 'es', 'fr', 'hu', 'nl', 'br', 'cs', 'pl', 'ru', 'zh', 'zh-TW', 'it', 'ar'];
|
||||
const rawDefaultLang = process.env.DEFAULT_LANGUAGE || 'en';
|
||||
if (!SUPPORTED_LANG_CODES.includes(rawDefaultLang)) {
|
||||
console.warn(`DEFAULT_LANGUAGE="${rawDefaultLang}" is not supported. Falling back to "en". Supported: ${SUPPORTED_LANG_CODES.join(', ')}`);
|
||||
}
|
||||
export const DEFAULT_LANGUAGE = SUPPORTED_LANG_CODES.includes(rawDefaultLang) ? rawDefaultLang : 'en';
|
||||
|
||||
Reference in New Issue
Block a user