import { describe, expect, test } from "bun:test";
import { clearMessageFormatCache, formatMessage } from "./format.ts";
import {
clearMessageCatalogs,
defineLocale,
defineMessages,
flattenMessages,
getMessageCatalogs,
matchConfiguredLocale,
translate,
} from "./messages.ts";
import type { FlattenKeys, MessagesFor } from "./types.ts";
describe("flattenMessages", () => {
test("flattens nested trees with dot keys", () => {
expect(
flattenMessages({
errors: { notFound: "Missing" },
hello: "Hi",
}),
).toEqual({
"errors.notFound": "Missing",
hello: "Hi",
});
});
});
describe("defineLocale / getMessageCatalogs", () => {
test("registers overlays that win over built-ins", () => {
clearMessageCatalogs();
defineLocale("en", { greeting: "Hello, {name}" });
defineLocale("ar", { greeting: "مرحباً، {name}" });
const catalogs = getMessageCatalogs();
expect(catalogs.en?.greeting).toBe("Hello, {name}");
expect(catalogs.ar?.greeting).toBe("مرحباً، {name}");
// Built-ins remain available under the same locale.
expect(catalogs.en?.["errors.Unauthorized"]).toBe("Authentication required.");
defineLocale("en", { greeting: "Hi, {name}" });
expect(getMessageCatalogs().en?.greeting).toBe("Hi, {name}");
clearMessageCatalogs();
});
});
describe("formatMessage — ICU", () => {
test("interpolates simple placeholders", () => {
clearMessageFormatCache();
expect(formatMessage("Hello, {name}", "en", { name: "Ada" })).toBe("Hello, Ada");
});
test("cardinal plurals", () => {
clearMessageFormatCache();
const msg = "{count, plural, =0 {no items} one {# item} other {# items}}";
expect(formatMessage(msg, "en", { count: 0 })).toBe("no items");
expect(formatMessage(msg, "en", { count: 1 })).toBe("1 item");
expect(formatMessage(msg, "en", { count: 5 })).toBe("5 items");
});
test("ordinal plurals via selectordinal", () => {
clearMessageFormatCache();
const msg = "You finished {place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}!";
expect(formatMessage(msg, "en", { place: 1 })).toBe("You finished 1st!");
expect(formatMessage(msg, "en", { place: 2 })).toBe("You finished 2nd!");
expect(formatMessage(msg, "en", { place: 3 })).toBe("You finished 3rd!");
expect(formatMessage(msg, "en", { place: 11 })).toBe("You finished 11th!");
});
test("enum select", () => {
clearMessageFormatCache();
const msg = "{status, select, online {Online} offline {Offline} other {Unknown}}";
expect(formatMessage(msg, "en", { status: "online" })).toBe("Online");
expect(formatMessage(msg, "en", { status: "offline" })).toBe("Offline");
expect(formatMessage(msg, "en", { status: "away" })).toBe("Unknown");
});
test("rich text tags via function values", () => {
clearMessageFormatCache();
const msg = "Read the docs.";
expect(
formatMessage(msg, "en", {
docs: (chunks) => `[${chunks.join("")}]`,
}),
).toBe("Read [the docs].");
});
test("Arabic plural sample", () => {
clearMessageFormatCache();
const msg =
"{count, plural, zero {لا عناصر} one {عنصر واحد} two {عنصران} few {# عناصر} many {# عنصراً} other {# عنصر}}";
expect(formatMessage(msg, "ar", { count: 0 })).toBe("لا عناصر");
expect(formatMessage(msg, "ar", { count: 1 })).toBe("عنصر واحد");
expect(formatMessage(msg, "ar", { count: 2 })).toBe("عنصران");
});
});
describe("translate", () => {
const catalogs = {
en: {
"errors.notFound": "Not found",
greeting: "Hello, {name}",
items: "{count, plural, one {# item} other {# items}}",
},
ar: {
greeting: "مرحباً، {name}",
},
};
test("uses locale, then default, then key", () => {
expect(
translate({
locale: "ar",
defaultLocale: "en",
catalogs,
key: "greeting",
values: { name: "Ada" },
}),
).toBe("مرحباً، Ada");
expect(
translate({
locale: "ar",
defaultLocale: "en",
catalogs,
key: "errors.notFound",
}),
).toBe("Not found");
expect(
translate({
locale: "en",
defaultLocale: "en",
catalogs,
key: "items",
values: { count: 3 },
}),
).toBe("3 items");
expect(
translate({
locale: "ar",
defaultLocale: "en",
catalogs,
key: "missing",
values: { x: 1 },
}),
).toBe('missing:{"x":1}');
});
});
describe("matchConfiguredLocale", () => {
test("matches exact, base tag, then default", () => {
const locales = ["en", "ar"] as const;
expect(matchConfiguredLocale("ar", locales, "en")).toBe("ar");
expect(matchConfiguredLocale("ar-SA", locales, "en")).toBe("ar");
expect(matchConfiguredLocale("fr", locales, "en")).toBe("en");
expect(matchConfiguredLocale(undefined, locales, "en")).toBe("en");
});
});
describe("type helpers (compile-time shapes)", () => {
test("defineMessages preserves tree; MessagesFor aligns locales", () => {
const en = defineMessages({
errors: { notFound: "Not found" },
greeting: "Hello, {name}",
});
const ar = {
errors: { notFound: "غير موجود" },
greeting: "مرحباً، {name}",
} satisfies MessagesFor;
type Keys = FlattenKeys;
const key: Keys = "errors.notFound";
expect(en.errors.notFound).toBe("Not found");
expect(ar.greeting).toContain("{name}");
expect(key).toBe("errors.notFound");
});
});