feat: add internationalization message platform
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user