96 lines
2.2 KiB
TypeScript
96 lines
2.2 KiB
TypeScript
'use client';
|
|
|
|
import {
|
|
createContext,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
type ReactNode,
|
|
} from 'react';
|
|
import { DEFAULT_LOCALE, dict, type Dict, type Locale } from './dictionaries';
|
|
|
|
type Direction = 'rtl' | 'ltr';
|
|
|
|
type Ctx = {
|
|
locale: Locale;
|
|
dir: Direction;
|
|
t: Dict;
|
|
setLocale: (l: Locale) => void;
|
|
toggle: () => void;
|
|
};
|
|
|
|
const LocaleContext = createContext<Ctx | null>(null);
|
|
|
|
const STORAGE_KEY = 'sa.locale';
|
|
|
|
function dirFor(locale: Locale): Direction {
|
|
return locale === 'fa' ? 'rtl' : 'ltr';
|
|
}
|
|
|
|
export function LocaleProvider({
|
|
initialLocale,
|
|
content,
|
|
children,
|
|
}: {
|
|
initialLocale?: Locale;
|
|
/**
|
|
* Server-resolved content (dict defaults merged with admin overrides).
|
|
* When omitted we fall back to the in-code dictionary, so the provider keeps
|
|
* working in isolation (tests, storybook, etc.).
|
|
*/
|
|
content?: { fa: Dict; en: Dict };
|
|
children: ReactNode;
|
|
}) {
|
|
const [locale, setLocaleState] = useState<Locale>(initialLocale ?? DEFAULT_LOCALE);
|
|
const source = content ?? (dict as unknown as { fa: Dict; en: Dict });
|
|
|
|
// Hydrate from localStorage on the client.
|
|
useEffect(() => {
|
|
try {
|
|
const saved = window.localStorage.getItem(STORAGE_KEY) as Locale | null;
|
|
if (saved === 'fa' || saved === 'en') {
|
|
setLocaleState(saved);
|
|
}
|
|
} catch {
|
|
/* noop */
|
|
}
|
|
}, []);
|
|
|
|
// Reflect locale + direction on the html element without a reload.
|
|
useEffect(() => {
|
|
const html = document.documentElement;
|
|
html.lang = locale;
|
|
html.dir = dirFor(locale);
|
|
html.dataset.locale = locale;
|
|
}, [locale]);
|
|
|
|
const setLocale = (l: Locale) => {
|
|
setLocaleState(l);
|
|
try {
|
|
window.localStorage.setItem(STORAGE_KEY, l);
|
|
} catch {
|
|
/* noop */
|
|
}
|
|
};
|
|
|
|
const value = useMemo<Ctx>(
|
|
() => ({
|
|
locale,
|
|
dir: dirFor(locale),
|
|
t: source[locale],
|
|
setLocale,
|
|
toggle: () => setLocale(locale === 'fa' ? 'en' : 'fa'),
|
|
}),
|
|
[locale, source],
|
|
);
|
|
|
|
return <LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>;
|
|
}
|
|
|
|
export function useLocale() {
|
|
const ctx = useContext(LocaleContext);
|
|
if (!ctx) throw new Error('useLocale must be used inside <LocaleProvider>');
|
|
return ctx;
|
|
}
|