/**
* ICU MessageFormat formatting via FormatJS (`intl-messageformat`).
*
* Supports interpolation, cardinal/ordinal plurals, `select` / `selectordinal`,
* and rich-text tags (`…` with function values).
*/
import IntlMessageFormat from "intl-messageformat";
import type { MessageValues } from "./types.ts";
const cache = new Map();
/**
* Clear the compiled-message cache (tests only).
*/
export function clearMessageFormatCache(): void {
cache.clear();
}
/**
* Format an ICU message for a locale.
*
* @param message - ICU MessageFormat source
* @param locale - BCP 47 locale tag
* @param values - Interpolation / plural / select / rich-text values
*/
export function formatMessage(message: string, locale: string, values?: MessageValues): string {
const cacheKey = `${locale}\0${message}`;
let formatter = cache.get(cacheKey);
if (!formatter) {
try {
formatter = new IntlMessageFormat(message, locale);
} catch {
// Malformed ICU — treat as a plain template.
return message;
}
cache.set(cacheKey, formatter);
}
try {
const result = formatter.format(values as Record | undefined);
return partsToString(result);
} catch {
// Missing args — leave the source for callers that fall back.
return message;
}
}
/**
* Collapse FormatJS rich-text / string output to a single string.
*
* @param result - `format()` return value
*/
function partsToString(result: unknown): string {
if (typeof result === "string") return result;
if (typeof result === "number" || typeof result === "boolean") return String(result);
if (Array.isArray(result)) {
return result
.map((part) => {
if (typeof part === "string" || typeof part === "number") return String(part);
return "";
})
.join("");
}
if (result == null) return "";
return String(result);
}