如何在 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 hook 格式化相对时间
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 实现了如随时间自动更新等高级功能,并通过 formatRelativeTime API 及其 value、unit 和 relative 格式化选项的 props 进行配置。对于需要自动更新的组件,建议使用组件形式。
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. 自定义格式化样式
该组件支持 RelativeTimeFormatOptions,包括 numeric 和 style 属性。可通过调整这些选项来控制输出格式。
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" 时,会在合适的情况下生成如 "yesterday" 这样的短语,而不是 "1 day ago";而 style="narrow" 则会生成更简短的形式,如 "2 mo. ago" 替代 "2 months ago"。