Session 12: i18n (10 languages, cookie-based), Africa tier .99, locale switcher, RTL Arabic (1305 tests)

This commit is contained in:
Kev
2026-06-10 22:24:40 -04:00
parent e5c45ecc8e
commit d957dee17b
27 changed files with 1834 additions and 29 deletions
+49
View File
@@ -0,0 +1,49 @@
'use client';
import { createContext, useContext, useMemo, ReactNode } from 'react';
import { Locale, DEFAULT_LOCALE, isLocale, LOCALE_META } from '@/lib/locales';
import { getTranslations, TFunction } from '@/lib/i18n';
/**
* Client-side locale context (Session 12).
*
* The root layout (server component) resolves the locale from the
* request header and passes it as a prop to `<LocaleProvider>`. From
* there every client component can `useT()` without prop-drilling.
*
* Memoized: the `t` function is stable per render of the provider,
* so consumers don't re-render on every parent render.
*/
interface LocaleContextValue {
locale: Locale;
dir: 'ltr' | 'rtl';
t: TFunction;
}
const LocaleContext = createContext<LocaleContextValue | null>(null);
export function LocaleProvider({ locale, children }: { locale: string; children: ReactNode }) {
const value = useMemo<LocaleContextValue>(() => {
const resolved: Locale = isLocale(locale) ? locale : DEFAULT_LOCALE;
const bundle = getTranslations(resolved);
return { locale: resolved, dir: LOCALE_META[resolved].dir, t: bundle.t };
}, [locale]);
return <LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>;
}
export function useT(): TFunction {
const ctx = useContext(LocaleContext);
if (!ctx) {
// Fall back to English silently — better than a crash if some
// component renders outside the provider (test envs, storybook).
return getTranslations('en').t;
}
return ctx.t;
}
export function useLocale(): { locale: Locale; dir: 'ltr' | 'rtl' } {
const ctx = useContext(LocaleContext);
if (!ctx) return { locale: DEFAULT_LOCALE, dir: 'ltr' };
return { locale: ctx.locale, dir: ctx.dir };
}