Skip to content

Commit 7c4b6ca

Browse files
Make URLs language-aware
Implements locale-based routing in App.tsx, updates LanguageContext to support URL mappings and translations, and adjusts AboutTimeline to use translated timeline data; updates Navbar to navigate using language URLs and logo links to current locale. X-Lovable-Edit-ID: edt-691b5fc5-5b20-4004-8ac5-9cbb2a3d0806
2 parents 5a2795b + 6abef32 commit 7c4b6ca

File tree

4 files changed

+349
-86
lines changed

4 files changed

+349
-86
lines changed

src/App.tsx

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,60 @@ import { Toaster } from "@/components/ui/toaster";
22
import { Toaster as Sonner } from "@/components/ui/sonner";
33
import { TooltipProvider } from "@/components/ui/tooltip";
44
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5-
import { BrowserRouter, Routes, Route } from "react-router-dom";
6-
import { LanguageProvider } from "@/contexts/LanguageContext";
5+
import { BrowserRouter, Routes, Route, Navigate, useParams, useLocation } from "react-router-dom";
6+
import { LanguageProvider, LANG_URL_MAP, useLanguage } from "@/contexts/LanguageContext";
77
import Index from "./pages/Index";
88
import About from "./pages/About";
99
import NotFound from "./pages/NotFound";
1010

1111
const queryClient = new QueryClient();
1212

13+
// Component that reads the :locale param and sets the language, then renders the page
14+
const LocaleWrapper = ({ page }: { page: "index" | "about" }) => {
15+
const { locale } = useParams<{ locale: string }>();
16+
const { setLang } = useLanguage();
17+
const location = useLocation();
18+
19+
const lang = locale ? LANG_URL_MAP[locale] : undefined;
20+
21+
if (!lang) {
22+
// Unknown locale — redirect to pt-BR equivalent
23+
const rest = location.pathname.replace(`/${locale}`, "");
24+
return <Navigate to={`/pt-BR${rest}`} replace />;
25+
}
26+
27+
// Set the language based on the URL
28+
// Using a side-effect via render (safe here, it's synchronous state update with same value guard)
29+
setLang(lang);
30+
31+
if (page === "about") return <About />;
32+
return <Index />;
33+
};
34+
35+
const AppRoutes = () => (
36+
<Routes>
37+
{/* Root redirect to pt-BR */}
38+
<Route path="/" element={<Navigate to="/pt-BR" replace />} />
39+
40+
{/* Language-prefixed home routes */}
41+
<Route path="/:locale" element={<LocaleWrapper page="index" />} />
42+
<Route path="/:locale/about" element={<LocaleWrapper page="about" />} />
43+
44+
{/* Legacy /about without locale → redirect */}
45+
<Route path="/about" element={<Navigate to="/pt-BR/about" replace />} />
46+
47+
<Route path="*" element={<NotFound />} />
48+
</Routes>
49+
);
50+
1351
const App = () => (
1452
<QueryClientProvider client={queryClient}>
1553
<TooltipProvider>
1654
<LanguageProvider>
1755
<Toaster />
1856
<Sonner />
1957
<BrowserRouter>
20-
<Routes>
21-
<Route path="/" element={<Index />} />
22-
<Route path="/about" element={<About />} />
23-
<Route path="*" element={<NotFound />} />
24-
</Routes>
58+
<AppRoutes />
2559
</BrowserRouter>
2660
</LanguageProvider>
2761
</TooltipProvider>

src/components/AboutTimeline.tsx

Lines changed: 67 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ interface TimelineYear {
2828
events: TimelineEvent[];
2929
}
3030

31-
const TimelineEventItem = ({ event, index }: { event: TimelineEvent; index: number }) => {
31+
const TimelineEventItem = ({ event }: { event: TimelineEvent }) => {
3232
const ref = useRef(null);
3333
const isInView = useInView(ref, { once: false, margin: "-15% 0px -15% 0px" });
34+
const { t } = useLanguage();
3435

3536
return (
3637
<div ref={ref} className="relative grid grid-cols-[1fr_auto_1fr] gap-0 md:gap-8 items-start min-h-[180px]">
@@ -47,7 +48,7 @@ const TimelineEventItem = ({ event, index }: { event: TimelineEvent; index: numb
4748
<p className="mt-2 text-sm text-muted-foreground">{event.description}</p>
4849
{event.link && (
4950
<ShimmerButton href={event.link} target="_blank" rel="noopener noreferrer" className="mt-3 text-xs">
50-
Ver mais →
51+
{t("timeline.viewMore")}
5152
</ShimmerButton>
5253
)}
5354
</div>
@@ -123,6 +124,7 @@ const YearMarker = ({ year }: { year: string }) => {
123124

124125
const AboutTimeline = () => {
125126
const sectionRef = useRef<HTMLElement>(null);
127+
const { t } = useLanguage();
126128

127129
const { scrollYProgress } = useScroll({
128130
target: sectionRef,
@@ -137,29 +139,29 @@ const AboutTimeline = () => {
137139
year: "2022",
138140
events: [
139141
{
140-
month: "Janeiro",
141-
title: "Pré-lançamento no Metaverso",
142-
description: "Primeiro registro Onchain da Comunidade no Metaverso Web3land, marcado por uma POAP exclusiva. Evento histórico para todos os presentes.",
142+
month: t("timeline.2022.jan.month"),
143+
title: t("timeline.2022.jan.title"),
144+
description: t("timeline.2022.jan.desc"),
143145
image: timelinePoap,
144146
link: "https://collectors.poap.xyz/drop/24766",
145147
},
146148
{
147-
month: "Fevereiro",
148-
title: "Captação do Primeiro Grant na NEAR",
149-
description: "Dentre os muitos grants que realizamos inauguramos com a NEAR, focando em criação e tradução de conteúdo de qualidade e técnico para devs.",
149+
month: t("timeline.2022.feb.month"),
150+
title: t("timeline.2022.feb.title"),
151+
description: t("timeline.2022.feb.desc"),
150152
videoId: "Yt6wovZLgf4",
151153
},
152154
{
153-
month: "Abril",
154-
title: "Lançamento primeiro Build",
155-
description: "Lançamento da plataforma de Build com bootcamp de smart contracts em Solidity e apresentação oficial da identidade visual.",
155+
month: t("timeline.2022.apr.month"),
156+
title: t("timeline.2022.apr.title"),
157+
description: t("timeline.2022.apr.desc"),
156158
videoId: "QRjrWEVGgno",
157159
link: "https://www.youtube.com/live/gunQidhjgcs",
158160
},
159161
{
160-
month: "Outubro",
161-
title: "Crescimento exponencial",
162-
description: "Alcançamos 3 mil membros no Discord em menos de um ano, expandindo nossa comunidade para o idioma espanhol.",
162+
month: t("timeline.2022.oct.month"),
163+
title: t("timeline.2022.oct.title"),
164+
description: t("timeline.2022.oct.desc"),
163165
image: timelineChart2022,
164166
},
165167
],
@@ -168,33 +170,33 @@ const AboutTimeline = () => {
168170
year: "2023",
169171
events: [
170172
{
171-
month: "Fevereiro",
172-
title: "Primeiro Funding",
173-
description: "Ganhamos um investimento significativo celebrado em Live, marcando um ano de sucesso com foco na educação e crescimento da comunidade.",
173+
month: t("timeline.2023.feb.month"),
174+
title: t("timeline.2023.feb.title"),
175+
description: t("timeline.2023.feb.desc"),
174176
videoId: "Jeo0p0D8ayQ",
175177
},
176178
{
177-
month: "Março",
178-
title: "Festa da Comunidade no Rio de Janeiro",
179-
description: "Realizamos o primeiro encontro presencial no Rio de Janeiro, promovendo momentos inesquecíveis!",
179+
month: t("timeline.2023.mar.month"),
180+
title: t("timeline.2023.mar.title"),
181+
description: t("timeline.2023.mar.desc"),
180182
image: timelineCommunityPhoto,
181183
},
182184
{
183-
month: "Junho",
184-
title: "Primeiro bootcamp presencial",
185-
description: "Primeiro bootcamp presencial em parceria com Ethereum Brasil, apoiando hackers a criar aplicações inovadoras com Solidity.",
185+
month: t("timeline.2023.jun.month"),
186+
title: t("timeline.2023.jun.title"),
187+
description: t("timeline.2023.jun.desc"),
186188
image: timelineDsc06607,
187189
},
188190
{
189-
month: "Agosto",
190-
title: "Ethereum Argentina",
191-
description: "Participação em eventos internacionais, incluindo o prestigiado Ethereum Argentina, destacando nossa presença global e engajamento.",
191+
month: t("timeline.2023.aug.month"),
192+
title: t("timeline.2023.aug.title"),
193+
description: t("timeline.2023.aug.desc"),
192194
videoId: "kOjqCf3vfhs",
193195
},
194196
{
195-
month: "Setembro",
196-
title: "Co-produção Hackathon Hyperdrive",
197-
description: "Contribuímos com um dos maiores hackathons globais da Solana, oferecendo workshops e contemplando nossa comunidade com premiações nacionais.",
197+
month: t("timeline.2023.sep.month"),
198+
title: t("timeline.2023.sep.title"),
199+
description: t("timeline.2023.sep.desc"),
198200
videoId: "kHg2EmVUARw",
199201
},
200202
],
@@ -203,34 +205,34 @@ const AboutTimeline = () => {
203205
year: "2024",
204206
events: [
205207
{
206-
month: "Janeiro",
207-
title: "Polkadot Academy",
208-
description: "Nosso time esteve presente no Polkadot Academy, fortalecendo laços com a comunidade global e absorvendo novos conhecimentos.",
208+
month: t("timeline.2024.jan.month"),
209+
title: t("timeline.2024.jan.title"),
210+
description: t("timeline.2024.jan.desc"),
209211
image: timelinePolkadot,
210212
},
211213
{
212-
month: "Abril",
213-
title: "Grant da Stellar",
214-
description: "Conquistamos um grant da Stellar, que impulsionou nossas iniciativas educacionais e de inovação, trazendo mais recursos para capacitar desenvolvedores.",
214+
month: t("timeline.2024.apr.month"),
215+
title: t("timeline.2024.apr.title"),
216+
description: t("timeline.2024.apr.desc"),
215217
videoId: "fGMI2m73Cn8",
216218
},
217219
{
218-
month: "Maio",
219-
title: "7.3 mil membros no Discord",
220-
description: "Seguimos crescendo e alcançamos a marca de 7.3 mil membros no Discord, consolidando nosso papel como referência em educação Web3 para a América Latina.",
220+
month: t("timeline.2024.may.month"),
221+
title: t("timeline.2024.may.title"),
222+
description: t("timeline.2024.may.desc"),
221223
image: timelineGraph2024,
222224
},
223225
{
224-
month: "Julho",
225-
title: "Lançamento do Build de Rust",
226-
description: "Com apoio do grant da Polkadot, lançamos o Build de Rust, expandindo nossa oferta de cursos técnicos e fortalecendo nossa rede de devs especializados.",
226+
month: t("timeline.2024.jul.month"),
227+
title: t("timeline.2024.jul.title"),
228+
description: t("timeline.2024.jul.desc"),
227229
videoId: "ROioE9Tlrmc",
228230
link: "https://www.w3d.community/build",
229231
},
230232
{
231-
month: "Setembro",
232-
title: "Hackathon NFT Brasil",
233-
description: "Participamos na co-produção do Hackathon NFT Brasil, apoiando projetos inovadores e oferecendo mentoria especializada, workshops e palestras.",
233+
month: t("timeline.2024.sep.month"),
234+
title: t("timeline.2024.sep.title"),
235+
description: t("timeline.2024.sep.desc"),
234236
image: timelineHackathonNft,
235237
},
236238
],
@@ -239,47 +241,45 @@ const AboutTimeline = () => {
239241
year: "2025",
240242
events: [
241243
{
242-
month: "Fevereiro",
243-
title: "Curso introdutório de Rust",
244-
description: "Criamos um curso introdutório gratuito de Rust no nosso canal do YouTube para aumentar a taxa de membros graduados e inscritos na Polkadot Academy (PBAx).",
244+
month: t("timeline.2025.feb.month"),
245+
title: t("timeline.2025.feb.title"),
246+
description: t("timeline.2025.feb.desc"),
245247
videoId: "18sCFMicV-4",
246248
},
247249
{
248-
month: "Março",
249-
title: "Digital Assets — Blockchain.Rio",
250-
description: "Participamos do evento Digital Assets parte do projeto Blockchain.Rio in the road, com um Talk sobre Substrate e Governança na Polkadot.",
250+
month: t("timeline.2025.mar.month"),
251+
title: t("timeline.2025.mar.title"),
252+
description: t("timeline.2025.mar.desc"),
251253
image: timelineDigitalAssets,
252254
},
253255
{
254-
month: "Maio",
255-
title: "Hackathon da TokenNation",
256-
description: "Co-produzimos o Hackathon da TokenNation, oferecendo toda estrutura de eventos online e suporte para os Hackers, além do time de mentores.",
256+
month: t("timeline.2025.may.month"),
257+
title: t("timeline.2025.may.title"),
258+
description: t("timeline.2025.may.desc"),
257259
image: timelineTokennation,
258260
},
259261
{
260-
month: "Junho",
261-
title: "Polkadot Cloud — Evento Presencial",
262-
description: "Realizamos um evento presencial sobre Polkadot Cloud e as inovações do ecossistema para criação de computação descentralizada.",
262+
month: t("timeline.2025.jun1.month"),
263+
title: t("timeline.2025.jun1.title"),
264+
description: t("timeline.2025.jun1.desc"),
263265
image: timelineFoundersHaus,
264266
},
265267
{
266-
month: "Junho",
267-
title: "Palestra na UFSC",
268-
description: "Palestra presencial sobre Computação Descentralizada na Polkadot Cloud, realizada na Universidade Federal de Santa Catarina.",
268+
month: t("timeline.2025.jun2.month"),
269+
title: t("timeline.2025.jun2.title"),
270+
description: t("timeline.2025.jun2.desc"),
269271
image: timelineUfsc,
270272
},
271273
{
272-
month: "Julho",
273-
title: "Hack the Block — BlockchainRio",
274-
description: "Colaboramos na co-produção do Hack the Block, Hackathon do Blockchain.Rio, oferecendo monitoria especializada e materiais de suporte educacional.",
274+
month: t("timeline.2025.jul.month"),
275+
title: t("timeline.2025.jul.title"),
276+
description: t("timeline.2025.jul.desc"),
275277
videoId: "bK9DA2BB7aM",
276278
},
277279
],
278280
},
279281
];
280282

281-
let eventCounter = 0;
282-
283283
return (
284284
<section ref={sectionRef} className="relative z-10 px-6 py-12">
285285
<div className="mx-auto max-w-5xl">
@@ -304,10 +304,9 @@ const AboutTimeline = () => {
304304
{timelineData.map((yearGroup) => (
305305
<div key={yearGroup.year} className="flex flex-col gap-12">
306306
<YearMarker year={yearGroup.year} />
307-
{yearGroup.events.map((event) => {
308-
const idx = eventCounter++;
309-
return <TimelineEventItem key={`${yearGroup.year}-${event.month}-${event.title}`} event={event} index={idx} />;
310-
})}
307+
{yearGroup.events.map((event) => (
308+
<TimelineEventItem key={`${yearGroup.year}-${event.month}-${event.title}`} event={event} />
309+
))}
311310
</div>
312311
))}
313312
</div>

src/components/Navbar.tsx

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useState } from "react";
2-
import { useNavigate } from "react-router-dom";
2+
import { useNavigate, useParams } from "react-router-dom";
33
import { motion, AnimatePresence } from "framer-motion";
44
import { Menu, X, ChevronDown, Calendar, FileText, BookOpen, BookMarked, Compass } from "lucide-react";
5-
import { useLanguage } from "@/contexts/LanguageContext";
5+
import { useLanguage, LANG_TO_URL } from "@/contexts/LanguageContext";
66
import web3devLogo from "@/assets/web3dev-logo.svg";
77

88
const Navbar = () => {
@@ -11,9 +11,12 @@ const Navbar = () => {
1111
const [langOpen, setLangOpen] = useState(false);
1212
const { lang, setLang, t } = useLanguage();
1313
const navigate = useNavigate();
14+
const { locale } = useParams<{ locale?: string }>();
15+
16+
const currentLocale = locale || "pt-BR";
1417

1518
const navLinks = [
16-
{ label: t("nav.about"), href: "/about", isRoute: true },
19+
{ label: t("nav.about"), href: `/${currentLocale}/about`, isRoute: true },
1720
{ label: t("nav.bootcamp"), href: "https://build.w3d.community/courses" },
1821
];
1922

@@ -27,17 +30,24 @@ const Navbar = () => {
2730
];
2831

2932
const langOptions = [
30-
{ code: "pt" as const, label: "🇧🇷 PT-BR" },
31-
{ code: "en" as const, label: "🇺🇸 EN-US" },
32-
{ code: "es" as const, label: "🇪🇸 ES" },
33+
{ code: "pt" as const, label: "🇧🇷 PT-BR", urlSegment: "pt-BR" },
34+
{ code: "en" as const, label: "🇺🇸 EN-US", urlSegment: "en-US" },
35+
{ code: "es" as const, label: "🇪🇸 ES", urlSegment: "es" },
3336
];
3437

3538
const currentLang = langOptions.find((l) => l.code === lang)!;
3639

40+
const handleLangChange = (option: typeof langOptions[0]) => {
41+
setLang(option.code);
42+
// Navigate to the same page but with the new locale
43+
const isAbout = window.location.pathname.includes("/about");
44+
navigate(`/${option.urlSegment}${isAbout ? "/about" : ""}`);
45+
};
46+
3747
return (
3848
<header className="fixed top-0 left-0 right-0 z-50 border-b border-border/30 bg-background/80 backdrop-blur-xl">
3949
<nav className="mx-auto flex max-w-7xl items-center justify-between px-6 py-4">
40-
<a href="/" className="flex items-center gap-2">
50+
<a href={`/${currentLocale}`} className="flex items-center gap-2">
4151
<img src={web3devLogo} alt="WEB3DEV" className="h-8 md:h-10" />
4252
</a>
4353

@@ -116,7 +126,7 @@ const Navbar = () => {
116126
{langOptions.map((option) => (
117127
<button
118128
key={option.code}
119-
onClick={() => { setLang(option.code); setLangOpen(false); }}
129+
onClick={() => { handleLangChange(option); setLangOpen(false); }}
120130
className={`block w-full rounded-md px-3 py-2 text-left text-sm transition-colors hover:bg-secondary hover:text-foreground ${
121131
lang === option.code ? "text-primary" : "text-muted-foreground"
122132
}`}
@@ -192,7 +202,7 @@ const Navbar = () => {
192202
{langOptions.map((option) => (
193203
<button
194204
key={option.code}
195-
onClick={() => { setLang(option.code); setMobileOpen(false); }}
205+
onClick={() => { handleLangChange(option); setMobileOpen(false); }}
196206
className={`text-sm font-body ${lang === option.code ? "text-primary" : "text-muted-foreground"}`}
197207
>
198208
{option.label}

0 commit comments

Comments
 (0)