97 lines
2.3 KiB
TypeScript
97 lines
2.3 KiB
TypeScript
import { normalizeLocale, type SupportedLocale } from "./message-contract.js";
|
|
|
|
const FORMAT_FALLBACK = "—";
|
|
|
|
export type DateFormatStyle = "short" | "medium" | "long";
|
|
|
|
export function formatDate(
|
|
locale: SupportedLocale,
|
|
value: Date | number,
|
|
options: Readonly<{
|
|
dateStyle?: DateFormatStyle;
|
|
timeZone?: string;
|
|
}> = {},
|
|
): string {
|
|
try {
|
|
const date = value instanceof Date ? value : new Date(value);
|
|
if (Number.isNaN(date.getTime())) return FORMAT_FALLBACK;
|
|
return new Intl.DateTimeFormat(normalizeLocale(locale), {
|
|
dateStyle: options.dateStyle ?? "medium",
|
|
timeZone: options.timeZone ?? "UTC",
|
|
}).format(date);
|
|
} catch {
|
|
return FORMAT_FALLBACK;
|
|
}
|
|
}
|
|
|
|
export function formatNumber(
|
|
locale: SupportedLocale,
|
|
value: number,
|
|
options: Readonly<{
|
|
style?: "decimal" | "percent";
|
|
maximumFractionDigits?: number;
|
|
}> = {},
|
|
): string {
|
|
try {
|
|
if (!Number.isFinite(value)) return FORMAT_FALLBACK;
|
|
return new Intl.NumberFormat(normalizeLocale(locale), options).format(value);
|
|
} catch {
|
|
return FORMAT_FALLBACK;
|
|
}
|
|
}
|
|
|
|
export function formatRelativeTime(
|
|
locale: SupportedLocale,
|
|
value: number,
|
|
unit: Intl.RelativeTimeFormatUnit,
|
|
): string {
|
|
try {
|
|
if (!Number.isFinite(value)) return FORMAT_FALLBACK;
|
|
return new Intl.RelativeTimeFormat(normalizeLocale(locale), {
|
|
numeric: "auto",
|
|
}).format(value, unit);
|
|
} catch {
|
|
return FORMAT_FALLBACK;
|
|
}
|
|
}
|
|
|
|
export function formatList(
|
|
locale: SupportedLocale,
|
|
values: readonly string[],
|
|
): string {
|
|
try {
|
|
return new Intl.ListFormat(normalizeLocale(locale), {
|
|
style: "long",
|
|
type: "conjunction",
|
|
}).format(values);
|
|
} catch {
|
|
return values.join(", ");
|
|
}
|
|
}
|
|
|
|
export function selectPlural(
|
|
locale: SupportedLocale,
|
|
value: number,
|
|
choices: Readonly<
|
|
Partial<Record<Intl.LDMLPluralRule, string>> & { other: string }
|
|
>,
|
|
): string {
|
|
try {
|
|
if (!Number.isFinite(value)) return choices.other;
|
|
return choices[
|
|
new Intl.PluralRules(normalizeLocale(locale)).select(value)
|
|
] ?? choices.other;
|
|
} catch {
|
|
return choices.other;
|
|
}
|
|
}
|
|
|
|
export function selectMessage(
|
|
value: string,
|
|
choices: Readonly<Record<string, string> & { other: string }>,
|
|
): string {
|
|
return choices[value] ?? choices.other;
|
|
}
|
|
|
|
export { FORMAT_FALLBACK };
|