Files
miracles_in_motion/src/components/ui/LanguageSwitcher.tsx
defiQUG 37469c105c feat: Implement Stripe payment form and language switcher
- Added StripePaymentForm component for handling donations with Stripe integration.
- Included customer information fields and payment processing logic.
- Integrated language switcher component for multilingual support.
- Configured i18n with multiple languages and corresponding translation files.
- Added translation files for Arabic, German, English, Spanish, French, Portuguese, Russian, and Chinese.
2025-10-05 10:05:03 -07:00

226 lines
7.7 KiB
TypeScript

import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { motion, AnimatePresence } from 'framer-motion'
import { Globe, ChevronDown } from 'lucide-react'
import { languages } from '@/i18n/config'
interface LanguageSwitcherProps {
className?: string
variant?: 'default' | 'minimal' | 'mobile'
}
export function LanguageSwitcher({
className = '',
variant = 'default'
}: LanguageSwitcherProps) {
const { i18n, t } = useTranslation()
const [isOpen, setIsOpen] = useState(false)
const currentLang = i18n.language || 'en'
const currentLanguage = languages[currentLang as keyof typeof languages]
const handleLanguageChange = (langCode: string) => {
i18n.changeLanguage(langCode)
setIsOpen(false)
// Update document direction for RTL languages
const langConfig = languages[langCode as keyof typeof languages]
document.documentElement.dir = langConfig.dir
document.documentElement.lang = langCode
// Store preference
localStorage.setItem('i18nextLng', langCode)
}
const dropdownVariants = {
hidden: {
opacity: 0,
scale: 0.95,
y: -10
},
visible: {
opacity: 1,
scale: 1,
y: 0,
transition: {
duration: 0.2,
ease: 'easeOut'
}
},
exit: {
opacity: 0,
scale: 0.95,
y: -10,
transition: {
duration: 0.15,
ease: 'easeIn'
}
}
}
if (variant === 'minimal') {
return (
<div className={`relative ${className}`}>
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-1 p-2 rounded-lg hover:bg-gray-100 transition-colors"
aria-label={t('accessibility.toggleLanguage')}
>
<span className="text-lg">{currentLanguage?.flag || '🌐'}</span>
<span className="text-sm font-medium">{currentLang.toUpperCase()}</span>
</button>
<AnimatePresence>
{isOpen && (
<motion.div
variants={dropdownVariants}
initial="hidden"
animate="visible"
exit="exit"
className="absolute top-full right-0 mt-1 bg-white rounded-lg shadow-lg border border-gray-200 py-1 min-w-[140px] z-50"
>
{Object.entries(languages).map(([code, lang]) => (
<button
key={code}
onClick={() => handleLanguageChange(code)}
className={`w-full px-3 py-2 text-left hover:bg-gray-50 transition-colors flex items-center gap-2 ${
code === currentLang ? 'bg-purple-50 text-purple-600' : 'text-gray-700'
}`}
>
<span className="text-base">{lang.flag}</span>
<span className="text-sm">{lang.name}</span>
</button>
))}
</motion.div>
)}
</AnimatePresence>
</div>
)
}
if (variant === 'mobile') {
return (
<div className={`w-full ${className}`}>
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full flex items-center justify-between p-3 bg-white rounded-lg border border-gray-200 hover:border-purple-300 transition-colors"
>
<div className="flex items-center gap-3">
<Globe className="w-5 h-5 text-gray-500" />
<div className="text-left">
<div className="text-sm font-medium text-gray-900">
Language / Idioma
</div>
<div className="text-xs text-gray-500">
{currentLanguage?.flag} {currentLanguage?.name}
</div>
</div>
</div>
<ChevronDown className={`w-4 h-4 text-gray-500 transition-transform ${
isOpen ? 'rotate-180' : ''
}`} />
</button>
<AnimatePresence>
{isOpen && (
<motion.div
variants={dropdownVariants}
initial="hidden"
animate="visible"
exit="exit"
className="mt-2 bg-white rounded-lg shadow-lg border border-gray-200 overflow-hidden"
>
{Object.entries(languages).map(([code, lang]) => (
<button
key={code}
onClick={() => handleLanguageChange(code)}
className={`w-full px-4 py-3 text-left hover:bg-gray-50 transition-colors border-b border-gray-100 last:border-b-0 ${
code === currentLang ? 'bg-purple-50 text-purple-600' : 'text-gray-700'
}`}
>
<div className="flex items-center gap-3">
<span className="text-xl">{lang.flag}</span>
<div>
<div className="font-medium">{lang.name}</div>
<div className="text-sm text-gray-500">{code.toUpperCase()}</div>
</div>
{code === currentLang && (
<div className="ml-auto w-2 h-2 bg-purple-600 rounded-full" />
)}
</div>
</button>
))}
</motion.div>
)}
</AnimatePresence>
</div>
)
}
// Default variant
return (
<div className={`relative ${className}`}>
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-2 px-3 py-2 bg-white rounded-lg border border-gray-200 hover:border-purple-300 transition-colors shadow-sm"
aria-label={t('accessibility.toggleLanguage')}
>
<Globe className="w-4 h-4 text-gray-500" />
<span className="text-lg">{currentLanguage?.flag || '🌐'}</span>
<span className="text-sm font-medium text-gray-700">
{currentLanguage?.name || 'English'}
</span>
<ChevronDown className={`w-4 h-4 text-gray-500 transition-transform ${
isOpen ? 'rotate-180' : ''
}`} />
</button>
<AnimatePresence>
{isOpen && (
<>
{/* Backdrop */}
<div
className="fixed inset-0 z-40"
onClick={() => setIsOpen(false)}
/>
{/* Dropdown */}
<motion.div
variants={dropdownVariants}
initial="hidden"
animate="visible"
exit="exit"
className="absolute top-full right-0 mt-2 bg-white rounded-lg shadow-xl border border-gray-200 py-2 min-w-[200px] z-50"
>
<div className="px-3 py-2 border-b border-gray-100">
<div className="text-xs font-medium text-gray-500 uppercase tracking-wide">
Select Language
</div>
</div>
{Object.entries(languages).map(([code, lang]) => (
<button
key={code}
onClick={() => handleLanguageChange(code)}
className={`w-full px-4 py-2 text-left hover:bg-gray-50 transition-colors flex items-center gap-3 ${
code === currentLang ? 'bg-purple-50 text-purple-600' : 'text-gray-700'
}`}
>
<span className="text-xl">{lang.flag}</span>
<div className="flex-1">
<div className="font-medium">{lang.name}</div>
<div className="text-xs text-gray-500">{code.toUpperCase()}</div>
</div>
{code === currentLang && (
<div className="w-2 h-2 bg-purple-600 rounded-full" />
)}
</button>
))}
</motion.div>
</>
)}
</AnimatePresence>
</div>
)
}
export default LanguageSwitcher