diff --git a/client/src/api/client.ts b/client/src/api/client.ts index 41f026b5..c8334e8a 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -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 = { diff --git a/client/src/i18n/TranslationContext.tsx b/client/src/i18n/TranslationContext.tsx index e73e2caa..aee8ff71 100644 --- a/client/src/i18n/TranslationContext.tsx +++ b/client/src/i18n/TranslationContext.tsx @@ -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 diff --git a/client/src/pages/LoginPage.tsx b/client/src/pages/LoginPage.tsx index 64b20666..8a309b62 100644 --- a/client/src/pages/LoginPage.tsx +++ b/client/src/pages/LoginPage.tsx @@ -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(() => { diff --git a/client/src/store/settingsStore.ts b/client/src/store/settingsStore.ts index 179527df..9e56e505 100644 --- a/client/src/store/settingsStore.ts +++ b/client/src/store/settingsStore.ts @@ -64,6 +64,8 @@ export const useSettingsStore = create((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 } })) }, diff --git a/server/src/config.ts b/server/src/config.ts index 1f3e787d..50a6dc97 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -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';