From 3e88a64933a7ccd5e55facb4d8a69885d325d59c Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Date: Sun, 24 May 2026 22:54:37 -0300 Subject: [PATCH] W5.8: language toggle in the navbar (whole-site PT-BR / EN) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User: "quero que o usuário possa escolher o idioma na navbar, e então leia o site todo em seu idioma." The locale infrastructure already existed (cookie + getLocale() + every page renders bilingual). The toggle only lived as a subtle floating control bottom-left. This surfaces it in the navbars where users expect it, and confirms whole-site language switching. locale-toggle-client.tsx - New variant="navbar": gold-amber pill matching the magazine theme (PT | EN, active = solid gold). Retro green variant kept for the floating fallback. - Guard: clicking the already-active language is a no-op. - aria-pressed on each button for a11y. locale-toggle.tsx: LocaleToggle gains variant passthrough. site-header.tsx: added to the main nav (homepage + /sightings /witnesses /objects /locations /operations /documents). bureau-nav.tsx: optional `locale` prop renders the same toggle pushed to the right (ml-auto) on /bureau, /c/[slug], /h/[id], /jobs/[id]. All four callers now pass locale (getLocale added to h + jobs pages). Cookie-based: switching sets `locale` cookie + router.refresh(); the server re-renders every component through getLocale(), so the entire page — headers, hero, summaries, case narratives, entity cards — flips language in one click. The floating bottom-left toggle stays as a global fallback for pages without a navbar (/d, /e, /search, /graph). Also this session: Sun-Tzu smoke succeeded on the refreshed CLAUDE_CODE_OAUTH_TOKEN — brief B-0001 written for the green fireballs ("bolas de fogo verdes confinadas ao corredor nuclear... cor espectral e trajetória quase horizontal que os próprios analistas consideraram irreconciliáveis com meteoros naturais", 3 pillars). Bulk entity enrichment resumed in the background. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/app/bureau/page.tsx | 2 +- web/app/c/[slug]/page.tsx | 2 +- web/app/documents/page.tsx | 2 +- web/app/h/[hypothesisId]/page.tsx | 4 +++- web/app/jobs/[id]/page.tsx | 4 +++- web/components/bureau-nav.tsx | 9 +++++++- web/components/locale-toggle-client.tsx | 29 ++++++++++++++++++++++++- web/components/locale-toggle.tsx | 7 ++++-- web/components/site-header.tsx | 4 +++- 9 files changed, 53 insertions(+), 10 deletions(-) diff --git a/web/app/bureau/page.tsx b/web/app/bureau/page.tsx index 4e7a782..21aae11 100644 --- a/web/app/bureau/page.tsx +++ b/web/app/bureau/page.tsx @@ -17,7 +17,7 @@ export default async function BureauPage() { return (
- +
diff --git a/web/app/c/[slug]/page.tsx b/web/app/c/[slug]/page.tsx index f5e227b..a8d2c85 100644 --- a/web/app/c/[slug]/page.tsx +++ b/web/app/c/[slug]/page.tsx @@ -131,7 +131,7 @@ export default async function CaseReportPage({ return (
- 32 ? title.slice(0, 32) + "…" : title }, ]} /> diff --git a/web/app/documents/page.tsx b/web/app/documents/page.tsx index 849be46..215adea 100644 --- a/web/app/documents/page.tsx +++ b/web/app/documents/page.tsx @@ -39,7 +39,7 @@ export default async function DocumentsPage() { return (
- +
diff --git a/web/app/h/[hypothesisId]/page.tsx b/web/app/h/[hypothesisId]/page.tsx index 4adfd13..8bb1187 100644 --- a/web/app/h/[hypothesisId]/page.tsx +++ b/web/app/h/[hypothesisId]/page.tsx @@ -18,6 +18,7 @@ import path from "node:path"; import { pgQuery } from "@/lib/retrieval/db"; import { AuthBar } from "@/components/auth-bar"; import { BureauNav } from "@/components/bureau-nav"; +import { getLocale } from "@/components/locale-toggle"; import { RedTeamRequestButton } from "@/components/red-team-request-button"; export const runtime = "nodejs"; @@ -149,6 +150,7 @@ export default async function HypothesisPage({ }: { params: Promise<{ hypothesisId: string }> }) { const { hypothesisId } = await params; if (!/^H-\d{4}$/.test(hypothesisId)) notFound(); + const navLocale = await getLocale(); const rows = await pgQuery( `SELECT hypothesis_id, question, question_pt_br, position, position_pt_br, @@ -197,7 +199,7 @@ export default async function HypothesisPage({ return (
- }) { const { id } = await params; if (!/^[0-9a-f-]{36}$/i.test(id)) notFound(); + const navLocale = await getLocale(); const rows = await pgQuery( `SELECT job_id, kind, payload, status, worker_id, started_at, finished_at, @@ -76,7 +78,7 @@ export default async function JobPage({ return (
-
@@ -56,6 +57,12 @@ export function BureauNav({ crumbs }: { crumbs: Crumb[] }) { ))}
)} + + {locale && ( + + + + )}
); diff --git a/web/components/locale-toggle-client.tsx b/web/components/locale-toggle-client.tsx index 00e7f7f..fc68426 100644 --- a/web/components/locale-toggle-client.tsx +++ b/web/components/locale-toggle-client.tsx @@ -4,14 +4,41 @@ import { useRouter } from "next/navigation"; import { Globe } from "lucide-react"; import type { Locale } from "./locale-toggle"; -export function LocaleToggleClient({ current }: { current: Locale }) { +export function LocaleToggleClient({ + current, + variant = "retro", +}: { current: Locale; variant?: "retro" | "navbar" }) { const router = useRouter(); function setLocale(next: Locale) { + if (next === current) return; document.cookie = `locale=${next}; path=/; max-age=${60 * 60 * 24 * 365}; SameSite=Lax`; router.refresh(); } + if (variant === "navbar") { + // Gold-amber magazine theme to match SiteHeader / BureauNav. + return ( +
+ + + +
+ ); + } + return (
diff --git a/web/components/locale-toggle.tsx b/web/components/locale-toggle.tsx index bb479da..8ce8e76 100644 --- a/web/components/locale-toggle.tsx +++ b/web/components/locale-toggle.tsx @@ -13,6 +13,9 @@ export async function getLocale(): Promise { return v === "en" ? "en" : "pt-br"; } -export function LocaleToggle({ current }: { current: Locale }) { - return ; +export function LocaleToggle({ + current, + variant = "retro", +}: { current: Locale; variant?: "retro" | "navbar" }) { + return ; } diff --git a/web/components/site-header.tsx b/web/components/site-header.tsx index f4df134..ef8849a 100644 --- a/web/components/site-header.tsx +++ b/web/components/site-header.tsx @@ -6,6 +6,7 @@ */ import Link from "next/link"; import { AuthBar } from "./auth-bar"; +import { LocaleToggle } from "./locale-toggle"; export function SiteHeader({ locale }: { locale: "pt-br" | "en" }) { return ( @@ -26,7 +27,8 @@ export function SiteHeader({ locale }: { locale: "pt-br" | "en" }) { - + +