ts-intl
チャプター:ICU 構文とリッチテキスト描画

ICU 構文とリッチテキスト描画

ts-intl は ICU MessageFormat 1.x の標準構文をサポートします。

複数形サポート (Pluralization)

構造化複数形オブジェクト

CLDR 標準の複数形カテゴリ(zero、one、two、few、many、other)に準拠したネストオブジェクト形式をサポートします:

const messages = {
  cart: {
    zero: "カートは空です",
    other: "カートに合計 {count} 件の商品があります",
  },
} as const;

const t = getTranslations("ja-JP");

t("cart", { count: 0 }); // "カートは空です"
t("cart", { count: 1 }); // "カートに合計 1 件の商品があります"
t("cart", { count: 5 }); // "カートに合計 5 件の商品があります"

説明:CLDR 仕様において、日本語には other 複数形カテゴリのみが存在します(ts-intl では count === 0 の場合に zero も追加サポートしています)。特定数値(例:1)に応じた文言の出し分けが必要な場合は、下記のテンプレート内埋め込み ICU plural 構文(=1 など)の利用を推奨します。

テンプレート内 ICU plural 構文

const messages = {
  inbox:
    "{count, plural, =0 {未読メッセージはありません} other {未読メッセージが # 件あります}}。",
} as const;

const t = getTranslations("ja-JP");

t("inbox", { count: 0 }); // "未読メッセージはありません。"
t("inbox", { count: 3 }); // "未読メッセージが 3 件あります。"

序数詞のフォーマット (selectordinal)

序数表現の処理に使用します(完全一致 = が分類ルールより優先されます):

const messages = {
  podium:
    "{rank, selectordinal, =1 {1位} =2 {2位} =3 {3位} other {#位}}になりました!",
} as const;

const t = getTranslations("ja-JP");

t("podium", { rank: 1 }); // "1位になりました!"
t("podium", { rank: 4 }); // "4位になりました!"

条件分岐選択 (select)

ステータス列挙型や分類値に基づいて異なる文言をマッチングします:

const messages = {
  roleBadge:
    "{role, select, admin {管理者} editor {編集者} other {一般メンバー}}",
} as const;

const t = getTranslations("ja-JP");

t("roleBadge", { role: "admin" }); // "管理者"
t("roleBadge", { role: "guest" }); // "一般メンバー"

汎用リッチテキスト補間 t.rich()

XML 風タグを通じて文言をフロントエンドコンポーネントや DOM ノードへマッピングし、dangerouslySetInnerHTML による XSS リスクを回避します:

const messages = {
  notice: "続行する前に<terms>利用規約</terms>と<privacy>プライバシーポリシー</privacy>をご確認ください。",
} as const;

const t = getTranslations("ja-JP");

// 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 文字列マークアップ描画 t.markup()

安全な HTML タグを含む文字列を生成するために使用します:

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