Waseel: driver app, dispatch, POI suggestions, map fixes
This commit is contained in:
+130
@@ -0,0 +1,130 @@
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
|
||||
import { useSettingsStore, type Lang } from "@/lib/settings";
|
||||
import { ar } from "@/lib/translations/ar";
|
||||
import { en } from "@/lib/translations/en";
|
||||
import { fr } from "@/lib/translations/fr";
|
||||
|
||||
export type Vars = Record<string, string | number>;
|
||||
|
||||
/**
|
||||
* Lightweight i18n. No external dependency: three languages, dot-path keys,
|
||||
* `{var}` interpolation, and a `one`/`other`/`zero` plural convention that
|
||||
* covers the few counts this app shows (minutes, seats).
|
||||
*/
|
||||
const DICTS: Record<Lang, Record<string, unknown>> = { en, ar, fr };
|
||||
|
||||
const interpolate = (text: string, vars?: Vars): string =>
|
||||
vars
|
||||
? text.replace(/\{(\w+)\}/g, (_, key: string) => String(vars[key] ?? ""))
|
||||
: text;
|
||||
|
||||
const readPath = (dict: Record<string, unknown>, path: string): unknown =>
|
||||
path.split(".").reduce<unknown>(
|
||||
(acc, segment) =>
|
||||
acc && typeof acc === "object"
|
||||
? (acc as Record<string, unknown>)[segment]
|
||||
: undefined,
|
||||
dict,
|
||||
);
|
||||
|
||||
const translate = (
|
||||
lang: Lang,
|
||||
key: string,
|
||||
vars?: Vars,
|
||||
count?: number,
|
||||
): string => {
|
||||
const dict = DICTS[lang] ?? en;
|
||||
|
||||
if (count !== undefined) {
|
||||
const pluralSuffix =
|
||||
count === 0 ? "zero" : count === 1 ? "one" : "other";
|
||||
const pluralValue = readPath(dict, `${key}.${pluralSuffix}`);
|
||||
|
||||
if (typeof pluralValue === "string") {
|
||||
return interpolate(pluralValue, { n: count, ...vars });
|
||||
}
|
||||
}
|
||||
|
||||
const value = readPath(dict, key);
|
||||
|
||||
if (typeof value === "string") return interpolate(value, vars);
|
||||
|
||||
// Fall back to English, then to the key itself, so a missing translation
|
||||
// never renders an empty string.
|
||||
if (lang !== "en") {
|
||||
const fallback = readPath(en, key);
|
||||
if (typeof fallback === "string") return interpolate(fallback, vars);
|
||||
}
|
||||
|
||||
return key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Module-level translator for non-React helpers (`lib/utils.ts`,
|
||||
* `lib/pricing.ts`) that can't call `useT`. The I18nProvider sets this on
|
||||
* mount and whenever the language changes; until then it defaults to English.
|
||||
*/
|
||||
let activeLang: Lang = "en";
|
||||
|
||||
export const setTranslator = (lang: Lang) => {
|
||||
activeLang = lang;
|
||||
};
|
||||
|
||||
export const tr = (key: string, vars?: Vars, count?: number): string =>
|
||||
translate(activeLang, key, vars, count);
|
||||
|
||||
type I18nContextValue = {
|
||||
lang: Lang;
|
||||
isRTL: boolean;
|
||||
t: (key: string, vars?: Vars, count?: number) => string;
|
||||
};
|
||||
|
||||
const I18nContext = createContext<I18nContextValue | null>(null);
|
||||
|
||||
export const I18nProvider = ({ children }: { children: ReactNode }) => {
|
||||
const lang = useSettingsStore((state) => state.lang);
|
||||
|
||||
useEffect(() => {
|
||||
setTranslator(lang);
|
||||
}, [lang]);
|
||||
|
||||
const value = useMemo<I18nContextValue>(
|
||||
() => ({
|
||||
lang,
|
||||
isRTL: lang === "ar",
|
||||
t: (key, vars, count) => translate(lang, key, vars, count),
|
||||
}),
|
||||
[lang],
|
||||
);
|
||||
|
||||
return (
|
||||
<I18nContext.Provider value={value}>{children}</I18nContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useT = (): I18nContextValue["t"] => {
|
||||
const context = useContext(I18nContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useT must be used within I18nProvider.");
|
||||
}
|
||||
|
||||
return context.t;
|
||||
};
|
||||
|
||||
export const useI18n = (): I18nContextValue => {
|
||||
const context = useContext(I18nContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useI18n must be used within I18nProvider.");
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
Reference in New Issue
Block a user