98 lines
3.4 KiB
TypeScript
98 lines
3.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
import { X, Globe } from "lucide-react";
|
|
import { localeFlags, type Locale, type Dictionary } from "@/lib/dictionaries";
|
|
|
|
interface LangBannerProps {
|
|
locale: Locale;
|
|
dict: Dictionary;
|
|
}
|
|
|
|
export default function LangBanner({ locale, dict }: LangBannerProps) {
|
|
const [show, setShow] = useState(false);
|
|
const pathname = usePathname();
|
|
|
|
const otherLocale: Locale = locale === "en" ? "sr" : "en";
|
|
|
|
useEffect(() => {
|
|
// Don't show if user already dismissed
|
|
if (document.cookie.includes("lang-banner-dismissed=1")) return;
|
|
|
|
// Check browser language
|
|
const browserLangs = navigator.languages || [navigator.language];
|
|
const prefersOther = browserLangs.some((lang) => {
|
|
const base = lang.toLowerCase().split("-")[0];
|
|
if (locale === "en" && ["sr", "bs", "hr"].includes(base)) return true;
|
|
if (locale === "sr" && base === "en") return true;
|
|
return false;
|
|
});
|
|
|
|
if (prefersOther) {
|
|
// Small delay so it doesn't flash on load
|
|
const timer = setTimeout(() => setShow(true), 1500);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [locale]);
|
|
|
|
useEffect(() => {
|
|
document.body.style.setProperty("--banner-h", show ? "40px" : "0px");
|
|
return () => { document.body.style.setProperty("--banner-h", "0px"); };
|
|
}, [show]);
|
|
|
|
function dismiss() {
|
|
setShow(false);
|
|
// Dismiss for 30 days
|
|
document.cookie = "lang-banner-dismissed=1; path=/; max-age=2592000; SameSite=Lax";
|
|
}
|
|
|
|
function getOtherLocalePath() {
|
|
const segments = pathname.split("/");
|
|
segments[1] = otherLocale;
|
|
return segments.join("/") || `/${otherLocale}`;
|
|
}
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{show && (
|
|
<motion.div
|
|
initial={{ height: 0, opacity: 0 }}
|
|
animate={{ height: "auto", opacity: 1 }}
|
|
exit={{ height: 0, opacity: 0 }}
|
|
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
|
className="fixed inset-x-0 top-0 z-[60] overflow-hidden"
|
|
>
|
|
<div className="flex items-center justify-center gap-3 border-b border-purple-400/20 bg-purple-500/10 px-4 py-2.5 text-sm backdrop-blur-xl">
|
|
<Globe className="h-3.5 w-3.5 text-purple-300" strokeWidth={1.5} />
|
|
<span className="text-text-secondary">
|
|
{dict.langBanner.message}{" "}
|
|
<Link
|
|
href={getOtherLocalePath()}
|
|
onClick={() => {
|
|
// Set cookie so future root visits go to this locale
|
|
document.cookie = `NEXT_LOCALE=${otherLocale}; path=/; max-age=31536000; SameSite=Lax`;
|
|
dismiss();
|
|
}}
|
|
className="font-medium text-purple-300 underline decoration-purple-400/40 underline-offset-2 transition-colors hover:text-purple-200"
|
|
>
|
|
{localeFlags[otherLocale]} {dict.langBanner.switchTo}
|
|
</Link>
|
|
</span>
|
|
<button
|
|
type="button"
|
|
onClick={dismiss}
|
|
className="ml-2 rounded-md p-1 text-text-secondary transition-colors hover:bg-white/5 hover:text-text-primary"
|
|
aria-label="Dismiss"
|
|
>
|
|
<X className="h-3.5 w-3.5" />
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|