feat: complete i18n support, telegram webhook, and security improvements
This commit is contained in:
101
components/LanguageSwitcher.tsx
Normal file
101
components/LanguageSwitcher.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
"use client";
|
||||
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
|
||||
export default function LanguageSwitcher({ currentLang }: { currentLang: string }) {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const languages = [
|
||||
{ code: "tr", label: "Türkçe", flag: "🇹🇷" },
|
||||
{ code: "en", label: "English", flag: "🇺🇸" },
|
||||
];
|
||||
|
||||
const handleLangChange = (newLang: string) => {
|
||||
if (newLang === currentLang) {
|
||||
setIsOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const segments = pathname.split("/");
|
||||
segments[1] = newLang;
|
||||
const newPath = segments.join("/");
|
||||
|
||||
router.push(newPath);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
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 current = languages.find((l) => l.code === currentLang) || languages[0];
|
||||
|
||||
return (
|
||||
<div className="lang-switcher" ref={dropdownRef} style={{ position: "relative", marginBottom: "12px" }}>
|
||||
<button
|
||||
className="btn btn-ghost"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
style={{ width: "100%", justifyContent: "space-between", padding: "8px 12px", border: "1px solid var(--border-color)" }}
|
||||
>
|
||||
<span style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<span>{current.flag}</span>
|
||||
<span style={{ fontSize: "13px" }}>{current.label}</span>
|
||||
</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ transform: isOpen ? "rotate(180deg)" : "none", transition: "transform 0.2s" }}>
|
||||
<polyline points="6 9 12 15 18 9" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
bottom: "100%",
|
||||
left: 0,
|
||||
right: 0,
|
||||
marginBottom: "4px",
|
||||
background: "var(--card-bg)",
|
||||
border: "1px solid var(--border-color)",
|
||||
borderRadius: "8px",
|
||||
boxShadow: "0 4px 12px rgba(0,0,0,0.2)",
|
||||
zIndex: 100,
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
{languages.map((lang) => (
|
||||
<button
|
||||
key={lang.code}
|
||||
className={`lang-option ${lang.code === currentLang ? "active" : ""}`}
|
||||
onClick={() => handleLangChange(lang.code)}
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "10px 12px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
background: lang.code === currentLang ? "var(--bg-secondary)" : "transparent",
|
||||
border: "none",
|
||||
color: "var(--text-primary)",
|
||||
cursor: "pointer",
|
||||
textAlign: "left",
|
||||
fontSize: "13px",
|
||||
transition: "background 0.2s"
|
||||
}}
|
||||
>
|
||||
<span>{lang.flag}</span>
|
||||
<span>{lang.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user