language Switcher added
This commit is contained in:
120
components/languageSwitcher.tsx
Normal file
120
components/languageSwitcher.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useTransition } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { Check, ChevronDown, Globe } from "lucide-react";
|
||||
import { locales, localeFlags, localeNames, type Locale } from "@/i18n/config";
|
||||
import { useLocale } from "next-intl";
|
||||
|
||||
export default function LanguageSelectRadix() {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const currentLocale = useLocale() as Locale;
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const changeLanguage = (newLocale: Locale) => {
|
||||
if (newLocale === currentLocale) {
|
||||
setIsOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
startTransition(() => {
|
||||
// ✅ 1. Set cookie (middleware will sync this)
|
||||
document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000; SameSite=Lax`;
|
||||
|
||||
// ✅ 2. Build new path
|
||||
const segments = pathname.split("/").filter(Boolean);
|
||||
|
||||
// Remove current locale if exists
|
||||
const pathWithoutLocale =
|
||||
segments[0] && locales.includes(segments[0] as Locale)
|
||||
? segments.slice(1)
|
||||
: segments;
|
||||
|
||||
// Add new locale
|
||||
const newPath = `/${newLocale}${
|
||||
pathWithoutLocale.length ? "/" + pathWithoutLocale.join("/") : ""
|
||||
}`;
|
||||
|
||||
// ✅ 3. Navigate (middleware will handle the rest)
|
||||
router.push(newPath);
|
||||
|
||||
// ✅ 4. Force refresh after navigation completes
|
||||
setTimeout(() => {
|
||||
router.refresh();
|
||||
}, 100); // Small delay ensures navigation completes
|
||||
});
|
||||
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{/* Trigger Button */}
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
disabled={isPending}
|
||||
className="inline-flex items-center justify-between gap-2 px-2 py-1 border border-gray-300 hover:border-red-600
|
||||
rounded-lg text-white text-sm font-medium shadow-sm hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
|
||||
aria-label="Tilni tanlash"
|
||||
aria-expanded={isOpen}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{isPending ? (
|
||||
<Globe className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<span className="text-lg">{localeFlags[currentLocale]}</span>
|
||||
)}
|
||||
<span className="hidden md:inline font-medium">
|
||||
{localeNames[currentLocale]}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronDown
|
||||
className={`w-4 h-4 transition-transform duration-200 ${
|
||||
isOpen ? "rotate-180" : ""
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{/* Dropdown */}
|
||||
{isOpen && (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 z-40"
|
||||
onClick={() => setIsOpen(false)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* Menu */}
|
||||
<div className="absolute right-0 mt-2 w-48 rounded-lg border border-gray-200 bg-white shadow-lg z-50 animate-in fade-in slide-in-from-top-2 duration-200">
|
||||
<div className="p-1" role="menu">
|
||||
{locales.map((lang) => (
|
||||
<button
|
||||
key={lang}
|
||||
onClick={() => changeLanguage(lang)}
|
||||
disabled={isPending}
|
||||
role="menuitem"
|
||||
className={`w-full flex items-center justify-between px-3 py-2 text-sm rounded-md transition-colors ${
|
||||
currentLocale === lang
|
||||
? "bg-blue-50 text-blue-700 font-medium"
|
||||
: "hover:bg-gray-100 text-gray-700"
|
||||
} disabled:opacity-50 disabled:cursor-not-allowed`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg">{localeFlags[lang]}</span>
|
||||
<span>{localeNames[lang]}</span>
|
||||
</div>
|
||||
{currentLocale === lang && (
|
||||
<Check className="w-4 h-4 text-blue-700" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user