diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx new file mode 100644 index 00000000..a702357b --- /dev/null +++ b/src/components/LanguageSwitcher.tsx @@ -0,0 +1,97 @@ +import { Languages } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { SUPPORTED_LANGUAGES, changeAppLanguage, isRTLLanguage } from '@/i18n'; +import { cn } from '@/lib/utils'; + +/** + * Resolve the live i18next language code to one of `SUPPORTED_LANGUAGES`' + * codes so the right row shows as selected. + * + * Mirrors the logic in `LanguageSettingsPage` so the top-nav switcher and the + * settings page agree on which language is active: + * 1. Exact match (case-insensitive) — `zh-Hant` → `zh-Hant`, `en` → `en`. + * 2. Traditional-Chinese aliases — `zh-TW` / `zh-HK` resolve to `zh-Hant` + * because `i18n.ts` registers them as resource aliases. + * 3. Base-code fallback — `en-US` → `en`, `pt-BR` → `pt`. + */ +function resolveCurrentLng(rawLng: string): string { + const lower = rawLng.toLowerCase(); + const exact = SUPPORTED_LANGUAGES.find((l) => l.code.toLowerCase() === lower); + if (exact) return exact.code; + if (lower === 'zh-tw' || lower === 'zh-hk') return 'zh-Hant'; + return lower.split('-')[0]; +} + +interface LanguageSwitcherProps { + /** Extra classes for the trigger button. */ + className?: string; +} + +/** + * Compact language picker for the top nav. A globe/languages icon opens a + * dropdown of every supported language by its own native name, so a visitor + * who can't read the current UI language can still recognize and pick theirs. + * + * Switching happens in place — `changeAppLanguage` swaps the active locale and + * the app re-renders translated — so the user never loses their scroll + * position, the campaign they're reading, or any in-progress form state. The + * full `/settings/language` page remains the canonical deep-link. + * + * Each row renders in its own language and direction (`lang` + `dir`) so the + * native names read correctly regardless of the current UI direction. + */ +export function LanguageSwitcher({ className }: LanguageSwitcherProps) { + const { t, i18n: i18nInstance } = useTranslation(); + const currentLng = resolveCurrentLng(i18nInstance.language ?? 'en'); + + return ( + + + + + + {t('language.title')} + + { + if (code !== currentLng) void changeAppLanguage(code); + }} + > + {SUPPORTED_LANGUAGES.map((language) => ( + + {language.nativeName} + + ))} + + + + ); +} + +export default LanguageSwitcher; diff --git a/src/components/TopNav.tsx b/src/components/TopNav.tsx index bbb13914..9e5a3f48 100644 --- a/src/components/TopNav.tsx +++ b/src/components/TopNav.tsx @@ -19,6 +19,7 @@ import { nip19 } from 'nostr-tools'; import { Capacitor } from '@capacitor/core'; import { LoginArea } from '@/components/auth/LoginArea'; +import { LanguageSwitcher } from '@/components/LanguageSwitcher'; import { LogoIcon } from '@/components/icons/LogoIcon'; import { Sheet, SheetContent } from '@/components/ui/sheet'; import { useAppContext } from '@/hooks/useAppContext'; @@ -120,6 +121,11 @@ export function TopNav() { {/* Right cluster */}
+ {/* Always-visible language switcher so a visitor who can't read the + current UI language can find and pick theirs — independent of + login state. Switches in place without leaving the page. */} + + {user ? ( ) : ( diff --git a/src/locales/ar.json b/src/locales/ar.json index 36c66262..6c53d893 100644 --- a/src/locales/ar.json +++ b/src/locales/ar.json @@ -63,6 +63,7 @@ "groups": "المجموعات", "pledge": "التعهدات", "search": "بحث", + "language": "اللغة", "dashboard": "لوحة التحكم", "myDashboard": "لوحتي", "wallet": "المحفظة", diff --git a/src/locales/en.json b/src/locales/en.json index 8533c1f6..063e5c88 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -68,6 +68,7 @@ "groups": "Groups", "pledge": "Pledge", "search": "Search", + "language": "Language", "dashboard": "Dashboard", "myDashboard": "My Dashboard", "wallet": "Wallet", diff --git a/src/locales/es.json b/src/locales/es.json index 6e923d5b..db90be19 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -67,6 +67,7 @@ "groups": "Grupos", "pledge": "Promesas", "search": "Buscar", + "language": "Idioma", "dashboard": "Panel", "myDashboard": "Mi panel", "wallet": "Cartera", diff --git a/src/locales/fa.json b/src/locales/fa.json index 191691ad..2c2e32b0 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -67,6 +67,7 @@ "groups": "گروه‌ها", "pledge": "تعهدها", "search": "جستجو", + "language": "زبان", "dashboard": "داشبورد", "myDashboard": "داشبورد من", "wallet": "کیف پول", diff --git a/src/locales/fr.json b/src/locales/fr.json index a72aa3a9..772fa8dd 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -66,6 +66,7 @@ "groups": "Groupes", "pledge": "Promesse", "search": "Rechercher", + "language": "Langue", "dashboard": "Tableau de bord", "myDashboard": "Mon tableau de bord", "wallet": "Portefeuille", diff --git a/src/locales/hi.json b/src/locales/hi.json index a9c95fd2..3f2005bb 100644 --- a/src/locales/hi.json +++ b/src/locales/hi.json @@ -67,6 +67,7 @@ "groups": "ग्रुप", "pledge": "प्लेज", "search": "खोजें", + "language": "भाषा", "dashboard": "डैशबोर्ड", "myDashboard": "मेरा डैशबोर्ड", "wallet": "वॉलेट", diff --git a/src/locales/id.json b/src/locales/id.json index 4ac976e6..82a3b5fc 100644 --- a/src/locales/id.json +++ b/src/locales/id.json @@ -67,6 +67,7 @@ "groups": "Grup", "pledge": "Ikrar", "search": "Cari", + "language": "Bahasa", "dashboard": "Dasbor", "myDashboard": "Dasbor Saya", "wallet": "Dompet", diff --git a/src/locales/km.json b/src/locales/km.json index cbe05c60..aebb0f2a 100644 --- a/src/locales/km.json +++ b/src/locales/km.json @@ -67,6 +67,7 @@ "groups": "ក្រុម", "pledge": "ការសន្យា", "search": "ស្វែងរក", + "language": "ភាសា", "dashboard": "ផ្ទាំងគ្រប់គ្រង", "myDashboard": "ផ្ទាំងគ្រប់គ្រងរបស់ខ្ញុំ", "wallet": "កាបូប", diff --git a/src/locales/ps.json b/src/locales/ps.json index 1f8f2f74..d98c7a20 100644 --- a/src/locales/ps.json +++ b/src/locales/ps.json @@ -67,6 +67,7 @@ "groups": "ډلې", "pledge": "ژمنې", "search": "لټون", + "language": "ژبه", "dashboard": "ډیشبورډ", "myDashboard": "زما ډیشبورډ", "wallet": "بټوه", diff --git a/src/locales/pt.json b/src/locales/pt.json index efe9b41b..fe25542d 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -67,6 +67,7 @@ "groups": "Grupos", "pledge": "Promessa", "search": "Pesquisar", + "language": "Idioma", "dashboard": "Painel", "myDashboard": "Meu painel", "wallet": "Carteira", diff --git a/src/locales/ru.json b/src/locales/ru.json index 822414b4..069b6df0 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -67,6 +67,7 @@ "groups": "Группы", "pledge": "Обещание", "search": "Поиск", + "language": "Язык", "dashboard": "Панель", "myDashboard": "Моя панель", "wallet": "Кошелёк", diff --git a/src/locales/sn.json b/src/locales/sn.json index fb25fdf2..7c43c911 100644 --- a/src/locales/sn.json +++ b/src/locales/sn.json @@ -67,6 +67,7 @@ "groups": "Mapoka", "pledge": "Zvivimbiso", "search": "Tsvaga", + "language": "Mutauro", "dashboard": "Dashboard", "myDashboard": "Dashboard Yangu", "wallet": "Chikwama", diff --git a/src/locales/sw.json b/src/locales/sw.json index 3337341e..292442e4 100644 --- a/src/locales/sw.json +++ b/src/locales/sw.json @@ -67,6 +67,7 @@ "groups": "Vikundi", "pledge": "Ahadi", "search": "Tafuta", + "language": "Lugha", "dashboard": "Dashibodi", "wallet": "Pochi", "notifications": "Arifa", diff --git a/src/locales/tr.json b/src/locales/tr.json index 8e4cba3c..ee0a9614 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -66,6 +66,7 @@ "groups": "Gruplar", "pledge": "Taahhüt", "search": "Ara", + "language": "Dil", "dashboard": "Panel", "myDashboard": "Panelim", "wallet": "Cüzdan", diff --git a/src/locales/zh-Hant.json b/src/locales/zh-Hant.json index 658e386b..92818168 100644 --- a/src/locales/zh-Hant.json +++ b/src/locales/zh-Hant.json @@ -67,6 +67,7 @@ "groups": "群組", "pledge": "承諾", "search": "搜尋", + "language": "語言", "dashboard": "儀表板", "myDashboard": "我的儀表板", "wallet": "錢包", diff --git a/src/locales/zh.json b/src/locales/zh.json index 26cbbc01..0c62e5fa 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -67,6 +67,7 @@ "groups": "群组", "pledge": "承诺", "search": "搜索", + "language": "语言", "dashboard": "仪表板", "myDashboard": "我的仪表板", "wallet": "钱包",