ts-intl
Chapters:ICU Syntax & Rich Text Rendering

ICU Syntax & Rich Text Rendering

ts-intl supports standard ICU MessageFormat 1.x syntax.

Plural Support (Pluralization)

Structured Plural Objects

Supports nested object forms following CLDR standard plural categories (zero, one, two, few, many, other):

const messages = {
  cart: {
    zero: "Your cart is empty",
    one: "1 item in your cart",
    other: "{count} items in your cart",
  },
} as const;

const t = getTranslations("en-US");

t("cart", { count: 0 }); // "Your cart is empty"
t("cart", { count: 1 }); // "1 item in your cart"
t("cart", { count: 8 }); // "8 items in your cart"

Note: In the CLDR specification, English only defines the one and other plural categories (ts-intl additionally provides built-in support for zero when count === 0). If you need differentiated text for specific numbers, it is recommended to use the inline ICU plural syntax below (such as =0).

Inline ICU plural Syntax

const messages = {
  inbox:
    "You have {count, plural, =0 {no unread messages} one {# message} other {# messages}}.",
} as const;

const t = getTranslations("en-US");

t("inbox", { count: 0 }); // "You have no unread messages."
t("inbox", { count: 1 }); // "You have 1 message."
t("inbox", { count: 12 }); // "You have 12 messages."

Ordinal Formatting (selectordinal)

Used to handle ordinal expressions (exact match = takes precedence over category rules):

const messages = {
  podium:
    "You finished {rank, selectordinal, =1 {first} =2 {second} =3 {third} other {#th}}!",
} as const;

const t = getTranslations("en-US");

t("podium", { rank: 1 }); // "You finished first!"
t("podium", { rank: 4 }); // "You finished 4th!"

Conditional Branch Selection (select)

Matches different text based on status enums or category values:

const messages = {
  roleBadge:
    "{role, select, admin {Administrator} moderator {Moderator} other {Member}}",
} as const;

const t = getTranslations("en-US");

t("roleBadge", { role: "admin" }); // "Administrator"
t("roleBadge", { role: "guest" }); // "Member"

General Rich Text Interpolation t.rich()

Maps text to frontend components or DOM nodes via XML-like tags, avoiding the XSS risks of using dangerouslySetInnerHTML:

const messages = {
  notice:
    "Please review our <terms>Terms of Service</terms> and accept the <privacy>Privacy Policy</privacy> before submitting.",
} as const;

const t = getTranslations("en-US");

// Map directly to components in React / Vue
const elements = t.rich("notice", {
  terms: (children) => <a href="/terms" className="underline">{children}</a>,
  privacy: (children) => <a href="/privacy" className="underline">{children}</a>,
});

HTML String Markup Rendering t.markup()

Used to generate strings containing safe HTML tags:

const html = t.markup("notice", {
  terms: (c) => `<a href="/terms">${c}</a>`,
  privacy: (c) => `<a href="/privacy">${c}</a>`,
});