import { describe, expect, it } from "vitest"; import { catalogKeys, FORMAT_FALLBACK, formatDate, formatList, formatMessage, formatNumber, formatRelativeTime, localeDirection, messagePlaceholders, normalizeLocale, resolveMessage, selectMessage, selectPlural, } from "../../src/presentation/i18n/index.js"; import { EN_MESSAGES, KO_MESSAGES, MESSAGE_CATALOGS, } from "../../src/presentation/i18n/catalog.js"; describe("internationalization message contract", () => { it("keeps every registered catalog and interpolation contract in parity", () => { expect(catalogKeys("en-US")).toEqual(catalogKeys("ko-KR")); expect(catalogKeys("ar-EG")).toEqual(catalogKeys("ko-KR")); for (const key of catalogKeys("ko-KR")) { expect(messagePlaceholders(EN_MESSAGES[key])).toEqual( messagePlaceholders(KO_MESSAGES[key]), ); } expect(MESSAGE_CATALOGS["ar-EG"]).toBe(EN_MESSAGES); }); it("formats typed parameters and expands the pseudo locale", () => { expect( formatMessage("en-US", "route.documentTitle", { title: "Settings", appName: "Skeleton", }), ).toBe("Settings · Skeleton"); const pseudo = formatMessage("en-XA", "action.closeNamed", { title: "Account", }); expect(pseudo).toMatch(/^[.+ ···]$/); expect(pseudo.length).toBeGreaterThan("Close Account".length); }); it("falls back safely for unknown locale, key and missing interpolation", () => { expect(normalizeLocale("fr-FR")).toBe("ko-KR"); expect(resolveMessage("fr-FR", "action.retry")).toBe("다시 시도"); expect(resolveMessage("en-US", "action.login")).toBe("Sign in"); expect(resolveMessage("en-US", "backend.stack.trace")).toBe( "요청한 문구를 표시할 수 없습니다.", ); expect(resolveMessage("en-US", "action.closeNamed")).toBe( "요청한 문구를 표시할 수 없습니다.", ); expect(resolveMessage("en-US", "backend.stack.trace")).not.toContain( "backend.stack.trace", ); }); it("defines direction without inferring it in components", () => { expect(localeDirection("ko-KR")).toBe("ltr"); expect(localeDirection("en-XA")).toBe("ltr"); expect(localeDirection("ar-EG")).toBe("rtl"); }); }); describe("internationalization formatter contract", () => { const instant = new Date("2026-07-26T23:30:00.000Z"); it("uses an explicit UTC default and deterministic locale formatting", () => { expect(formatDate("en-US", instant, { dateStyle: "short" })).toBe( "7/26/26", ); expect( formatDate("en-US", instant, { dateStyle: "short", timeZone: "Asia/Seoul", }), ).toBe("7/27/26"); expect(formatNumber("en-US", 1234.5)).toBe("1,234.5"); expect(formatRelativeTime("en-US", -1, "day")).toBe("yesterday"); expect(formatList("en-US", ["one", "two", "three"])).toBe( "one, two, and three", ); expect(selectPlural("en-US", 1, { one: "item", other: "items" })).toBe( "item", ); expect( selectPlural("en-US", 3, { one: "item", other: "items" }), ).toBe("items"); expect( selectMessage("pending", { ready: "Ready", pending: "Pending", other: "Unknown", }), ).toBe("Pending"); }); it("does not throw or expose invalid formatter input", () => { expect(formatDate("en-US", Number.NaN)).toBe(FORMAT_FALLBACK); expect(formatNumber("en-US", Number.POSITIVE_INFINITY)).toBe( FORMAT_FALLBACK, ); expect( formatDate("en-US", instant, { timeZone: "not/a-time-zone" }), ).toBe(FORMAT_FALLBACK); }); });