React Router v7에서 상대 시간을 포맷하는 방법
타임스탬프를 '2일 전' 형식으로 포맷
문제
타임스탬프를 "2일 전" 또는 "3시간 후"와 같은 상대 시간 표현으로 표시하면 콘텐츠가 즉각적이고 맥락적으로 느껴집니다. 그러나 이러한 표현은 언어마다 크게 다른 복잡한 문법 규칙을 따릅니다. 영어는 과거 수량 뒤에 "ago"를 배치하고 미래 수량 앞에 "in"을 배치하지만, 다른 언어는 시간 단위 자체를 변형하거나, 단어 순서를 재배열하거나, 완전히 다른 문법 구조를 사용할 수 있습니다. 문자열 연결로 이러한 표현을 수동으로 구성하면 하드코딩한 언어를 제외한 모든 언어에서 잘못된 출력이 생성되어 국제 사용자의 사용자 경험이 손상됩니다.
솔루션
로케일 인식 상대 시간 포맷팅을 사용하여 타임스탬프 차이를 문법적으로 올바른 표현으로 변환합니다. 주어진 타임스탬프와 현재 시점 간의 시간 차이를 계산한 다음 사용자의 로케일 규칙을 사용하여 해당 차이를 포맷합니다. 이를 통해 수동 문자열 조작 없이 과거 및 미래 시간 표현이 각 언어에 대한 올바른 문법, 단어 순서 및 변형 패턴을 따르도록 보장합니다.
단계
1. 상대 시간 값을 계산하는 헬퍼 생성
FormattedRelativeTime 컴포넌트는 숫자형 value와 unit prop이 필요합니다. 시간 차이를 계산하고 적절한 단위를 선택하는 헬퍼 함수를 작성하세요.
export function getRelativeTimeValue(date: Date | number) {
const now = Date.now();
const timestamp = typeof date === "number" ? date : date.getTime();
const diffInSeconds = Math.round((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: 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. FormattedRelativeTime을 사용하여 상대 시간 컴포넌트 생성
react-intl의 FormattedRelativeTime 컴포넌트를 사용하세요. 이 컴포넌트는 포맷된 상대 시간을 렌더링하며 선택적으로 일정 간격으로 업데이트할 수 있습니다.
import { FormattedRelativeTime } from "react-intl";
import { getRelativeTimeValue } from "./getRelativeTimeValue";
interface RelativeTimeProps {
date: Date | number;
updateIntervalInSeconds?: number;
}
export function RelativeTime({
date,
updateIntervalInSeconds,
}: RelativeTimeProps) {
const { value, unit } = getRelativeTimeValue(date);
return (
<FormattedRelativeTime
value={value}
unit={unit}
numeric="auto"
updateIntervalInSeconds={updateIntervalInSeconds}
/>
);
}
numeric="auto" 옵션을 사용하면 포매터가 적절한 경우 "1일 전" 대신 "어제"와 같은 표현을 사용할 수 있습니다. 선택적 updateIntervalInSeconds prop은 상대 시간을 최신 상태로 유지하기 위해 컴포넌트가 다시 렌더링되는 빈도를 제어합니다.
3. React Router 라우트에서 컴포넌트 사용하기
타임스탬프를 표시해야 하는 모든 라우트 컴포넌트에서 상대 시간 컴포넌트를 렌더링합니다.
import { RelativeTime } from "./RelativeTime";
interface Post {
id: string;
title: string;
content: string;
createdAt: number;
}
export function PostDetail({ post }: { post: Post }) {
return (
<article>
<h1>{post.title}</h1>
<time dateTime={new Date(post.createdAt).toISOString()}>
<RelativeTime date={post.createdAt} />
</time>
<p>{post.content}</p>
</article>
);
}
컴포넌트는 사용자의 로케일에 따라 타임스탬프를 자동으로 포맷하여 영어로는 "2 days ago", 프랑스어로는 "il y a 2 jours", 스페인어로는 "hace 2 días"와 같은 표현을 생성합니다.
4. useIntl을 사용하여 명령형으로 상대 시간 포맷하기
요소 속성 설정과 같이 포맷된 문자열이 직접 필요한 경우 useIntl 훅의 formatRelativeTime 함수를 사용하세요.
import { useIntl } from "react-intl";
import { getRelativeTimeValue } from "./getRelativeTimeValue";
interface CommentProps {
author: string;
text: string;
timestamp: number;
}
export function Comment({ author, text, timestamp }: CommentProps) {
const intl = useIntl();
const { value, unit } = getRelativeTimeValue(timestamp);
const relativeTime = intl.formatRelativeTime(value, unit, {
numeric: "auto",
});
return (
<div aria-label={`Comment by ${author}, posted ${relativeTime}`}>
<strong>{author}</strong>
<p>{text}</p>
</div>
);
}
이 접근 방식은 속성에 사용하거나, 다른 텍스트와 연결하거나, React가 아닌 API에 전달할 수 있는 포맷된 문자열을 제공하면서 완전한 로케일 지원을 유지합니다.