كيفية الحصول على أجزاء فردية من الأرقام المنسقة للعرض المخصص

تقسيم الأرقام المنسقة إلى مكونات لتطبيق تنسيق مخصص وبناء واجهات معقدة

مقدمة

تُرجع الدالة format() سلسلة نصية منسقة كاملة مثل "$1,234.56" أو "1.5M". يعمل هذا بشكل جيد للعرض البسيط، لكن لا يمكنك تنسيق الأجزاء الفردية بشكل مختلف. لا يمكنك جعل رمز العملة غامقاً، أو تلوين الجزء العشري بشكل مختلف، أو تطبيق ترميز مخصص على مكونات محددة.

توفر JavaScript الدالة formatToParts() لحل هذه المشكلة. بدلاً من إرجاع سلسلة نصية واحدة، تُرجع مصفوفة من الكائنات، يمثل كل منها جزءاً واحداً من الرقم المنسق. كل جزء له نوع مثل currency، أو integer، أو decimal، وقيمة مثل $، أو 1234، أو .. يمكنك بعد ذلك معالجة هذه الأجزاء لتطبيق تنسيق مخصص، أو بناء تخطيطات معقدة، أو دمج الأرقام المنسقة في واجهات مستخدم غنية.

لماذا يصعب تخصيص السلاسل النصية المنسقة

عندما تتلقى سلسلة نصية منسقة مثل "$1,234.56"، لا يمكنك تحديد مكان انتهاء رمز العملة وبداية الرقم بسهولة. تضع اللغات المختلفة الرموز في مواضع مختلفة. تستخدم بعض اللغات فواصل مختلفة. يتطلب تحليل هذه السلاسل النصية بشكل موثوق منطقاً معقداً يكرر قواعد التنسيق المطبقة بالفعل في Intl API.

لنأخذ مثالاً على لوحة معلومات تعرض مبالغ نقدية برمز العملة بلون مختلف. مع format()، ستحتاج إلى:

  1. اكتشاف الأحرف التي تمثل رمز العملة
  2. مراعاة المسافات بين الرمز والرقم
  3. التعامل مع مواضع الرموز المختلفة عبر اللغات
  4. تحليل السلسلة النصية بعناية لتجنب كسر الرقم

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

تقضي الطريقة formatToParts() على هذه المشكلة من خلال توفير المكونات بشكل منفصل. تتلقى بيانات منظمة تخبرك بالضبط أي جزء هو أي، بغض النظر عن اللغة.

استخدام formatToParts للحصول على مكونات الأرقام

تعمل الطريقة formatToParts() بشكل مطابق لـ format() باستثناء القيمة المُرجعة. تقوم بإنشاء مُنسِّق بنفس الخيارات، ثم تستدعي formatToParts() بدلاً من format().

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

const parts = formatter.formatToParts(1234.56);
console.log(parts);

ينتج عن هذا مصفوفة من الكائنات:

[
  { type: "currency", value: "$" },
  { type: "integer", value: "1" },
  { type: "group", value: "," },
  { type: "integer", value: "234" },
  { type: "decimal", value: "." },
  { type: "fraction", value: "56" }
]

يحتوي كل كائن على خاصية type تحدد ما يمثله الجزء وخاصية value تحتوي على السلسلة النصية الفعلية. تظهر الأجزاء بنفس الترتيب الذي ستظهر به في المخرجات المنسقة.

يمكنك التحقق من ذلك عن طريق دمج جميع القيم معاً:

const formatted = parts.map(part => part.value).join("");
console.log(formatted);
// Output: "$1,234.56"

تنتج الأجزاء المدمجة نفس المخرجات تماماً كما لو استدعيت format().

فهم أنواع الأجزاء

تحدد خاصية type كل مكون. تنتج خيارات التنسيق المختلفة أنواعاً مختلفة من الأجزاء.

لتنسيق الأرقام الأساسي:

const formatter = new Intl.NumberFormat("en-US");
const parts = formatter.formatToParts(1234.56);
console.log(parts);
// [
//   { type: "integer", value: "1" },
//   { type: "group", value: "," },
//   { type: "integer", value: "234" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "56" }
// ]

يمثل النوع integer الجزء الصحيح من العدد. تظهر عدة أجزاء من نوع integer عندما تقسم فواصل المجموعات العدد. يمثل النوع group فاصل الآلاف. يمثل النوع decimal الفاصلة العشرية. يمثل النوع fraction الأرقام بعد الفاصلة العشرية.

لتنسيق العملة:

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "EUR"
});

const parts = formatter.formatToParts(1234.56);
console.log(parts);
// [
//   { type: "currency", value: "€" },
//   { type: "integer", value: "1" },
//   { type: "group", value: "," },
//   { type: "integer", value: "234" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "56" }
// ]

يظهر النوع currency قبل أو بعد الرقم حسب اصطلاحات اللغة.

للنسب المئوية:

const formatter = new Intl.NumberFormat("en-US", {
  style: "percent"
});

const parts = formatter.formatToParts(0.1234);
console.log(parts);
// [
//   { type: "integer", value: "12" },
//   { type: "percentSign", value: "%" }
// ]

يمثل النوع percentSign رمز النسبة المئوية.

للتدوين المضغوط:

const formatter = new Intl.NumberFormat("en-US", {
  notation: "compact"
});

const parts = formatter.formatToParts(1500000);
console.log(parts);
// [
//   { type: "integer", value: "1" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "5" },
//   { type: "compact", value: "M" }
// ]

يمثل النوع compact مؤشر الحجم مثل K أو M أو B.

تطبيق تنسيق مخصص على أجزاء الأرقام

حالة الاستخدام الأساسية لـ formatToParts() هي تطبيق أنماط مختلفة على مكونات مختلفة. يمكنك معالجة مصفوفة الأجزاء لتغليف أنواع محددة في عناصر HTML.

جعل رمز العملة غامقاً:

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

const parts = formatter.formatToParts(1234.56);
const html = parts
  .map(part => {
    if (part.type === "currency") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "<strong>$</strong>1,234.56"

يعمل هذا النهج مع أي لغة ترميز. يمكنك إنشاء HTML أو JSX أو أي تنسيق آخر من خلال معالجة مصفوفة الأجزاء.

تنسيق الأجزاء العشرية بشكل مختلف:

const formatter = new Intl.NumberFormat("en-US", {
  minimumFractionDigits: 2
});

const parts = formatter.formatToParts(1234.5);
const html = parts
  .map(part => {
    if (part.type === "decimal" || part.type === "fraction") {
      return `<span class="text-gray-500">${part.value}</span>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "1,234<span class="text-gray-500">.50</span>"

هذا النمط شائع في عروض الأسعار حيث يظهر الجزء العشري بحجم أصغر أو بلون أفتح.

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

غالباً ما تعرض التطبيقات المالية الأرقام السالبة باللون الأحمر. باستخدام formatToParts()، يمكنك اكتشاف علامة الطرح وتطبيق التنسيق وفقاً لذلك.

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

function formatWithColor(number) {
  const parts = formatter.formatToParts(number);
  const hasMinusSign = parts.some(part => part.type === "minusSign");

  const html = parts
    .map(part => part.value)
    .join("");

  if (hasMinusSign) {
    return `<span class="text-red-600">${html}</span>`;
  }

  return html;
}

console.log(formatWithColor(-1234.56));
// Output: "<span class="text-red-600">-$1,234.56</span>"

console.log(formatWithColor(1234.56));
// Output: "$1,234.56"

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

بناء عروض أرقام مخصصة بأنماط متعددة

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

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

function formatCurrency(number) {
  const parts = formatter.formatToParts(number);

  return parts
    .map(part => {
      switch (part.type) {
        case "currency":
          return `<span class="currency-symbol">${part.value}</span>`;
        case "integer":
          return `<span class="integer">${part.value}</span>`;
        case "group":
          return `<span class="group">${part.value}</span>`;
        case "decimal":
          return `<span class="decimal">${part.value}</span>`;
        case "fraction":
          return `<span class="fraction">${part.value}</span>`;
        case "minusSign":
          return `<span class="minus">${part.value}</span>`;
        default:
          return part.value;
      }
    })
    .join("");
}

console.log(formatCurrency(1234.56));
// Output: "<span class="currency-symbol">$</span><span class="integer">1</span><span class="group">,</span><span class="integer">234</span><span class="decimal">.</span><span class="fraction">56</span>"

يتيح هذا التحكم الدقيق تنسيقاً محدداً لكل مكون. يمكنك بعد ذلك استخدام CSS لتنسيق كل فئة بشكل مختلف.

جميع أنواع الأجزاء المتاحة

يمكن أن تحتوي الخاصية type على هذه القيم اعتماداً على خيارات التنسيق المستخدمة:

  • integer: أرقام الأعداد الصحيحة
  • fraction: الأرقام العشرية
  • decimal: الفاصل العشري
  • group: فاصل الآلاف
  • currency: رمز العملة
  • literal: مسافات أو نص حرفي آخر مضاف بواسطة التنسيق
  • percentSign: رمز النسبة المئوية
  • minusSign: مؤشر الرقم السالب
  • plusSign: مؤشر الرقم الموجب (عند تعيين signDisplay)
  • unit: سلسلة الوحدة لتنسيق الوحدات
  • compact: مؤشر الحجم في التدوين المضغوط (K، M، B)
  • exponentInteger: قيمة الأس في التدوين العلمي
  • exponentMinusSign: علامة السالب في الأس
  • exponentSeparator: الرمز الفاصل بين المعامل والأس
  • infinity: تمثيل اللانهاية
  • nan: تمثيل ليس رقماً
  • unknown: رموز غير معروفة

لا ينتج كل خيار تنسيق كل نوع جزء. تعتمد الأجزاء التي تتلقاها على قيمة الرقم وإعدادات المنسق.

ينتج الترميز العلمي أجزاء متعلقة بالأس:

const formatter = new Intl.NumberFormat("en-US", {
  notation: "scientific"
});

const parts = formatter.formatToParts(1234);
console.log(parts);
// [
//   { type: "integer", value: "1" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "234" },
//   { type: "exponentSeparator", value: "E" },
//   { type: "exponentInteger", value: "3" }
// ]

تنتج القيم الخاصة أنواع أجزاء محددة:

const formatter = new Intl.NumberFormat("en-US");

console.log(formatter.formatToParts(Infinity));
// [{ type: "infinity", value: "∞" }]

console.log(formatter.formatToParts(NaN));
// [{ type: "nan", value: "NaN" }]

إنشاء عروض أرقام يسهل الوصول إليها

يمكنك استخدام formatToParts() لإضافة سمات إمكانية الوصول إلى الأرقام المنسقة. يساعد هذا قارئات الشاشة على الإعلان عن القيم بشكل صحيح.

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

function formatAccessibleCurrency(number) {
  const parts = formatter.formatToParts(number);
  const formatted = parts.map(part => part.value).join("");

  return `<span aria-label="${number} US dollars">${formatted}</span>`;
}

console.log(formatAccessibleCurrency(1234.56));
// Output: "<span aria-label="1234.56 US dollars">$1,234.56</span>"

يضمن هذا أن تعلن قارئات الشاشة عن كل من قيمة العرض المنسقة والقيمة الرقمية الأساسية مع السياق المناسب.

تمييز نطاقات أرقام محددة

تميز بعض التطبيقات الأرقام التي تقع ضمن نطاقات معينة. باستخدام formatToParts()، يمكنك تطبيق التنسيق بناءً على القيمة مع الحفاظ على التنسيق الصحيح.

const formatter = new Intl.NumberFormat("en-US");

function formatWithThreshold(number, threshold) {
  const parts = formatter.formatToParts(number);
  const formatted = parts.map(part => part.value).join("");

  if (number >= threshold) {
    return `<span class="text-green-600 font-bold">${formatted}</span>`;
  }

  return formatted;
}

console.log(formatWithThreshold(1500, 1000));
// Output: "<span class="text-green-600 font-bold">1,500</span>"

console.log(formatWithThreshold(500, 1000));
// Output: "500"

يتلقى الرقم التنسيق المناسب للغة المحلية بينما يتم تطبيق التنسيق الشرطي بناءً على منطق الأعمال.

متى تستخدم formatToParts مقابل format

استخدم format() عندما تحتاج إلى سلسلة نصية منسقة بسيطة دون أي تخصيص. هذه هي الحالة الشائعة لمعظم عروض الأرقام.

استخدم formatToParts() عندما تحتاج إلى:

  • تطبيق تنسيق مختلف على أجزاء مختلفة من الرقم
  • بناء HTML أو JSX بأرقام منسقة
  • إضافة سمات أو بيانات وصفية إلى مكونات محددة
  • دمج الأرقام المنسقة في تخطيطات معقدة
  • معالجة المخرجات المنسقة برمجياً

تحتوي طريقة formatToParts() على عبء إضافي أكبر قليلاً من format() لأنها تنشئ مصفوفة من الكائنات بدلاً من سلسلة نصية واحدة. هذا الاختلاف ضئيل للتطبيقات النموذجية، ولكن إذا كنت تنسق آلاف الأرقام في الثانية، فإن format() يعمل بشكل أفضل.

بالنسبة لمعظم التطبيقات، اختر بناءً على احتياجات التنسيق الخاصة بك بدلاً من مخاوف الأداء. إذا كنت لا تحتاج إلى تخصيص المخرجات، استخدم format(). إذا كنت بحاجة إلى تنسيق أو ترميز مخصص، استخدم formatToParts().

كيف تحافظ الأجزاء على التنسيق الخاص بالإعدادات المحلية

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

const usdFormatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

console.log(usdFormatter.formatToParts(1234.56));
// [
//   { type: "currency", value: "$" },
//   { type: "integer", value: "1" },
//   { type: "group", value: "," },
//   { type: "integer", value: "234" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "56" }
// ]

const eurFormatter = new Intl.NumberFormat("de-DE", {
  style: "currency",
  currency: "EUR"
});

console.log(eurFormatter.formatToParts(1234.56));
// [
//   { type: "integer", value: "1" },
//   { type: "group", value: "." },
//   { type: "integer", value: "234" },
//   { type: "decimal", value: "," },
//   { type: "fraction", value: "56" },
//   { type: "literal", value: " " },
//   { type: "currency", value: "€" }
// ]

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

يمثل نوع literal أي مسافات أو نص يتم إدراجه بواسطة المنسق ولا يتناسب مع الفئات الأخرى. في تنسيق العملة الألمانية، يمثل المسافة بين الرقم ورمز العملة.

دمج formatToParts مع مكونات الإطار

يمكن لأطر العمل الحديثة مثل React استخدام formatToParts() لبناء المكونات بكفاءة.

function CurrencyDisplay({ value, locale, currency }) {
  const formatter = new Intl.NumberFormat(locale, {
    style: "currency",
    currency: currency
  });

  const parts = formatter.formatToParts(value);

  return (
    <span className="currency-display">
      {parts.map((part, index) => {
        if (part.type === "currency") {
          return <strong key={index}>{part.value}</strong>;
        }
        if (part.type === "fraction" || part.type === "decimal") {
          return <span key={index} className="text-sm text-gray-500">{part.value}</span>;
        }
        return <span key={index}>{part.value}</span>;
      })}
    </span>
  );
}

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