واجهة برمجة التطبيقات Intl.RelativeTimeFormat
تنسيق سلاسل الوقت النسبي في JavaScript مع دعم كامل للتدويل
مقدمة
يُعد عرض الطوابع الزمنية النسبية مثل "منذ 3 ساعات" أو "خلال يومين" متطلباً شائعاً في تطبيقات الويب. تحتاج خلاصات وسائل التواصل الاجتماعي وأقسام التعليقات وأنظمة الإشعارات وسجلات النشاط جميعها إلى عروض زمنية قابلة للقراءة البشرية تتحدث مع تقادم المحتوى.
يمثل بناء هذه الوظيفة من الصفر تحديات. تحتاج إلى حساب فروق الوقت واختيار الوحدات المناسبة ومعالجة قواعد الجمع عبر اللغات والحفاظ على الترجمات لكل لغة مدعومة. يفسر هذا التعقيد سبب لجوء المطورين تقليدياً إلى مكتبات مثل Moment.js، مما يضيف حجماً كبيراً للحزمة لما يبدو وكأنه تنسيق بسيط.
توفر واجهة برمجة التطبيقات Intl.RelativeTimeFormat حلاً أصلياً. تقوم بتنسيق سلاسل الوقت النسبي مع دعم كامل للتدويل، وتتعامل مع قواعد الجمع والاتفاقيات الثقافية تلقائياً. تعمل واجهة برمجة التطبيقات عبر جميع المتصفحات الرئيسية بتغطية عالمية تبلغ 95%، مما يلغي الحاجة إلى التبعيات الخارجية مع إنتاج مخرجات ذات صوت طبيعي بعشرات اللغات.
الاستخدام الأساسي
ينشئ منشئ Intl.RelativeTimeFormat مثيل منسق يحول القيم الرقمية ووحدات الوقت إلى سلاسل محلية.
const rtf = new Intl.RelativeTimeFormat('en');
console.log(rtf.format(-1, 'day'));
// "1 day ago"
console.log(rtf.format(2, 'hour'));
// "in 2 hours"
console.log(rtf.format(-3, 'month'));
// "3 months ago"
تأخذ طريقة format() معاملين:
value: رقم يشير إلى مقدار الوقتunit: سلسلة تحدد وحدة الوقت
تشير القيم السالبة إلى الأوقات الماضية، بينما تشير القيم الموجبة إلى الأوقات المستقبلية. تتعامل واجهة برمجة التطبيقات تلقائياً مع الجمع، منتجة "منذ يوم واحد" أو "منذ يومين" بناءً على القيمة.
وحدات الوقت المدعومة
تدعم واجهة برمجة التطبيقات ثماني وحدات زمنية، تقبل كل منها الأشكال المفردة والجمع:
const rtf = new Intl.RelativeTimeFormat('en');
// These produce identical output
console.log(rtf.format(-5, 'second'));
// "5 seconds ago"
console.log(rtf.format(-5, 'seconds'));
// "5 seconds ago"
الوحدات المتاحة من الأصغر إلى الأكبر:
secondأوsecondsminuteأوminuteshourأوhoursdayأوdaysweekأوweeksmonthأوmonthsquarterأوquartersyearأوyears
تُعد وحدة الربع مفيدة في تطبيقات الأعمال لتتبع الفترات المالية، بينما تغطي الوحدات الأخرى احتياجات تنسيق الوقت النسبي النموذجية.
مخرجات اللغة الطبيعية
يتحكم خيار numeric في ما إذا كان المنسق يستخدم قيمًا رقمية أو بدائل لغة طبيعية.
const rtfNumeric = new Intl.RelativeTimeFormat('en', {
numeric: 'always'
});
console.log(rtfNumeric.format(-1, 'day'));
// "1 day ago"
console.log(rtfNumeric.format(0, 'day'));
// "in 0 days"
console.log(rtfNumeric.format(1, 'day'));
// "in 1 day"
يؤدي تعيين numeric إلى auto إلى إنتاج صياغة أكثر اصطلاحية للقيم الشائعة:
const rtfAuto = new Intl.RelativeTimeFormat('en', {
numeric: 'auto'
});
console.log(rtfAuto.format(-1, 'day'));
// "yesterday"
console.log(rtfAuto.format(0, 'day'));
// "today"
console.log(rtfAuto.format(1, 'day'));
// "tomorrow"
تنشئ مخرجات اللغة الطبيعية هذه واجهات أكثر حوارية. يعمل خيار auto عبر جميع وحدات الوقت، على الرغم من أن التأثير يكون أكثر وضوحًا مع الأيام. تحتوي اللغات الأخرى على بدائل اصطلاحية خاصة بها تتعامل معها واجهة برمجة التطبيقات تلقائيًا.
أنماط التنسيق
يضبط خيار style مستوى التفصيل في المخرجات لسياقات واجهة مختلفة:
const rtfLong = new Intl.RelativeTimeFormat('en', {
style: 'long'
});
console.log(rtfLong.format(-2, 'hour'));
// "2 hours ago"
const rtfShort = new Intl.RelativeTimeFormat('en', {
style: 'short'
});
console.log(rtfShort.format(-2, 'hour'));
// "2 hr. ago"
const rtfNarrow = new Intl.RelativeTimeFormat('en', {
style: 'narrow'
});
console.log(rtfNarrow.format(-2, 'hour'));
// "2h ago"
استخدم نمط long (الافتراضي) للواجهات القياسية حيث تكون سهولة القراءة أكثر أهمية. استخدم نمط short للتخطيطات محدودة المساحة مثل واجهات الهاتف المحمول أو جداول البيانات. استخدم نمط narrow للعروض المدمجة للغاية حيث يهم كل حرف.
حساب فروق الوقت
تقوم واجهة برمجة التطبيقات Intl.RelativeTimeFormat بتنسيق القيم ولكنها لا تحسبها. يجب عليك حساب فروق الوقت واختيار الوحدات المناسبة بنفسك. يمنحك هذا الفصل بين المسؤوليات التحكم في منطق الحساب مع تفويض تعقيد التنسيق إلى واجهة برمجة التطبيقات.
حساب فرق الوقت الأساسي
لوحدة زمنية محددة، احسب الفرق بين تاريخين:
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
function formatDaysAgo(date) {
const now = new Date();
const diffInMs = date - now;
const diffInDays = Math.round(diffInMs / (1000 * 60 * 60 * 24));
return rtf.format(diffInDays, 'day');
}
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
console.log(formatDaysAgo(yesterday));
// "yesterday"
const nextWeek = new Date();
nextWeek.setDate(nextWeek.getDate() + 7);
console.log(formatDaysAgo(nextWeek));
// "in 7 days"
يعمل هذا النهج عندما تعرف أي وحدة منطقية لحالة الاستخدام الخاصة بك. قد تستخدم الطوابع الزمنية للتعليقات دائمًا الساعات أو الأيام، بينما قد يركز جدولة الأحداث على الأيام أو الأسابيع.
اختيار الوحدة التلقائي
لتنسيق الوقت النسبي للأغراض العامة، اختر الوحدة الأنسب بناءً على مقدار فرق الوقت:
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const units = {
year: 24 * 60 * 60 * 1000 * 365,
month: 24 * 60 * 60 * 1000 * 365 / 12,
week: 24 * 60 * 60 * 1000 * 7,
day: 24 * 60 * 60 * 1000,
hour: 60 * 60 * 1000,
minute: 60 * 1000,
second: 1000
};
function formatRelativeTime(date) {
const now = new Date();
const diffInMs = date - now;
const absDiff = Math.abs(diffInMs);
for (const [unit, msValue] of Object.entries(units)) {
if (absDiff >= msValue || unit === 'second') {
const value = Math.round(diffInMs / msValue);
return rtf.format(value, unit);
}
}
}
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
console.log(formatRelativeTime(fiveMinutesAgo));
// "5 minutes ago"
const threeDaysAgo = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000);
console.log(formatRelativeTime(threeDaysAgo));
// "3 days ago"
يتكرر هذا التنفيذ عبر الوحدات من الأكبر إلى الأصغر، ويختار أول وحدة يتجاوز فيها فرق الوقت قيمة الوحدة بالميلي ثانية. يضمن الرجوع إلى الثواني أن الدالة تُرجع نتيجة دائمًا.
تستخدم عتبات الوحدات قيمًا تقريبية. تُحسب الأشهر على أنها 1/12 من السنة بدلاً من حساب أطوال الأشهر المتغيرة. يعمل هذا التقريب بشكل جيد لعروض الوقت النسبي حيث تكون سهولة القراءة أهم من الدقة.
دعم التدويل
يحترم المنسق الاتفاقيات الخاصة باللغة المحلية لعرض الوقت النسبي. تحتوي اللغات المختلفة على قواعد جمع مختلفة، وترتيبات كلمات مختلفة، وتعبيرات اصطلاحية مختلفة.
const rtfEnglish = new Intl.RelativeTimeFormat('en', {
numeric: 'auto'
});
console.log(rtfEnglish.format(-1, 'day'));
// "yesterday"
const rtfSpanish = new Intl.RelativeTimeFormat('es', {
numeric: 'auto'
});
console.log(rtfSpanish.format(-1, 'day'));
// "ayer"
const rtfJapanese = new Intl.RelativeTimeFormat('ja', {
numeric: 'auto'
});
console.log(rtfJapanese.format(-1, 'day'));
// "昨日"
تختلف قواعد الجمع بشكل كبير عبر اللغات. تميز الإنجليزية بين الواحد والكثير (يوم واحد مقابل يومين). العربية لديها ستة أشكال للجمع حسب العدد. اليابانية تستخدم نفس الشكل بغض النظر عن الكمية. تتعامل واجهة برمجة التطبيقات مع هذه التعقيدات تلقائيًا.
const rtfArabic = new Intl.RelativeTimeFormat('ar');
console.log(rtfArabic.format(-1, 'day'));
// "قبل يوم واحد"
console.log(rtfArabic.format(-2, 'day'));
// "قبل يومين"
console.log(rtfArabic.format(-3, 'day'));
// "قبل 3 أيام"
console.log(rtfArabic.format(-11, 'day'));
// "قبل 11 يومًا"
يتعامل المنسق أيضًا مع اتجاه النص للغات من اليمين إلى اليسار ويطبق اتفاقيات التنسيق المناسبة ثقافيًا. يلغي هذا التوطين التلقائي الحاجة إلى صيانة ملفات الترجمة أو تنفيذ منطق جمع مخصص.
التنسيق المتقدم باستخدام formatToParts
ترجع طريقة formatToParts() السلسلة المنسقة كمصفوفة من الكائنات، مما يسمح بتخصيص التنسيق أو معالجة المكونات الفردية.
const rtf = new Intl.RelativeTimeFormat('en');
const parts = rtf.formatToParts(-5, 'second');
console.log(parts);
// [
// { type: 'integer', value: '5', unit: 'second' },
// { type: 'literal', value: ' seconds ago' }
// ]
يحتوي كل كائن جزء على:
type: إماintegerللقيم الرقمية أوliteralللنصvalue: محتوى السلسلة النصية لهذا الجزءunit: وحدة الوقت (موجودة في الأجزاء الصحيحة)
يتيح هذا الهيكل العرض المخصص حيث قد ترغب في تنسيق الأرقام بشكل مختلف عن النص، أو استخراج مكونات محددة للعرض:
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
function formatWithStyledNumber(value, unit) {
const parts = rtf.formatToParts(value, unit);
return parts.map(part => {
if (part.type === 'integer') {
return `<strong>${part.value}</strong>`;
}
return part.value;
}).join('');
}
console.log(formatWithStyledNumber(-5, 'hour'));
// "<strong>5</strong> hours ago"
عند استخدام numeric: 'auto' مع قيم لها بدائل لغوية طبيعية، يُرجع formatToParts() جزءاً حرفياً واحداً:
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const parts = rtf.formatToParts(-1, 'day');
console.log(parts);
// [
// { type: 'literal', value: 'yesterday' }
// ]
يتيح لك هذا السلوك اكتشاف متى يتم استخدام اللغة الطبيعية مقابل التنسيق الرقمي، مما يسمح لك بتطبيق تنسيق أو سلوك مختلف بناءً على نوع الإخراج.
تحسين الأداء
يتضمن إنشاء مثيلات Intl.RelativeTimeFormat تحميل بيانات اللغة وتهيئة قواعد التنسيق. هذه العملية مكلفة بما يكفي لتجنب تكرارها دون داعٍ.
تخزين مثيلات المنسق مؤقتاً
أنشئ المنسقات مرة واحدة وأعد استخدامها:
const formatterCache = new Map();
function getFormatter(locale, options = {}) {
const cacheKey = `${locale}-${JSON.stringify(options)}`;
if (!formatterCache.has(cacheKey)) {
formatterCache.set(
cacheKey,
new Intl.RelativeTimeFormat(locale, options)
);
}
return formatterCache.get(cacheKey);
}
// Reuse cached formatters
const rtf = getFormatter('en', { numeric: 'auto' });
console.log(rtf.format(-1, 'day'));
// "yesterday"
تصبح استراتيجية التخزين المؤقت هذه مهمة عند تنسيق العديد من الطوابع الزمنية، مثل عرض موجزات النشاط أو سلاسل التعليقات.
تقليل عبء الحساب
خزّن الطوابع الزمنية بدلاً من حساب الأوقات النسبية بشكل متكرر:
// Store the creation date
const comment = {
text: "Great article!",
createdAt: new Date('2025-10-14T10:30:00Z')
};
// Calculate relative time only when rendering
function renderComment(comment, locale) {
const rtf = getFormatter(locale, { numeric: 'auto' });
const units = {
day: 24 * 60 * 60 * 1000,
hour: 60 * 60 * 1000,
minute: 60 * 1000,
second: 1000
};
const diffInMs = comment.createdAt - new Date();
const absDiff = Math.abs(diffInMs);
for (const [unit, msValue] of Object.entries(units)) {
if (absDiff >= msValue || unit === 'second') {
const value = Math.round(diffInMs / msValue);
return rtf.format(value, unit);
}
}
}
يفصل هذا النهج تخزين البيانات عن العرض، مما يسمح لك بإعادة حساب الأوقات النسبية عندما تتغير لغة المستخدم أو عند تحديث العرض دون تعديل البيانات الأساسية.
التنفيذ العملي
ينتج الجمع بين منطق الحساب والتنسيق دالة أداة قابلة لإعادة الاستخدام ومناسبة لتطبيقات الإنتاج:
class RelativeTimeFormatter {
constructor(locale = 'en', options = { numeric: 'auto' }) {
this.formatter = new Intl.RelativeTimeFormat(locale, options);
this.units = [
{ name: 'year', ms: 24 * 60 * 60 * 1000 * 365 },
{ name: 'month', ms: 24 * 60 * 60 * 1000 * 365 / 12 },
{ name: 'week', ms: 24 * 60 * 60 * 1000 * 7 },
{ name: 'day', ms: 24 * 60 * 60 * 1000 },
{ name: 'hour', ms: 60 * 60 * 1000 },
{ name: 'minute', ms: 60 * 1000 },
{ name: 'second', ms: 1000 }
];
}
format(date) {
const now = new Date();
const diffInMs = date - now;
const absDiff = Math.abs(diffInMs);
for (const unit of this.units) {
if (absDiff >= unit.ms || unit.name === 'second') {
const value = Math.round(diffInMs / unit.ms);
return this.formatter.format(value, unit.name);
}
}
}
}
// Usage
const formatter = new RelativeTimeFormatter('en');
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
console.log(formatter.format(fiveMinutesAgo));
// "5 minutes ago"
const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000);
console.log(formatter.format(tomorrow));
// "tomorrow"
تغلف هذه الفئة كلاً من المنسق ومنطق اختيار الوحدة، مما يوفر واجهة نظيفة تقبل كائنات التاريخ وتُرجع سلاسل نصية منسقة.
التكامل مع أطر العمل
في تطبيقات React، قم بإنشاء المنسق مرة واحدة وتمريره عبر السياق أو الخصائص:
import { createContext, useContext } from 'react';
const RelativeTimeContext = createContext(null);
export function RelativeTimeProvider({ locale, children }) {
const formatter = new RelativeTimeFormatter(locale);
return (
<RelativeTimeContext.Provider value={formatter}>
{children}
</RelativeTimeContext.Provider>
);
}
export function useRelativeTime() {
const formatter = useContext(RelativeTimeContext);
if (!formatter) {
throw new Error('useRelativeTime must be used within RelativeTimeProvider');
}
return formatter;
}
// Component usage
function CommentTimestamp({ date }) {
const formatter = useRelativeTime();
return <time>{formatter.format(date)}</time>;
}
يضمن هذا النمط إنشاء المنسقات مرة واحدة لكل لغة ومشاركتها عبر جميع المكونات التي تحتاج إلى تنسيق الوقت النسبي.
دعم المتصفحات
يعمل Intl.RelativeTimeFormat عبر جميع المتصفحات الحديثة بتغطية عالمية تبلغ 95%:
- Chrome 71+
- Firefox 65+
- Safari 14+
- Edge 79+
لا يدعم Internet Explorer واجهة برمجة التطبيقات هذه. بالنسبة للتطبيقات التي تتطلب دعم IE، تتوفر polyfills، على الرغم من أن التنفيذ الأصلي يوفر أداءً أفضل وأحجام حزم أصغر.
متى تستخدم واجهة برمجة التطبيقات هذه
يعمل Intl.RelativeTimeFormat بشكل أفضل لـ:
- عرض عمر المحتوى في الخلاصات والجداول الزمنية
- إظهار الطوابع الزمنية للتعليقات أو المنشورات
- تنسيق جدولة الأحداث بالنسبة للوقت الحالي
- بناء أنظمة الإشعارات بطوابع زمنية نسبية
- إنشاء سجلات النشاط بأوقات قابلة للقراءة البشرية
واجهة برمجة التطبيقات غير مناسبة لـ:
- تنسيق التاريخ والوقت المطلق (استخدم
Intl.DateTimeFormat) - تتبع الوقت الدقيق الذي يتطلب دقة بالميلي ثانية
- مؤقتات العد التنازلي التي تتحدث كل ثانية
- العمليات الحسابية للتاريخ أو حسابات التقويم
بالنسبة للتطبيقات التي تتطلب عرض الوقت النسبي والمطلق، ادمج Intl.RelativeTimeFormat مع Intl.DateTimeFormat. اعرض الأوقات النسبية للمحتوى الحديث وانتقل إلى التواريخ المطلقة للمحتوى الأقدم:
function formatTimestamp(date, locale = 'en') {
const now = new Date();
const diffInMs = Math.abs(date - now);
const sevenDaysInMs = 7 * 24 * 60 * 60 * 1000;
if (diffInMs < sevenDaysInMs) {
const rtf = new RelativeTimeFormatter(locale);
return rtf.format(date);
} else {
const dtf = new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'short',
day: 'numeric'
});
return dtf.format(date);
}
}
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
console.log(formatTimestamp(yesterday));
// "yesterday"
const lastMonth = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
console.log(formatTimestamp(lastMonth));
// "Sep 14, 2025"
يوفر هذا النهج الهجين فوائد اللغة الطبيعية للأوقات النسبية للمحتوى الحديث مع الحفاظ على الوضوح للطوابع الزمنية الأقدم.