141 lines
5.9 KiB
TypeScript
141 lines
5.9 KiB
TypeScript
|
|
'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>
|
||
|
|
);
|
||
|
|
}
|