Files
BarevClub/packages/web/src/i18n.tsx
T
2026-10-25 22:55:29 +04:00

59 lines
1.9 KiB
TypeScript

import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
import { createT, formatAmd, formatDateTime, isLocale, type Locale, type TFunction } from '@barev/i18n';
import { reportClientError } from './telegram.ts';
interface I18nValue {
locale: Locale;
t: TFunction;
setLocale: (l: Locale) => void;
amd: (n: number) => string;
dt: (d: Date, opts?: Intl.DateTimeFormatOptions) => string;
}
const Ctx = createContext<I18nValue | null>(null);
export function pickLocale(...candidates: (string | null | undefined)[]): Locale {
for (const c of candidates) {
const base = c?.split('-')[0]?.toLowerCase();
if (isLocale(base)) return base;
}
return 'ru';
}
export function I18nProvider({ initial, children }: { initial: Locale; children: ReactNode }) {
const [locale, setLocale] = useState<Locale>(initial);
useEffect(() => setLocale(initial), [initial]);
useEffect(() => {
document.documentElement.lang = locale;
}, [locale]);
const value = useMemo<I18nValue>(
() => ({
locale,
t: createT(locale),
setLocale,
amd: (n) => formatAmd(locale, n),
dt: (d, opts) => formatDateTime(locale, d, opts),
}),
[locale],
);
return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
}
export function useI18n(): I18nValue {
const v = useContext(Ctx);
if (!v) throw new Error('I18nProvider missing');
return v;
}
/** Текст ошибки API: errors.<код бизнес-ошибки> или общий */
export function errorText(t: TFunction, err: unknown): string {
reportClientError(err, 'errorText');
const e = err as { data?: { domainCode?: string | null; code?: string }; message?: string } | null;
const code = e?.data?.domainCode ?? e?.message;
if (code && /^[A-Z0-9_]+$/.test(code)) {
const s = t(`errors.${code}`);
if (s !== `errors.${code}`) return s;
}
return t('errors.generic');
}