intl-messageformat ICU plural and select: working versions (tested 2026-10-18)
Format ICU messages offline with intl-messageformat: plural with =0, select by gender, number and date formatting for en and fr, nested arguments and rich text tags driven by a formatter function.
passed7 passed · 0 failed2026-10-18 03:58 UTC- Status
- passed
- Last passed
- 2026-10-18 03:58 UTC
- Tests
- 7 / 7
- Language
- TypeScript
- Runtime
- node v22.23.3
pinned versions3
intl-messageformat@12.1.3@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false npm test
test environment
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 39095442401
The code
Exactly the files that passed. Your agent can pull them with get_recipe("intl-messageformat-icu-plural") (install), or see them on GitHub.
package.json
{
"name": "intl-messageformat-icu-plural",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"intl-messageformat": "12.1.3"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
src/messages.ts
import { IntlMessageFormat } from "intl-messageformat";
export const locales = ["en", "fr"] as const;
export type Locale = (typeof locales)[number];
export type MessageValues = Record<string, unknown>;
const overrideFormats = {
date: {
ymd: {
year: "numeric",
month: "2-digit",
day: "2-digit",
timeZone: "UTC",
},
},
};
export const catalog = {
en: {
attachments:
"{count, plural, =0 {No attachments} one {# attachment} other {# attachments}}",
reply:
"{gender, select, male {He replied} female {She replied} other {They replied}}",
price: "Total: {amount, number}",
due: "Due on {when, date, ymd}",
inbox:
"{gender, select, male {{count, plural, =0 {He has no messages} one {He has # message} other {He has # messages}}} female {{count, plural, =0 {She has no messages} one {She has # message} other {She has # messages}}} other {{count, plural, =0 {They have no messages} one {They have # message} other {They have # messages}}}}",
banner: "Read <b>{topic}</b> now",
},
fr: {
attachments:
"{count, plural, =0 {Aucune piece jointe} one {# piece jointe} other {# pieces jointes}}",
reply:
"{gender, select, male {Il a repondu} female {Elle a repondu} other {Iel a repondu}}",
price: "Total : {amount, number}",
due: "Echeance le {when, date, ymd}",
inbox:
"{gender, select, male {{count, plural, =0 {Pas de message pour lui} one {Il a # message} other {Il a # messages}}} female {{count, plural, =0 {Pas de message pour elle} one {Elle a # message} other {Elle a # messages}}} other {{count, plural, =0 {Pas de message} one {Iel a # message} other {Iel a # messages}}}}",
banner: "Lire <b>{topic}</b> maintenant",
},
} as const;
export type MessageId = keyof (typeof catalog)["en"];
export function formatMessage(
locale: Locale,
id: MessageId,
values: MessageValues = {},
): string {
const message = catalog[locale][id];
const formatter = new IntlMessageFormat(message, locale, overrideFormats, {
ignoreTag: false,
});
return formatter.format(values) as string;
}
test/messages.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { formatMessage } from "../src/messages.ts";
test("plural selects =0, one and other in en", () => {
assert.equal(formatMessage("en", "attachments", { count: 0 }), "No attachments");
assert.equal(formatMessage("en", "attachments", { count: 1 }), "1 attachment");
assert.equal(formatMessage("en", "attachments", { count: 5 }), "5 attachments");
});
test("plural applies French categories with =0 override", () => {
assert.equal(formatMessage("fr", "attachments", { count: 0 }), "Aucune piece jointe");
assert.equal(formatMessage("fr", "attachments", { count: 1 }), "1 piece jointe");
assert.equal(formatMessage("fr", "attachments", { count: 3 }), "3 pieces jointes");
});
test("select picks a branch by gender and falls back to other", () => {
assert.equal(formatMessage("en", "reply", { gender: "male" }), "He replied");
assert.equal(formatMessage("en", "reply", { gender: "female" }), "She replied");
assert.equal(formatMessage("en", "reply", { gender: "unknown" }), "They replied");
assert.equal(formatMessage("fr", "reply", { gender: "female" }), "Elle a repondu");
});
test("number formatting differs between locales", () => {
assert.equal(formatMessage("en", "price", { amount: 3.5 }), "Total: 3.5");
assert.equal(formatMessage("fr", "price", { amount: 3.5 }), "Total : 3,5");
});
test("date formatting uses locale order with UTC override", () => {
const when = new Date("2024-01-15T00:00:00.000Z");
assert.equal(formatMessage("en", "due", { when }), "Due on 01/15/2024");
assert.equal(formatMessage("fr", "due", { when }), "Echeance le 15/01/2024");
});
test("nested select and plural resolve inner arguments", () => {
assert.equal(
formatMessage("en", "inbox", { gender: "male", count: 3 }),
"He has 3 messages",
);
assert.equal(
formatMessage("en", "inbox", { gender: "female", count: 1 }),
"She has 1 message",
);
assert.equal(
formatMessage("en", "inbox", { gender: "unknown", count: 0 }),
"They have no messages",
);
assert.equal(
formatMessage("fr", "inbox", { gender: "female", count: 2 }),
"Elle a 2 messages",
);
});
test("rich text tags call the matching formatter function", () => {
const bold = (chunks: unknown) => `[${chunks}]`;
assert.equal(
formatMessage("en", "banner", { topic: "pricing", b: bold }),
"Read [pricing] now",
);
assert.equal(
formatMessage("fr", "banner", { topic: "tarifs", b: bold }),
"Lire [tarifs] maintenant",
);
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.