Next.js(Pages Router)v16で相対時刻をフォーマットする方法
タイムスタンプを「2日前」のようなフレーズにフォーマット
問題
タイムスタンプを「2日前」や「3時間後」のような相対時刻フレーズで表示すると、コンテンツが即座に文脈に沿ったものに感じられます。しかし、これらのフレーズは言語によって大きく異なる複雑な文法パターンに従います。英語では過去の数量の後に「ago」を、未来の数量の前に「in」を配置しますが、他の言語では異なる語順を使用したり、数量に基づいて時間単位を活用したり、まったく異なる文法構造を採用したりする場合があります。文字列連結でこれらのフレーズを手動で構築すると、記述された言語以外のすべての言語で誤った出力が生成され、国際的なオーディエンスのユーザーエクスペリエンスが損なわれます。
解決策
react-intlの相対時刻フォーマット機能を使用して、数値の時間差をユーザーのロケールに対して文法的に正しいフレーズに変換します。タイムスタンプと現在の時刻との時間差を計算し、適切な単位(秒、分、時間、日)を決定し、両方の値をreact-intlのフォーマットAPIに渡します。基盤となるIntl.RelativeTimeFormat APIは、ロケール固有の文法、語順、活用規則を自動的に処理します。
手順
1. 相対時刻の値を計算するヘルパーを作成
時間差は数値と適切な単位に変換する必要があります。タイムスタンプを現在時刻と比較し、最適な単位を選択する関数を構築します。
export function getRelativeTimeValue(date: Date | number) {
const now = Date.now();
const timestamp = typeof date === "number" ? date : date.getTime();
const diffInSeconds = (timestamp - now) / 1000;
const minute = 60;
const hour = minute * 60;
const day = hour * 24;
const week = day * 7;
const month = day * 30;
const year = day * 365;
const absDiff = Math.abs(diffInSeconds);
if (absDiff < minute) {
return { value: Math.round(diffInSeconds), unit: "second" as const };
} else if (absDiff < hour) {
return {
value: Math.round(diffInSeconds / minute),
unit: "minute" as const,
};
} else if (absDiff < day) {
return { value: Math.round(diffInSeconds / hour), unit: "hour" as const };
} else if (absDiff < week) {
return { value: Math.round(diffInSeconds / day), unit: "day" as const };
} else if (absDiff < month) {
return { value: Math.round(diffInSeconds / week), unit: "week" as const };
} else if (absDiff < year) {
return { value: Math.round(diffInSeconds / month), unit: "month" as const };
} else {
return { value: Math.round(diffInSeconds / year), unit: "year" as const };
}
}
この関数は、符号付き数値(過去の場合は負、未来の場合は正)と、差の大きさに基づいた最も適切な単位を返します。
2. useIntlフックで相対時刻をフォーマット
formatRelativeTime関数は、フォーマットされた相対時刻文字列を返し、数値、単位、およびIntl.RelativeTimeFormatOptionsに準拠するオプションを期待します。ステップ1のヘルパーを使用して値と単位を取得し、それをフォーマットします。
import { useIntl } from "react-intl";
import { getRelativeTimeValue } from "@/lib/relative-time";
export default function CommentTimestamp({ date }: { date: Date }) {
const intl = useIntl();
const { value, unit } = getRelativeTimeValue(date);
return (
<time dateTime={date.toISOString()}>
{intl.formatRelativeTime(value, unit)}
</time>
);
}
formatRelativeTimeメソッドは、英語では「2 days ago」、フランス語では「il y a 2 jours」のようなロケールに適したフレーズを生成します。
3. 宣言的なフォーマットにはFormattedRelativeTimeを使用する
FormattedRelativeTimeは、時間経過に伴う自動更新などの高度な機能を実装しており、value、unit、相対フォーマットオプションのプロパティを持つformatRelativeTime APIを使用します。自動更新が必要なコンポーネントには、コンポーネント形式を使用してください。
import { FormattedRelativeTime } from "react-intl";
import { getRelativeTimeValue } from "@/lib/relative-time";
export default function LiveTimestamp({ date }: { date: Date }) {
const { value, unit } = getRelativeTimeValue(date);
return (
<time dateTime={date.toISOString()}>
<FormattedRelativeTime
value={value}
unit={unit}
numeric="auto"
updateIntervalInSeconds={10}
/>
</time>
);
}
updateIntervalInSecondsプロパティは、コンポーネントの再レンダリング頻度を制御し、時間の経過とともに表示されるフレーズを最新の状態に保ちます。
4. フォーマットスタイルをカスタマイズする
このコンポーネントは、numericおよびstyleプロパティを含むRelativeTimeFormatOptionsを受け入れます。これらのオプションを調整して、出力形式を制御します。
import { FormattedRelativeTime } from "react-intl";
import { getRelativeTimeValue } from "@/lib/relative-time";
export default function CompactTimestamp({ date }: { date: Date }) {
const { value, unit } = getRelativeTimeValue(date);
return (
<FormattedRelativeTime
value={value}
unit={unit}
numeric="auto"
style="narrow"
/>
);
}
numeric="auto"を設定すると、適切な場合に「1 day ago」ではなく「yesterday」のようなフレーズが生成され、style="narrow"は「2 months ago」ではなく「2 mo. ago」のような短縮形を作成します。