توطين الأرقام والتواريخ بـ Intl.NumberFormat و Intl.DateTimeFormat
كيف تعرض الأرقام والتواريخ تلقائياً بحسب لغة المستخدم ومنطقته باستخدام Intl.NumberFormat و Intl.DateTimeFormat في جافا سكريبت.
عدد الكلمات: ~١٩٠٠ • مدة القراءة: ٩ دقائق
توطين الأرقام والتواريخ بـ Intl
دع جافا سكريبت يعرف من أين يأتي المستخدم، وهو سيتكفّل بالباقي
ملاحظة للقارئ: هذا المقال مستقلٌ تماماً ويمكن قراءته دون مقدمات، لكن إن أردت فهم الفرق بين نظامَي الأرقام وتأثير الخطوط على عرضهما، فابدأ بـ الأرقام العربية والهندية والخطوط.
في هذا المقال من منصة ذي يزن، نصل إلى صلب المسألة: بدل أن تكتب شرطاً لكل دولة وتُدير جداول تحويل يدوية، جافا سكريبت تمنحك واجهةً برمجيةً جاهزةً تعرف بنفسها كيف يكتب السعودي تاريخه، وكيف يقرأ المغربي رقمه، وما هو رمز العملة في الإمارات. هذه الواجهة اسمها Intl.
أولاً: ما هي واجهة Intl وما حدودها؟
Intl هي كائنٌ عالميٌ مدمجٌ في جافا سكريبت منذ عام ٢٠١٢، ومدعوم في جميع المتصفحات الحديثة وبيئات Node.js. لا تحتاج إلى تثبيت أي مكتبةٍ خارجية.
تضم Intl عدة بانيات (constructors)، أهمها لسياقنا:
Intl.NumberFormat— لتنسيق الأرقام والعملات والنسب المئويةIntl.DateTimeFormat— لتنسيق التواريخ والأوقاتIntl.Collator— لمقارنة النصوص وترتيبها أبجدياً (خارج نطاق هذا المقال)
لكن قبل الغوص في الأمثلة، هنالك نقطةٌ حدوديةٌ مهمة: Intl تعرف كيف تعرض الأرقام، لكنها لا تعرف ماذا تعرض. تخزين العملات وحساب الضرائب وتحديد العملة المناسبة، هو شأن قاعدة البيانات وتصميم التطبيق، وهو موضوع مقالنا الثالث.
ثانياً: Intl.NumberFormat — الأرقام بحسب المنطقة
الاستخدام الأساسي بسيط جداً. تُنشئ كائن تنسيق مرتبطاً بمنطقة جغرافية (locale)، ثم تستدعي format() على أي رقم:
// نفس الرقم — ثلاثة عروض مختلفة
const number = 1234567.89;
// السعودية: أرقام هندية، فاصل ألفي عربي
const saudiFormat = new Intl.NumberFormat('ar-SA');
console.log(saudiFormat.format(number));
// ← ١٬٢٣٤٬٥٦٧٫٨٩
// المغرب: أرقام لاتينية، فاصل غربي
const moroccanFormat = new Intl.NumberFormat('ar-MA');
console.log(moroccanFormat.format(number));
// ← 1.234.567,89
// الولايات المتحدة للمقارنة
const usFormat = new Intl.NumberFormat('en-US');
console.log(usFormat.format(number));
// ← 1,234,567.89
لاحظ كيف أن ar-SA وar-MA — وكلاهما “عربي”، ينتجان تنسيقين مختلفين تماماً. هذا بالضبط ما ناقشناه في المقال السابق حول تعدد الأعراف الرقمية في العالم العربي.
خيارات التنسيق المتقدمة
تقبل Intl.NumberFormat كائن خيارات (options) كمعامل ثانٍ يمنحك تحكماً دقيقاً:
// تحديد عدد الخانات العشرية
const preciseFormat = new Intl.NumberFormat('ar-SA', {
minimumFractionDigits: 2, // لا تقل عن خانتين عشريتين
maximumFractionDigits: 2, // لا تزيد عن خانتين عشريتين
});
console.log(preciseFormat.format(1500));
// ← ١٬٥٠٠٫٠٠
// النسب المئوية
const percentFormat = new Intl.NumberFormat('ar-SA', {
style: 'percent',
maximumFractionDigits: 1,
});
console.log(percentFormat.format(0.157));
// ← ١٥٫٧٪
// الأعداد الكبيرة بصيغة مختصرة (compact)
const compactFormat = new Intl.NumberFormat('ar-SA', {
notation: 'compact',
compactDisplay: 'short',
});
console.log(compactFormat.format(1500000));
// ← ١٫٥ مليون
تنسيق العملات
هنا تتألق Intl.NumberFormat بشكلٍ خاص. تمرير style: 'currency' يضع رمز العملة تلقائياً في موضعه الصحيح — يميناً أو يساراً — ويحدد عدد الخانات العشرية المعتادة لتلك العملة:
const amount = 1250;
// الريال السعودي
const sarFormat = new Intl.NumberFormat('ar-SA', {
style: 'currency',
currency: 'SAR',
});
console.log(sarFormat.format(amount));
// ← ر.س ١٬٢٥٠٫٠٠
// الدرهم الإماراتي
const aedFormat = new Intl.NumberFormat('ar-AE', {
style: 'currency',
currency: 'AED',
});
console.log(aedFormat.format(amount));
// ← د.إ. ١٬٢٥٠٫٠٠
// الجنيه المصري
const egpFormat = new Intl.NumberFormat('ar-EG', {
style: 'currency',
currency: 'EGP',
});
console.log(egpFormat.format(amount));
// ← ج.م. ١٬٢٥٠٫٠٠
ما تراه ليس سحراً، إنما بياناتٌ مخزّنةٌ في قاعدة بياناتٍ ضخمةٍ تُسمى CLDR (Common Locale Data Repository) تمولها شركات التقنية الكبرى وتُحدَّث دورياً لتعكس أعراف كل منطقة.
ثالثاً: Intl.DateTimeFormat — التواريخ بلا ترجمةٍ يدوية
التواريخ أكثر تعقيداً من الأرقام لسببين: أولاً، ترتيب اليوم والشهر والسنة يختلف بين الثقافات. ثانياً، العالم العربي يستخدم تقويمين — الميلادي والهجري — وبعض السياقات تتطلب إظهار كليهما.
الاستخدام الأساسي
const date = new Date(2026, 4, 15); // 15 مايو 2026
// السعودية — تاريخ كامل بالعربية
const saudiDate = new Intl.DateTimeFormat('ar-SA', {
dateStyle: 'full',
});
console.log(saudiDate.format(date));
// ← الجمعة، ١٧ ذو القعدة ١٤٤٧ هـ
// الإمارات — يوم/شهر/سنة ميلادية
const uaeDate = new Intl.DateTimeFormat('ar-AE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
});
console.log(uaeDate.format(date));
// ← ١٥/٠٥/٢٠٢٦
// مصر — صيغة متوسطة
const egyptDate = new Intl.DateTimeFormat('ar-EG', {
dateStyle: 'medium',
});
console.log(egyptDate.format(date));
// ← ١٥ مايو ٢٠٢٦
لاحظ ar-SA: المتصفح تحوّل تلقائياً إلى التقويم الهجري لأن هذا هو الإعداد الافتراضي للمنطقة السعودية في بيانات CLDR.
التحكم في التقويم المستخدم
يمكنك إجبار تقويمٍ بعينه بصرف النظر عن إعداد المنطقة:
const date = new Date(2026, 4, 15);
// إجبار التقويم الهجري لأي منطقة
const hijriFormat = new Intl.DateTimeFormat('ar-SA-u-ca-islamic', {
dateStyle: 'long',
});
console.log(hijriFormat.format(date));
// ← ١٧ ذو القعدة ١٤٤٧ هـ
// إجبار التقويم الميلادي حتى في السعودية
const gregorianInSaudi = new Intl.DateTimeFormat('ar-SA-u-ca-gregory', {
dateStyle: 'long',
});
console.log(gregorianInSaudi.format(date));
// ← ١٥ مايو ٢٠٢٦
// إظهار التاريخين معاً في الفاتورة
function dualDate(date) {
const hijri = new Intl.DateTimeFormat('ar-SA-u-ca-islamic', {
dateStyle: 'medium'
}).format(date);
const gregorian = new Intl.DateTimeFormat('ar-SA-u-ca-gregory', {
dateStyle: 'medium'
}).format(date);
return `${hijri} — ${gregorian}`;
}
console.log(dualDate(new Date(2026, 4, 15)));
// ← ١٧ ذو القعدة ١٤٤٧ هـ — ١٥ مايو ٢٠٢٦
دالة dualDate هذه ستظهر في مشروعنا التطبيقي (المقال الرابع) حين نبني نظام الفواتير الكامل.
تنسيق الوقت
const now = new Date();
// وقت كامل بصيغة 12 ساعة
const time12 = new Intl.DateTimeFormat('ar-SA', {
hour: 'numeric',
minute: '2-digit',
hour12: true,
});
console.log(time12.format(now));
// ← ٣:٤٥ م
// وقت بصيغة 24 ساعة (أكثر شيوعاً في السياقات التقنية)
const time24 = new Intl.DateTimeFormat('ar-EG', {
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
console.log(time24.format(now));
// ← ١٥:٤٥
رابعاً: اكتشاف locale المستخدم تلقائياً
حتى الآن كنا نمرر الـ locale يدوياً. لكن في التطبيقات الحقيقية، تريد في الغالب أن تقرأ هذه المعلومة من إعدادات المتصفح أو نظام المستخدم:
// قراءة لغة المتصفح الأولى
const userLocale = navigator.language; // مثال: "ar-SA" أو "ar-EG"
// قراءة كل اللغات المفضّلة مرتبةً
const userLocales = navigator.languages; // مثال: ["ar-SA", "ar", "en-US"]
// استخدام لغة المستخدم مباشرة
const autoFormat = new Intl.NumberFormat(userLocale, {
style: 'currency',
currency: 'SAR', // العملة لا تُكتشف تلقائياً — تحتاج منطقك الخاص
});
console.log(autoFormat.format(1500));
// احتياط: إذا لم تُعرف اللغة، استخدم 'ar' كافتراضي
const safeLocale = navigator.language || 'ar';
لاحظ التمييز المهم: Intl تكتشف تنسيق الأرقام تلقائياً، لكن العملة المناسبة لا تزال قراراً تجارياً تحدده أنت. المستخدم في السعودية قد يشتري بالدولار، والمستخدم في مصر قد يدفع باليورو. هذا التمييز دقيق لكنه حرج في أي نظام دفع.
خامساً: الأداء — متى تُنشئ كائن Intl ومتى تُعيد استخدامه؟
إنشاء كائن Intl.NumberFormat أو Intl.DateTimeFormat له تكلفة حسابية. إذا كنت تنسّق آلاف الأرقام في جدول، لا تنشئ كائناً جديداً لكل خلية:
// ❌ بطيء — ينشئ كائناً جديداً لكل رقم
prices.forEach(price => {
const formatted = new Intl.NumberFormat('ar-SA', {
style: 'currency',
currency: 'SAR'
}).format(price);
});
// ✅ صحيح — أنشئ الكائن مرة واحدة وأعد استخدامه
const sarFormatter = new Intl.NumberFormat('ar-SA', {
style: 'currency',
currency: 'SAR'
});
prices.forEach(price => {
const formatted = sarFormatter.format(price);
});
هذا المبدأ (( أنشئ مرةً واحدة، واستخدم كثيراً)) ينطبق على كل كائنات Intl، ويصنع فارقاً ملحوظاً في الجداول والقوائم الطويلة.
سادساً: جدول مرجعي للـ locales العربية الرئيسية
| الدولة | كود الـ locale | نظام الأرقام | التقويم الافتراضي | رمز العملة |
|---|---|---|---|---|
| المملكة العربية السعودية | ar-SA | هندي (١٢٣) | هجري | SAR |
| الإمارات العربية المتحدة | ar-AE | هندي (١٢٣) | ميلادي | AED |
| مصر | ar-EG | هندي (١٢٣) | ميلادي | EGP |
| المغرب | ar-MA | لاتيني (123) | ميلادي | MAD |
| الجزائر | ar-DZ | لاتيني (123) | ميلادي | DZD |
| الكويت | ar-KW | هندي (١٢٣) | ميلادي | KWD |
هذا الجدول مرجع عملي ستحتاجه حين تُهيّئ نظامك. لاحظ أن السعودية الوحيدة بين هذه الدول التي تعتمد الهجري تقويماً افتراضياً في بيانات CLDR، وهذا يفسر لماذا يحتاج نظام الفواتير السعودي معالجةً خاصةً للتواريخ.
سابعاً: أداة Intl.NumberFormat().formatToParts() — حين تحتاج تحكماً أدق
أحياناً لا تريد النص كاملاً، بل تريد الوصول إلى أجزائه لتُنسّقها بشكلٍ مختلفٍ، مثلاً: جعل رمز العملة بلون مختلف، أو إظهار الجزء العشري بحجم خط أصغر:
const formatter = new Intl.NumberFormat('ar-SA', {
style: 'currency',
currency: 'SAR',
});
const parts = formatter.formatToParts(1234.56);
console.log(parts);
/*
[
{ type: 'currency', value: 'ر.س' },
{ type: 'literal', value: ' ' },
{ type: 'integer', value: '١٬٢٣٤' },
{ type: 'decimal', value: '٫' },
{ type: 'fraction', value: '٥٦' }
]
*/
// مثال: بناء HTML مخصص من الأجزاء
function styledCurrency(amount, locale, currency) {
const parts = new Intl.NumberFormat(locale, {
style: 'currency',
currency,
}).formatToParts(amount);
return parts.map(part => {
if (part.type === 'currency') {
return `${part.value}`;
}
if (part.type === 'fraction') {
return `${part.value}`;
}
return part.value;
}).join('');
}
هذه الطريقة المتقدمة مفيدةٌ في واجهات الفواتير والتقارير المالية حيث يهم التمييز البصري بين أجزاء الرقم.
خلاصة المقال والخطوة القادمة
واجهة Intl تحل المشكلة الظاهرة — كيف تعرض الرقم؟ — لكنها لا تحل المشكلة الخفية: كيف تُخزَّن الأرقام والعملات والنسب المئوية في قاعدة البيانات؟ وأين تضع رمز العملة، قبل الرقم أم بعده؟ وهل النسبة المئوية تُخزَّن كـ 0.15 أم كـ 15؟ هذه المعضلات هي موضوع مقالنا القادم.
الخطوة التالية الموصى بها:
تابع معنا: معضلات الجداول والعملات والنسب المئوية
المراجع والمصادر:
- توثيق MDN —
Intl.NumberFormat: developer.mozilla.org - توثيق MDN —
Intl.DateTimeFormat: developer.mozilla.org - مستودع CLDR — بيانات المناطق الجغرافية: cldr.unicode.org
- دعم المتصفحات لـ Intl API: caniuse.com — Intl.NumberFormat
منصة ذي يزن © ٢٠٢٦
سلاسل التوطين
دليل معايرة البيانات المالية — ٤ مقالات
سلسلة دليل معايرة البيانات المالية — ٤ مقالات | منصة ذي يزن © ٢٠٢٦




