119 lines
3.4 KiB
TypeScript
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);
|
|
}
|