ProvenPin Install

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

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.