CloudProxy/components/LanguageSwitcher.tsx

141 lines
5.9 KiB
TypeScript
Raw Permalink Normal View History

2025-09-15 18:30:09 +08:00
'use client';
import { useState, useRef, useEffect } from 'react';
import { ChevronDownIcon, GlobeIcon } from 'lucide-react';
import { useRouter, usePathname } from 'next/navigation';
interface Language {
code: string;
name: string;
flag: string;
}
interface LanguageSwitcherProps {
currentLang: string;
onLanguageChange: (lang: string) => void;
}
export default function LanguageSwitcher({ currentLang, onLanguageChange }: LanguageSwitcherProps) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const router = useRouter();
const pathname = usePathname();
const languages: Language[] = [
{ code: 'zh-CN', name: '简体中文', flag: '🇨🇳' },
{ code: 'zh-TW', name: '繁體中文', flag: '🇹🇼' },
{ code: 'en', name: 'English', flag: '🇺🇸' },
{ code: 'ko', name: '한국어', flag: '🇰🇷' },
{ code: 'ja', name: '日本語', flag: '🇯🇵' },
];
const currentLanguage = languages.find((lang) => lang.code === currentLang) || languages[0];
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const handleLanguageSelect = (langCode: string) => {
// Extract the current path without the locale
const pathWithoutLocale = pathname.replace(/^\/[a-z]{2}(-[A-Z]{2})?/, '') || '/';
// Navigate to the new locale path
router.push(`/${langCode}${pathWithoutLocale}`);
// Call the callback if provided
if (onLanguageChange) {
onLanguageChange(langCode);
}
setIsOpen(false);
};
return (
<div className="relative" ref={dropdownRef} data-oid="ogskmmp">
{/* Trigger Button */}
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center space-x-2 px-4 py-2 bg-gray-800/80 backdrop-blur-sm border border-gray-700/50 rounded-xl hover:border-purple-500/50 hover:bg-gray-700/80 transition-all duration-300 group"
data-oid="7xd90mg"
>
<GlobeIcon
className="w-4 h-4 text-gray-400 group-hover:text-purple-400 transition-colors duration-300"
data-oid="s1uio5o"
/>
<span className="text-lg" data-oid="vmlb5gl">
{currentLanguage.flag}
</span>
<span
className="text-sm font-medium text-gray-300 group-hover:text-white transition-colors duration-300"
data-oid="aebk8wz"
>
{currentLanguage.name}
</span>
<ChevronDownIcon
className={`w-4 h-4 text-gray-400 group-hover:text-purple-400 transition-all duration-300 ${
isOpen ? 'rotate-180' : ''
}`}
data-oid="0ps4vmh"
/>
</button>
{/* Dropdown Menu */}
{isOpen && (
<div
className="absolute top-full right-0 mt-2 w-48 bg-gray-800/95 backdrop-blur-md border border-gray-700/50 rounded-xl shadow-2xl shadow-black/50 overflow-hidden z-50 animate-in fade-in-0 zoom-in-95 duration-200"
data-oid="9-sz9sr"
>
<div className="py-2" data-oid="db.:hro">
{languages.map((language) => (
<button
key={language.code}
onClick={() => handleLanguageSelect(language.code)}
className={`w-full flex items-center space-x-3 px-4 py-3 text-left hover:bg-gray-700/50 transition-all duration-200 ${
currentLang === language.code
? 'bg-purple-900/30 border-r-2 border-purple-500'
: ''
}`}
data-oid="pbhzkaa"
>
<span className="text-xl" data-oid="1oafez1">
{language.flag}
</span>
<span
className={`text-sm font-medium transition-colors duration-200 ${
currentLang === language.code
? 'text-purple-300'
: 'text-gray-300 hover:text-white'
}`}
data-oid="6qa2r1m"
>
{language.name}
</span>
{currentLang === language.code && (
<div
className="ml-auto w-2 h-2 bg-purple-500 rounded-full animate-pulse"
data-oid="em6bbg."
></div>
)}
</button>
))}
</div>
{/* Decorative gradient border */}
<div
className="absolute inset-0 rounded-xl bg-gradient-to-r from-purple-500/20 via-pink-500/20 to-blue-500/20 pointer-events-none opacity-50"
data-oid="cg-73k5"
></div>
</div>
)}
</div>
);
}