Files
tech-log-frontend/src/presentation/i18n/locale-provider.tsx
T

119 lines
3.4 KiB
TypeScript

import {
createContext,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import {
formatDate,
formatList,
formatNumber,
formatRelativeTime,
selectMessage,
selectPlural,
} from "./formatters.ts";
import {
formatMessage,
localeDirection,
normalizeLocale,
resolveMessage,
type MessageArguments,
type SupportedLocale,
type TextDirection,
} from "./message-contract.ts";
import type { MessageKey } from "./catalog.ts";
type LocaleContextValue = Readonly<{
locale: SupportedLocale;
direction: TextDirection;
setLocale(locale: SupportedLocale): void;
message<Key extends MessageKey>(
key: Key,
...args: MessageArguments<Key>
): string;
resolve(key: string): string;
date(
value: Date | number,
options?: Parameters<typeof formatDate>[2],
): string;
number(value: number, options?: Parameters<typeof formatNumber>[2]): string;
relativeTime(
value: number,
unit: Intl.RelativeTimeFormatUnit,
): string;
list(values: readonly string[]): string;
plural(
value: number,
choices: Parameters<typeof selectPlural>[2],
): string;
select(
value: string,
choices: Readonly<Record<string, string> & { other: string }>,
): string;
}>;
const DEFAULT_LOCALE = "ko-KR" as const;
const DEFAULT_CONTEXT: LocaleContextValue = Object.freeze({
locale: DEFAULT_LOCALE,
direction: "ltr",
setLocale() {},
message: (key, ...args) => formatMessage(DEFAULT_LOCALE, key, ...args),
resolve: (key) => resolveMessage(DEFAULT_LOCALE, key),
date: (value, options) => formatDate(DEFAULT_LOCALE, value, options),
number: (value, options) => formatNumber(DEFAULT_LOCALE, value, options),
relativeTime: (value, unit) =>
formatRelativeTime(DEFAULT_LOCALE, value, unit),
list: (values) => formatList(DEFAULT_LOCALE, values),
plural: (value, choices) => selectPlural(DEFAULT_LOCALE, value, choices),
select: (value, choices) => selectMessage(value, choices),
});
const LocaleContext = createContext<LocaleContextValue>(DEFAULT_CONTEXT);
export function LocaleProvider({
children,
initialLocale = "ko-KR",
}: Readonly<{
children: React.ReactNode;
initialLocale?: SupportedLocale;
}>) {
const [locale, setLocaleState] = useState<SupportedLocale>(() =>
normalizeLocale(initialLocale),
);
const direction = localeDirection(locale);
useEffect(() => {
document.documentElement.lang = locale;
document.documentElement.dir = direction;
}, [direction, locale]);
const value = useMemo<LocaleContextValue>(
() => ({
locale,
direction,
setLocale: setLocaleState,
message: (key, ...args) => formatMessage(locale, key, ...args),
resolve: (key) => resolveMessage(locale, key),
date: (dateValue, options) => formatDate(locale, dateValue, options),
number: (numberValue, options) =>
formatNumber(locale, numberValue, options),
relativeTime: (relativeValue, unit) =>
formatRelativeTime(locale, relativeValue, unit),
list: (values) => formatList(locale, values),
plural: (pluralValue, choices) =>
selectPlural(locale, pluralValue, choices),
select: (selectValue, choices) => selectMessage(selectValue, choices),
}),
[direction, locale],
);
return (
<LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>
);
}
export function useLocale(): LocaleContextValue {
return useContext(LocaleContext);
}