Skip to content

Commit f688549

Browse files
authored
Merge pull request #300 from zigzagdev/feat/locale-provider
feat: add LocaleProvider with URL synced locale state
2 parents aa248ea + db43626 commit f688549

3 files changed

Lines changed: 70 additions & 8 deletions

File tree

client/src/app/routes/AppRoutes.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@ import TopPageContainer from "@features/top/containers/top-page-container.tsx";
33
import { WorldHeritageDetailContainer } from "@features/top/containers/world-heritage-detail-container.tsx";
44
import { SearchHeritageResultsContainer } from "@features/search/containers/search-heritage-result-container.tsx";
55
import { BreadcrumbProvider } from "@features/breadcrumbs/BreadCrumbProvider.tsx";
6+
import { LocaleProvider } from "@shared/locale/LocaleProvider.tsx";
67

78
export function AppRoutes() {
89
return (
9-
<BreadcrumbProvider>
10-
<Routes>
11-
<Route path="/heritages" element={<TopPageContainer />} />
12-
<Route path="/heritages/results" element={<SearchHeritageResultsContainer />} />
13-
<Route path="/heritages/:id" element={<WorldHeritageDetailContainer />} />
14-
<Route path="*" element={<Navigate to="/heritages" replace />} />
15-
</Routes>
16-
</BreadcrumbProvider>
10+
<LocaleProvider>
11+
<BreadcrumbProvider>
12+
<Routes>
13+
<Route path="/heritages" element={<TopPageContainer />} />
14+
<Route path="/heritages/results" element={<SearchHeritageResultsContainer />} />
15+
<Route path="/heritages/:id" element={<WorldHeritageDetailContainer />} />
16+
<Route path="*" element={<Navigate to="/heritages" replace />} />
17+
</Routes>
18+
</BreadcrumbProvider>
19+
</LocaleProvider>
1720
);
1821
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { useContext } from "react";
2+
import LocaleContext from "./LocaleProvider.tsx";
3+
4+
export const useLocale = () => {
5+
const context = useContext(LocaleContext);
6+
if (!context) throw new Error("useLocale must be used within LocaleProvider");
7+
return context;
8+
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React, { createContext, useCallback, useMemo } from "react";
2+
import type { ReactNode } from "react";
3+
import { useSearchParams } from "react-router-dom";
4+
import { LOCALES, type Locale } from "../../../domain/criteria.ts";
5+
6+
type LocaleContextType = {
7+
locale: Locale;
8+
setLocale: (locale: Locale) => void;
9+
toggleLocale: () => void;
10+
};
11+
12+
const LocaleContext = createContext<LocaleContextType | undefined>(undefined);
13+
14+
const isLocale = (value: string): value is Locale => LOCALES.some((l) => l === value);
15+
16+
const resolveLocale = (raw: string | null): Locale => {
17+
if (!raw) return "en";
18+
return isLocale(raw) ? raw : "en";
19+
};
20+
21+
export const LocaleProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
22+
const [searchParams, setSearchParams] = useSearchParams();
23+
const locale = useMemo(() => resolveLocale(searchParams.get("lang")), [searchParams]);
24+
25+
const setLocale = useCallback(
26+
(next: Locale) => {
27+
setSearchParams(
28+
(prev) => {
29+
const updated = new URLSearchParams(prev);
30+
updated.set("lang", next);
31+
return updated;
32+
},
33+
{ replace: false },
34+
);
35+
},
36+
[setSearchParams],
37+
);
38+
39+
const toggleLocale = useCallback(() => {
40+
setLocale(locale === "ja" ? "en" : "ja");
41+
}, [locale, setLocale]);
42+
43+
const value = useMemo(
44+
() => ({ locale, setLocale, toggleLocale }),
45+
[locale, setLocale, toggleLocale],
46+
);
47+
48+
return <LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>;
49+
};
50+
51+
export default LocaleContext;

0 commit comments

Comments
 (0)