CI / Lint e auditorias (push) Successful in 45s
CI / Trivy (dependências e segredos) (push) Successful in 9s
CI / Imagem Docker (linux/arm64) (push) Successful in 2m25s
Release / Plano (reaproveitar a imagem?) (push) Successful in 5s
Release / ci (push) Skipped
Release / Publica a imagem (linux/arm64) (push) Successful in 20s
Release / Cria a release no Gitea (push) Successful in 5s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
143 lines
4.6 KiB
React
143 lines
4.6 KiB
React
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { usePathname } from "next/navigation";
|
|
import { ChevronDown, Globe } from "lucide-react";
|
|
import { LOCALES, getLocalizedHref } from "@/i18n/links";
|
|
|
|
export { LOCALES, getLocalizedHref };
|
|
|
|
export default function LanguageSelector() {
|
|
if (LOCALES.length <= 1) {
|
|
return null;
|
|
}
|
|
|
|
const pathname = usePathname() || "";
|
|
const [open, setOpen] = useState(false);
|
|
const [hash, setHash] = useState("");
|
|
const containerRef = useRef(null);
|
|
|
|
// Detecta locale da URL atual
|
|
const currentLocaleCode =
|
|
LOCALES.find((l) => pathname === `/${l.code}` || pathname.startsWith(`/${l.code}/`))?.code || "pt";
|
|
|
|
const currentLocale = LOCALES.find((l) => l.code === currentLocaleCode) || LOCALES[0];
|
|
|
|
useEffect(() => {
|
|
if (typeof window !== "undefined") {
|
|
setHash(window.location.hash || "");
|
|
}
|
|
}, [pathname]);
|
|
|
|
useEffect(() => {
|
|
function onPointerDown(e) {
|
|
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
setOpen(false);
|
|
}
|
|
}
|
|
function onKeyDown(e) {
|
|
if (e.key === "Escape") {
|
|
setOpen(false);
|
|
}
|
|
}
|
|
document.addEventListener("mousedown", onPointerDown);
|
|
document.addEventListener("keydown", onKeyDown);
|
|
return () => {
|
|
document.removeEventListener("mousedown", onPointerDown);
|
|
document.removeEventListener("keydown", onKeyDown);
|
|
};
|
|
}, []);
|
|
|
|
const handleSelect = (code) => {
|
|
if (typeof document !== "undefined") {
|
|
document.cookie = `NEXT_LOCALE=${code}; path=/; max-age=31536000; SameSite=Lax`;
|
|
}
|
|
setOpen(false);
|
|
};
|
|
|
|
return (
|
|
<div ref={containerRef} className="relative">
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen(!open)}
|
|
className="flex h-11 min-w-[44px] items-center justify-center gap-1.5 rounded-full border border-line bg-surface px-2.5 text-sm sm:px-3 font-semibold text-ink transition-colors hover:bg-surface-2 focus-visible:outline-2 focus-visible:outline-accent"
|
|
aria-haspopup="listbox"
|
|
aria-expanded={open}
|
|
aria-label={
|
|
currentLocaleCode === "en"
|
|
? `Current language: ${currentLocale.label}. Click to switch language.`
|
|
: currentLocaleCode === "es"
|
|
? `Idioma actual: ${currentLocale.label}. Haga clic para cambiar de idioma.`
|
|
: currentLocaleCode === "fr"
|
|
? `Langue actuelle : ${currentLocale.label}. Cliquez pour changer de langue.`
|
|
: currentLocaleCode === "de"
|
|
? `Aktuelle Sprache: ${currentLocale.label}. Klicken Sie, um die Sprache zu wechseln.`
|
|
: `Idioma atual: ${currentLocale.label}. Clique para trocar de idioma.`
|
|
}
|
|
title={
|
|
currentLocaleCode === "en"
|
|
? `Language: ${currentLocale.label}`
|
|
: currentLocaleCode === "fr"
|
|
? `Langue : ${currentLocale.label}`
|
|
: currentLocaleCode === "de"
|
|
? `Sprache: ${currentLocale.label}`
|
|
: `Idioma: ${currentLocale.label}`
|
|
}
|
|
>
|
|
<Globe className="hidden h-4 w-4 text-muted sm:block" aria-hidden="true" />
|
|
<span className="font-semibold">{currentLocale.short}</span>
|
|
<ChevronDown
|
|
className={`hidden h-3 w-3 text-muted transition-transform duration-200 sm:block ${open ? "rotate-180" : ""}`}
|
|
aria-hidden="true"
|
|
/>
|
|
</button>
|
|
|
|
{open && (
|
|
<div
|
|
role="listbox"
|
|
aria-label={
|
|
currentLocaleCode === "en"
|
|
? "Available languages"
|
|
: currentLocaleCode === "es"
|
|
? "Idiomas disponibles"
|
|
: currentLocaleCode === "fr"
|
|
? "Langues disponibles"
|
|
: currentLocaleCode === "de"
|
|
? "Verfügbare Sprachen"
|
|
: "Idiomas disponíveis"
|
|
}
|
|
className="absolute right-0 top-full mt-2 w-48 rounded-2xl border border-line bg-surface p-1.5 shadow-[0_24px_60px_-20px_rgba(15,23,42,0.35)] dark:shadow-[0_24px_60px_-20px_rgba(0,0,0,0.8)] z-50"
|
|
>
|
|
{LOCALES.map((loc) => {
|
|
const isActive = loc.code === currentLocaleCode;
|
|
const href = `${getLocalizedHref(pathname, loc.code)}${hash}`;
|
|
|
|
return (
|
|
<a
|
|
key={loc.code}
|
|
href={href}
|
|
lang={loc.lang}
|
|
hrefLang={loc.lang}
|
|
role="option"
|
|
aria-selected={isActive}
|
|
aria-current={isActive ? "true" : undefined}
|
|
onClick={() => handleSelect(loc.code)}
|
|
className={`flex min-h-[44px] items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors ${
|
|
isActive
|
|
? "bg-accent-soft text-accent font-semibold"
|
|
: "text-ink-soft hover:bg-surface-2 hover:text-ink"
|
|
}`}
|
|
>
|
|
<span>{loc.label}</span>
|
|
<span className={`text-xs ${isActive ? "text-accent" : "text-muted"}`}>
|
|
{loc.short}
|
|
</span>
|
|
</a>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|