كيفية تنسيق الوقت النسبي في TanStack Start الإصدار الأول
تنسيق الطوابع الزمنية كعبارات 'منذ يومين'
المشكلة
عرض الطوابع الزمنية كعبارات زمنية نسبية مثل "منذ يومين" أو "خلال 3 ساعات" يجعل المعلومات الزمنية أكثر بديهية للمستخدمين. ومع ذلك، تتبع هذه العبارات قواعد نحوية معقدة تختلف حسب اللغة. اللغة الإنجليزية تضع "ago" بعد الكميات الماضية و"in" قبل الكميات المستقبلية، لكن اللغات الأخرى قد تستخدم ترتيبات كلمات مختلفة، أو تصرف وحدات الوقت، أو تستخدم تراكيب نحوية مختلفة تمامًا. إن بناء هذه العبارات يدويًا بواسطة سلسلة التسلسل ينتج مخرجات غير صحيحة في كل لغة باستثناء اللغة التي قمت ببرمجتها.
تتطلب واجهة برمجة التنسيق الأساسية قيمة رقمية ووحدة زمنية، لكن الطوابع الزمنية تصل ككائنات Date أو قيم بالميلي ثانية. تحويل الطابع الزمني إلى الوحدة والقيمة المناسبة يتطلب حساب الفرق من اللحظة الحالية واختيار الوحدة الأكثر طبيعية للتعبير عن هذا الفرق.
الحل
حساب الفرق الزمني بين الطابع الزمني واللحظة الحالية، واختيار الوحدة الأكثر ملاءمة لهذا الفرق، ثم تنسيق النتيجة باستخدام تنسيق الوقت النسبي الخاص باللغة المحلية. هذا يحول الطوابع الزمنية الخام إلى عبارات صحيحة نحويًا تتكيف مع لغة المستخدم.
استخدم تنسيق الوقت النسبي من react-intl مع دالة مساعدة تحدد أفضل وحدة. تقارن الدالة المساعدة الفرق الزمني مع عتبات كل وحدة وتعيد كلاً من القيمة الرقمية واسم الوحدة. ثم يطبق المنسق القواعد النحوية الصحيحة للغة المحلية النشطة.
الخطوات
1. إنشاء مساعد اختيار الوحدة
بناء دالة تحسب الفرق الزمني وتختار الوحدة الأكثر طبيعية للتعبير عنه.
type RelativeTimeUnit =
| "second"
| "minute"
| "hour"
| "day"
| "week"
| "month"
| "year";
interface RelativeTimeValue {
value: number;
unit: RelativeTimeUnit;
}
export function selectRelativeTimeUnit(
timestamp: Date | number,
baseTime: Date | number = Date.now(),
): RelativeTimeValue {
const date = typeof timestamp === "number" ? timestamp : timestamp.getTime();
const base = typeof baseTime === "number" ? baseTime : baseTime.getTime();
const diffMs = date - base;
const absDiff = Math.abs(diffMs);
const minute = 60 * 1000;
const hour = 60 * minute;
const day = 24 * hour;
const week = 7 * day;
const month = 30 * day;
const year = 365 * day;
if (absDiff < minute) {
return { value: Math.round(diffMs / 1000), unit: "second" };
}
if (absDiff < hour) {
return { value: Math.round(diffMs / minute), unit: "minute" };
}
if (absDiff < day) {
return { value: Math.round(diffMs / hour), unit: "hour" };
}
if (absDiff < week) {
return { value: Math.round(diffMs / day), unit: "day" };
}
if (absDiff < month) {
return { value: Math.round(diffMs / week), unit: "week" };
}
if (absDiff < year) {
return { value: Math.round(diffMs / month), unit: "month" };
}
return { value: Math.round(diffMs / year), unit: "year" };
}
تقوم هذه الدالة بتحويل الطابع الزمني إلى قيمة ووحدة نسبية من خلال مقارنة الفرق الزمني مع عتبات ثابتة. وهي تعيد قيمًا سالبة للأوقات الماضية وقيمًا موجبة للأوقات المستقبلية، والتي يفسرها المنسق بشكل صحيح.
2. إنشاء مكون للوقت النسبي
قم ببناء مكون يجمع بين أداة اختيار الوحدة مع تنسيق react-intl.
import { FormattedRelativeTime } from "react-intl";
import { selectRelativeTimeUnit } from "./selectRelativeTimeUnit";
interface RelativeTimeProps {
date: Date | number;
numeric?: "always" | "auto";
style?: "long" | "short" | "narrow";
}
export function RelativeTime({
date,
numeric = "auto",
style = "long",
}: RelativeTimeProps) {
const { value, unit } = selectRelativeTimeUnit(date);
return (
<FormattedRelativeTime
value={value}
unit={unit}
numeric={numeric}
style={style}
/>
);
}
يقبل هذا المكون طابع زمني وخيارات التنسيق، ويحسب القيمة النسبية والوحدة، ثم يفوض إلى مكون react-intl للعرض المتوافق مع اللغة المحلية. خيار numeric="auto" ينتج عبارات مثل "أمس" بدلاً من "منذ يوم واحد" عندما يكون ذلك مناسبًا.
3. استخدام المكون في المسارات الخاصة بك
قم باستيراد وعرض المكون أينما تحتاج إلى عرض الطوابع الزمنية النسبية.
import { createFileRoute } from "@tanstack/react-router";
import { RelativeTime } from "../components/RelativeTime";
export const Route = createFileRoute("/posts/$postId")({
component: PostPage,
});
function PostPage() {
const post = {
title: "Understanding Relative Time",
publishedAt: new Date("2024-11-15T10:30:00Z"),
updatedAt: new Date(Date.now() - 2 * 60 * 60 * 1000),
};
return (
<article>
<h1>{post.title}</h1>
<p>
Published <RelativeTime date={post.publishedAt} />
</p>
<p>
Updated <RelativeTime date={post.updatedAt} style="short" />
</p>
</article>
);
}
يعمل المكون في سياقات العرض على الخادم والعميل. على الخادم ينتج عبارة الوقت النسبي الأولية، وعلى العميل يعرض نفس العبارة باستخدام اللغة المحلية للمستخدم من IntlProvider.
4. إضافة خيار تنسيق إلزامي
للحالات التي تحتاج فيها إلى السلسلة المنسقة مباشرة، قم بإنشاء مساعد قائم على الـ hook.
import { useIntl } from "react-intl";
import { selectRelativeTimeUnit } from "./selectRelativeTimeUnit";
export function useRelativeTime() {
const intl = useIntl();
return (
date: Date | number,
options?: {
numeric?: "always" | "auto";
style?: "long" | "short" | "narrow";
},
) => {
const { value, unit } = selectRelativeTimeUnit(date);
return intl.formatRelativeTime(value, unit, options);
};
}
تُرجع هذه الـ hook دالة تقوم بتنسيق الطوابع الزمنية بشكل إلزامي، وهي مفيدة لتعيين سمات النص أو حساب القيم خارج JSX.
5. استخدام الهوك للسياقات غير المكونة
استدعِ الهوك في المكونات حيث تحتاج إلى سلاسل نصية منسقة للسمات أو المنطق البرمجي.
import { createFileRoute } from "@tanstack/react-router";
import { useRelativeTime } from "../hooks/useRelativeTime";
export const Route = createFileRoute("/events/$eventId")({
component: EventPage,
});
function EventPage() {
const formatRelativeTime = useRelativeTime();
const event = {
name: "Product Launch",
startTime: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000),
};
const timeUntil = formatRelativeTime(event.startTime);
return (
<div>
<h1>{event.name}</h1>
<time dateTime={event.startTime.toISOString()} title={timeUntil}>
{timeUntil}
</time>
</div>
);
}
يوفر الهوك نفس منطق التنسيق في شكل وظيفي، مما يتيح لك استخدام سلاسل الوقت النسبي في السمات أو القيم المحسوبة أو في أي مكان لا يمكن فيه عرض المكون.