كيف تقوم بتنسيق الأوقات النسبية مثل منذ 3 أيام أو خلال ساعتين؟

استخدم Intl.RelativeTimeFormat لعرض أوقات مثل منذ 3 أيام أو خلال ساعتين بأي لغة مع صيغ الجمع والتوطين التلقائي

مقدمة

تعرض خلاصات وسائل التواصل الاجتماعي وأقسام التعليقات وسجلات النشاط طوابع زمنية مثل "منذ 5 دقائق" أو "منذ ساعتين" أو "خلال 3 أيام". تساعد هذه الطوابع الزمنية النسبية المستخدمين على فهم وقت حدوث شيء ما بسرعة دون الحاجة إلى تحليل تاريخ مطلق.

عندما تقوم ببرمجة هذه النصوص باللغة الإنجليزية، فإنك تفترض أن جميع المستخدمين يتحدثون الإنجليزية ويتبعون قواعد النحو الإنجليزية. تمتلك اللغات المختلفة طرقًا مختلفة للتعبير عن الوقت النسبي. تقول الإسبانية "hace 3 días" بدلاً من "3 days ago". تستخدم اليابانية "3日前" بهيكل مختلف تمامًا. كما أن لكل لغة قواعد جمع فريدة تحدد متى يتم استخدام صيغ المفرد مقابل صيغ الجمع.

توفر جافا سكريبت واجهة برمجة التطبيقات Intl.RelativeTimeFormat للتعامل مع تنسيق الوقت النسبي تلقائيًا. يشرح هذا الدرس كيفية تنسيق الأوقات النسبية بشكل صحيح لأي لغة باستخدام واجهة برمجة التطبيقات المدمجة هذه.

لماذا يحتاج تنسيق الوقت النسبي إلى التدويل

تعبر اللغات المختلفة عن الوقت النسبي بطرق مختلفة. تضع اللغة الإنجليزية وحدة الوقت قبل كلمة "ago" للأوقات الماضية وبعد "in" للأوقات المستقبلية. تستخدم اللغات الأخرى ترتيبات كلمات مختلفة، أو حروف جر مختلفة، أو هياكل نحوية مختلفة تمامًا.

const rtfEnglish = new Intl.RelativeTimeFormat('en');
console.log(rtfEnglish.format(-3, 'day'));
// "3 days ago"

const rtfSpanish = new Intl.RelativeTimeFormat('es');
console.log(rtfSpanish.format(-3, 'day'));
// "hace 3 días"

const rtfJapanese = new Intl.RelativeTimeFormat('ja');
console.log(rtfJapanese.format(-3, 'day'));
// "3 日前"

تنتج كل لغة مخرجات تبدو طبيعية وتتبع اتفاقياتها الخاصة. لا تحتاج إلى معرفة هذه الاتفاقيات أو الاحتفاظ بملفات الترجمة. تتعامل واجهة برمجة التطبيقات مع جميع تفاصيل التنسيق تلقائيًا.

تختلف قواعد الجمع أيضًا بشكل كبير عبر اللغات. تميز اللغة الإنجليزية بين "1 day" و"2 days". تحتوي اللغة العربية على ستة أشكال جمع مختلفة اعتمادًا على العدد. تستخدم اليابانية نفس الصيغة بغض النظر عن الكمية. تطبق واجهة برمجة التطبيقات Intl.RelativeTimeFormat قواعد الجمع الصحيحة لكل لغة.

واجهة برمجة التطبيقات Intl.RelativeTimeFormat

يقوم مُنشئ Intl.RelativeTimeFormat بإنشاء مُنسّق يحوّل القيم الرقمية ووحدات الوقت إلى سلاسل نصية مُعرّبة. يمكنك تمرير مُعرّف اللغة كوسيط أول، ثم استدعاء طريقة format() مع قيمة ووحدة.

const rtf = new Intl.RelativeTimeFormat('en-US');

console.log(rtf.format(-1, 'day'));
// "1 day ago"

console.log(rtf.format(2, 'hour'));
// "in 2 hours"

تأخذ طريقة format() وسيطين. الأول هو رقم يمثل مقدار الوقت. والثاني هو سلسلة نصية تحدد وحدة الوقت.

تشير الأرقام السالبة إلى الأوقات الماضية، بينما تشير الأرقام الموجبة إلى الأوقات المستقبلية. هذه الاتفاقية تجعل واجهة برمجة التطبيقات بديهية الاستخدام بمجرد فهمك لاتفاقية الإشارة.

تنسيق الأوقات الماضية والمستقبلية

تحدد إشارة القيمة ما إذا كان الوقت في الماضي أو المستقبل. تنتج القيم السالبة صيغ الماضي، بينما تنتج القيم الموجبة صيغ المستقبل.

const rtf = new Intl.RelativeTimeFormat('en-US');

console.log(rtf.format(-5, 'minute'));
// "5 minutes ago"

console.log(rtf.format(5, 'minute'));
// "in 5 minutes"

console.log(rtf.format(-2, 'week'));
// "2 weeks ago"

console.log(rtf.format(2, 'week'));
// "in 2 weeks"

يعمل هذا النمط بشكل متسق عبر جميع وحدات الوقت وجميع اللغات. تختار واجهة برمجة التطبيقات تلقائيًا البنية النحوية الصحيحة بناءً على ما إذا كانت القيمة موجبة أو سالبة.

وحدات الوقت المتاحة

تدعم واجهة برمجة التطبيقات ثماني وحدات زمنية تغطي معظم احتياجات تنسيق الوقت النسبي. يمكنك استخدام الصيغ المفردة أو الجمع، وكلاهما يعمل بشكل متطابق.

const rtf = new Intl.RelativeTimeFormat('en-US');

console.log(rtf.format(-30, 'second'));
// "30 seconds ago"

console.log(rtf.format(-15, 'minute'));
// "15 minutes ago"

console.log(rtf.format(-6, 'hour'));
// "6 hours ago"

console.log(rtf.format(-3, 'day'));
// "3 days ago"

console.log(rtf.format(-2, 'week'));
// "2 weeks ago"

console.log(rtf.format(-4, 'month'));
// "4 months ago"

console.log(rtf.format(-1, 'quarter'));
// "1 quarter ago"

console.log(rtf.format(-2, 'year'));
// "2 years ago"

تقبل واجهة برمجة التطبيقات كلاً من الصيغ المفردة مثل day والصيغ الجمع مثل days. كلاهما ينتج مخرجات متطابقة. وحدة الربع مفيدة للتطبيقات التجارية التي تتعامل مع الفترات المالية.

استخدام اللغة الطبيعية مع numeric auto

يتحكم خيار numeric في ما إذا كان المنسق يستخدم الأرقام أو بدائل اللغة الطبيعية. القيمة الافتراضية هي always، والتي تُظهر الأرقام دائمًا.

const rtfAlways = new Intl.RelativeTimeFormat('en-US', {
  numeric: 'always'
});

console.log(rtfAlways.format(-1, 'day'));
// "1 day ago"

console.log(rtfAlways.format(0, 'day'));
// "in 0 days"

console.log(rtfAlways.format(1, 'day'));
// "in 1 day"

يؤدي ضبط numeric على auto إلى إنتاج صياغة أكثر طبيعية لقيم معينة.

const rtfAuto = new Intl.RelativeTimeFormat('en-US', {
  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 في إطالة الإخراج. الأنماط الثلاثة المتاحة هي long وshort وnarrow.

const rtfLong = new Intl.RelativeTimeFormat('en-US', {
  style: 'long'
});
console.log(rtfLong.format(-2, 'hour'));
// "2 hours ago"

const rtfShort = new Intl.RelativeTimeFormat('en-US', {
  style: 'short'
});
console.log(rtfShort.format(-2, 'hour'));
// "2 hr. ago"

const rtfNarrow = new Intl.RelativeTimeFormat('en-US', {
  style: 'narrow'
});
console.log(rtfNarrow.format(-2, 'hour'));
// "2h ago"

نمط long هو الافتراضي ويعمل بشكل جيد لمعظم الواجهات. يوفر نمط short مساحة في تخطيطات الجوال أو الجداول. ينتج نمط narrow الإخراج الأكثر إيجازًا للتصميمات ذات المساحة المحدودة للغاية.

حساب الفروق الزمنية

تقوم واجهة برمجة التطبيقات Intl.RelativeTimeFormat بتنسيق القيم ولكنها لا تحسبها. يجب عليك حساب الفرق الزمني بنفسك، ثم تمرير النتيجة إلى المنسق.

لحساب الفرق الزمني، اطرح التاريخ المستهدف من التاريخ الحالي، ثم حوّل النتيجة من مللي ثانية إلى الوحدة المطلوبة.

const rtf = new Intl.RelativeTimeFormat('en-US', { 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 tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);

console.log(formatDaysAgo(tomorrow));
// "tomorrow"

تحسب هذه الدالة الفرق بالأيام بين التاريخ المستهدف والوقت الحالي. تقسم العملية الحسابية المللي ثانية على عدد المللي ثانية في اليوم، ثم تقرب إلى أقرب عدد صحيح.

ينتج عن الطرح date - now قيمة سالبة للتواريخ الماضية وقيمة موجبة للتواريخ المستقبلية. وهذا يتوافق مع اصطلاح الإشارة المتوقع من طريقة format().

بناء دالة أداة متكاملة

لمنسق وقت نسبي للاستخدام العام، تحتاج إلى اختيار وحدة الوقت الأكثر ملاءمة بناءً على حجم الفرق الزمني.

const rtf = new Intl.RelativeTimeFormat('en-US', { 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"

const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000);
console.log(formatRelativeTime(tomorrow));
// "tomorrow"

تتكرر هذه الدالة عبر وحدات الوقت من الأكبر إلى الأصغر، وتختار الوحدة الأولى حيث يتجاوز الفرق المطلق قيمة الوحدة بالميلي ثانية. الرجوع إلى الثواني يضمن أن الدالة تعيد دائمًا نتيجة.

تستخدم تعريفات الوحدة قيمًا تقريبية. يتم حساب الشهور كـ 1/12 من السنة بدلاً من مراعاة اختلاف أطوال الشهور. هذا التقريب يعمل بشكل جيد لعروض الوقت النسبية حيث تكون القيم التقريبية أكثر فائدة من الدقة المطلقة.

التنسيق حسب اللغة المفضلة للمستخدم

بدلاً من تحديد لغة معينة في الكود، يمكنك استخدام اللغة المفضلة للمستخدم من المتصفح.

const userLocale = navigator.language;
const rtf = new Intl.RelativeTimeFormat(userLocale, { numeric: 'auto' });

const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
console.log(rtf.format(-1, 'day'));
// تختلف المخرجات حسب لغة المستخدم
// للغة en-US: "yesterday"
// للغة es-ES: "ayer"
// للغة fr-FR: "hier"
// للغة de-DE: "gestern"

يعرض هذا النهج الأوقات النسبية وفقًا لتفضيلات لغة كل مستخدم دون الحاجة إلى اختيار اللغة يدويًا. يوفر المتصفح تفضيل اللغة، وتطبق واجهة البرمجة اتفاقيات التنسيق المناسبة.

رؤية نفس الوقت بلغات مختلفة

تنتج قيمة الوقت النسبي نفسها مخرجات مختلفة للغات المختلفة. تتبع كل لغة اتفاقياتها الخاصة في ترتيب الكلمات والقواعد النحوية وصيغ الجمع.

const threeDaysAgo = -3;

const rtfEnglish = new Intl.RelativeTimeFormat('en-US');
console.log(rtfEnglish.format(threeDaysAgo, 'day'));
// "3 days ago"

const rtfSpanish = new Intl.RelativeTimeFormat('es-ES');
console.log(rtfSpanish.format(threeDaysAgo, 'day'));
// "hace 3 días"

const rtfFrench = new Intl.RelativeTimeFormat('fr-FR');
console.log(rtfFrench.format(threeDaysAgo, 'day'));
// "il y a 3 jours"

const rtfGerman = new Intl.RelativeTimeFormat('de-DE');
console.log(rtfGerman.format(threeDaysAgo, 'day'));
// "vor 3 Tagen"

const rtfJapanese = new Intl.RelativeTimeFormat('ja-JP');
console.log(rtfJapanese.format(threeDaysAgo, 'day'));
// "3 日前"

const rtfArabic = new Intl.RelativeTimeFormat('ar-SA');
console.log(rtfArabic.format(threeDaysAgo, 'day'));
// "قبل 3 أيام"

تنتج كل لغة مخرجات تبدو طبيعية يستخدمها المتحدثون الأصليون في المحادثة. تتعامل واجهة البرمجة مع كل تعقيدات البنى النحوية المختلفة وأنظمة الكتابة المختلفة واتجاهات النص المختلفة.

إعادة استخدام المنسقات لتحسين الأداء

يتضمن إنشاء نسخة من Intl.RelativeTimeFormat تحميل بيانات اللغة ومعالجة الخيارات. عند تنسيق طوابع زمنية متعددة، قم بإنشاء المنسق مرة واحدة وأعد استخدامه.

const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });

const timestamps = [
  new Date(Date.now() - 5 * 60 * 1000),      // قبل 5 دقائق
  new Date(Date.now() - 2 * 60 * 60 * 1000), // قبل ساعتين
  new Date(Date.now() - 24 * 60 * 60 * 1000) // قبل يوم واحد
];

timestamps.forEach(date => {
  const diffInMs = date - new Date();
  const diffInMinutes = Math.round(diffInMs / (60 * 1000));
  console.log(rtf.format(diffInMinutes, 'minute'));
});

هذا النهج أكثر كفاءة من إنشاء منسق جديد لكل طابع زمني. يصبح الفرق في الأداء ملحوظًا عند تنسيق مئات أو آلاف الطوابع الزمنية في تغذيات النشاط أو سلاسل التعليقات.

استخدام الأوقات النسبية في واجهات المستخدم

يمكنك استخدام تنسيق الوقت النسبي في أي مكان تعرض فيه الطوابع الزمنية للمستخدمين. وهذا يشمل تغذيات وسائل التواصل الاجتماعي، وأقسام التعليقات، وسجلات النشاط، وأنظمة الإشعارات، وأي واجهة يساعد فيها إظهار الوقت الذي مضى منذ حدوث شيء ما المستخدمين على فهم السياق.

const rtf = new Intl.RelativeTimeFormat(navigator.language, {
  numeric: 'auto'
});

function updateTimestamp(element, date) {
  const now = new Date();
  const diffInMs = date - now;
  const diffInMinutes = Math.round(diffInMs / (60 * 1000));

  element.textContent = rtf.format(diffInMinutes, 'minute');
}

const commentDate = new Date('2025-10-15T14:30:00');
const timestampElement = document.getElementById('comment-timestamp');

updateTimestamp(timestampElement, commentDate);

تعمل السلاسل النصية المنسقة مثل أي قيمة نصية أخرى. يمكنك إدراجها في محتوى النص، أو السمات، أو أي سياق آخر تعرض فيه معلومات للمستخدمين.