Как форматировать относительное время в Next.js (Pages Router) v16
Форматируйте метки времени как фразы вроде «2 дня назад»
Проблема
Отображение меток времени в виде относительных фраз, например «2 дня назад» или «через 3 часа», делает контент более живым и понятным. Но такие фразы подчиняются сложным грамматическим правилам, которые сильно различаются в разных языках. В английском «ago» ставится после количества в прошлом, а «in» — перед будущим, но в других языках может быть другой порядок слов, изменяться форма времени в зависимости от числа или использоваться совершенно иные грамматические конструкции. Если собирать такие фразы вручную через конкатенацию строк, получится корректно только для одного языка, а для остальных — сломается пользовательский опыт.
Решение
Используйте форматирование относительного времени из react-intl, чтобы преобразовывать числовые разницы во времени в грамматически правильные фразы для нужной локали. Вычислите разницу между меткой времени и текущим моментом, определите подходящую единицу (секунды, минуты, часы, дни) и передайте оба значения в API форматирования react-intl. Внутри используется Intl.RelativeTimeFormat, который автоматически учитывает грамматику, порядок слов и правила склонения для выбранного языка.
Шаги
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 дня назад» на английском или «il y a 2 jours» на французском.
3. Используйте FormattedRelativeTime для декларативного форматирования
FormattedRelativeTime реализует продвинутые функции, такие как обновление со временем, и использует API formatRelativeTime с пропсами для значения, единицы и опций относительного форматирования. Для компонентов, которым нужно автоматическое обновление, используйте компонентную форму.
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" позволяет получать фразы вроде «вчера» вместо «1 день назад», а style="narrow" создает более короткие формы, например, «2 мес. назад» вместо «2 месяца назад».