@k8ordo/i18n

日付と数値

複数形・日付・数値・並びの書式は Intl そのものです。ロケール集合は、今のロケール(日付ならそのロケールのタイムゾーンも)で Intl のオブジェクトを引き、作ったものを使い回します。独自の書式の記法はありません。

集合が引く Intl

どれも集合のメンバーで、hook ではありません。Server Component でも Client Component でも、文言の関数の中でも呼べます。

メンバー返すもの
dateTimeFormat(options?)今のロケールと、そのロケールの timeZone の Intl.DateTimeFormat。
numberFormat(options?)今のロケールの Intl.NumberFormat。
relativeTimeFormat(options?)今のロケールの Intl.RelativeTimeFormat。
pluralRules(options?)今のロケールの Intl.PluralRules。
listFormat(options?)今のロケールの Intl.ListFormat。

返すのは Intl のオブジェクトそのものなので、format・formatToParts・formatRange・select・resolvedOptions は Intl のものをそのまま使えます。オプションも Intl のものです。

ここに無い Intl(Intl.Collator、Intl.DisplayNames など)は、getLocale() のタグを渡して自分で作ります。

日付はロケールのタイムゾーンで

dateTimeFormat は、defineLocales に書いたそのロケールの timeZone でしか日付を書きません。

tsx
// src/components/published-at.tsx
import { locales } from '../i18n';

export function PublishedAt({ date }: { date: Date }) {
  return (
    <time dateTime={date.toISOString()}>
      {locales.dateTimeFormat({ dateStyle: 'medium' }).format(date)}
    </time>
  );
}

実行環境のタイムゾーンは、サーバーではサーバーのもの、ブラウザでは訪問者のものです。それに任せて日付を書くと、サーバーの HTML とブラウザの hydrate で文が変わり、日付の境目では 1 日ずれます。ロケールのタイムゾーンは両側で同じなので、この食い違いが起きません。

オプションの型は timeZone を受け付けません。as で押し通しても、ロケールのタイムゾーンが上書きします。

ts
// the type refuses it — the locale's time zone is the only one
locales.dateTimeFormat({ dateStyle: 'medium', timeZone: 'UTC' });

訪問者のタイムゾーンで見せたい表示(手元の時計、今からの相対時間)は、サーバーとブラウザで必ず違うものです。Intl を直接使い、ブラウザだけで描く部分に置きます。

文言の中で

値を取る文言の関数の中で呼べば、複数形も日付もその文言の一部になります。関数が呼ばれるのは、そのロケールが今のロケールのときです。

ts
// src/messages/cart.ts
import { message } from '@k8ordo/i18n';

import { locales } from '../i18n';

export const items = message({
  ja: (count: number) => `${locales.numberFormat().format(count)} 件`,
  en: (count) =>
    `${locales.numberFormat().format(count)} ${locales.pluralRules().select(count) === 'one' ? 'item' : 'items'}`,
});

export const updated = message({
  ja: (date: Date) =>
    `${locales.dateTimeFormat({ dateStyle: 'long' }).format(date)} 更新`,
  en: (date) =>
    `Updated ${locales.dateTimeFormat({ dateStyle: 'long' }).format(date)}`,
});

作ったものは使い回す

Intl のオブジェクトは作るのが重いので、ロケールとオプションの組ごとに 1 つ作り、次からは同じものを返します。

オプションは JSON で見分けます。同じオプションを違う順で書くと 2 つ作られますが、答えが変わることはありません。描画のたびに呼んでも、作り直しにはなりません。