كيفية تقسيم المخرجات المنسقة إلى أجزاء لتخصيص التنسيق

استخدم الدالة formatToParts() للوصول إلى مكونات المخرجات المنسقة وتطبيق تنسيق مخصص على كل جزء

المقدمة

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

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

الدالة formatToParts() متوفرة في العديد من أدوات Intl في JavaScript، مثل NumberFormat وDateTimeFormat وListFormat وRelativeTimeFormat وDurationFormat. هذا يجعل استخدامها أسلوباً موحداً في جميع عمليات التنسيق الدولية في JavaScript.

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

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

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

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

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

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

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

كيف يعمل formatToParts

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

تقوم الطريقة بإرجاع مصفوفة من الكائنات. كل كائن يحتوي على خاصيتين:

  • type: يحدد ماذا يمثل الجزء، مثل currency أو month أو literal
  • value: يحتوي على السلسلة الفعلية لهذا الجزء

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

هذا النمط ثابت عبر جميع المكوّنات التي تدعم formatToParts(). تختلف أنواع الأجزاء حسب المكوّن، لكن البنية نفسها تظل كما هي.

تقسيم الأرقام المنسقة إلى أجزاء

يقدم مكوّن NumberFormat طريقة formatToParts() لتقسيم الأرقام المنسقة. يعمل ذلك مع الأرقام الأساسية والعملات والنسب المئوية وأنواع أخرى من الأنماط الرقمية.

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" }
]

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

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

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

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

تنسيق رموز العملات داخل الأرقام المنسقة

الاستخدام الرئيسي لـ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>"

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

تقسيم التواريخ المنسقة إلى أجزاء

المُنسق DateTimeFormat يوفر formatToParts() لتجزئة التواريخ والأوقات المنسقة.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
console.log(parts);

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

[
  { type: "month", value: "January" },
  { type: "literal", value: " " },
  { type: "day", value: "15" },
  { type: "literal", value: ", " },
  { type: "year", value: "2025" }
]

نوع month يمثل اسم أو رقم الشهر. نوع day يمثل يوم الشهر. نوع year يمثل السنة. نوع literal يمثل المسافات أو علامات الترقيم أو أي نص آخر يُدرجه المنسق.

تنسيق أسماء الأشهر داخل التواريخ المنسقة

يمكنك تطبيق أنماط مخصصة على مكونات التاريخ بنفس الأسلوب المتبع مع الأرقام.

جعل اسم الشهر عريضًا:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
const html = parts
  .map(part => {
    if (part.type === "month") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "<strong>January</strong> 15, 2025"

تنسيق عدة مكونات من التاريخ:

const formatter = new Intl.DateTimeFormat("en-US", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);

const html = parts
  .map(part => {
    switch (part.type) {
      case "weekday":
        return `<span class="font-bold">${part.value}</span>`;
      case "month":
        return `<span class="text-blue-600">${part.value}</span>`;
      case "year":
        return `<span class="text-gray-500">${part.value}</span>`;
      default:
        return part.value;
    }
  })
  .join("");

console.log(html);
// Output: "<span class="font-bold">Wednesday</span>, <span class="text-blue-600">January</span> 15, <span class="text-gray-500">2025</span>"

هذا التحكم الدقيق يتيح لك تخصيص تنسيق كل مكون بدقة.

تقسيم القوائم المنسقة إلى أجزاء

المُنسق ListFormat يقدم formatToParts() لتقسيم القوائم المنسقة.

const formatter = new Intl.ListFormat("en-US", {
  style: "long",
  type: "conjunction"
});

const items = ["apples", "oranges", "bananas"];
const parts = formatter.formatToParts(items);
console.log(parts);

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

[
  { type: "element", value: "apples" },
  { type: "literal", value: ", " },
  { type: "element", value: "oranges" },
  { type: "literal", value: ", and " },
  { type: "element", value: "bananas" }
]

النوع element يمثل كل عنصر في القائمة. أما النوع literal فيمثل الفواصل والعطف التي يضيفها المُنسق.

تنسيق عناصر القائمة بشكل منفصل

يمكنك تطبيق أنماط مخصصة على عناصر القائمة باستخدام نفس النمط.

جعل عناصر القائمة غامقة:

const formatter = new Intl.ListFormat("en-US", {
  style: "long",
  type: "conjunction"
});

const items = ["apples", "oranges", "bananas"];
const parts = formatter.formatToParts(items);
const html = parts
  .map(part => {
    if (part.type === "element") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "<strong>apples</strong>, <strong>oranges</strong>, and <strong>bananas</strong>"

تخصيص تنسيق عناصر معينة من القائمة:

const formatter = new Intl.ListFormat("en-US", {
  style: "long",
  type: "conjunction"
});

const items = ["apples", "oranges", "bananas"];
const parts = formatter.formatToParts(items);

let itemIndex = 0;
const html = parts
  .map(part => {
    if (part.type === "element") {
      const currentIndex = itemIndex++;
      if (currentIndex === 0) {
        return `<span class="text-green-600">${part.value}</span>`;
      }
      return part.value;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "<span class="text-green-600">apples</span>, oranges, and bananas"

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

تقسيم صيغ الوقت النسبي المنسقة إلى أجزاء

المُنسق RelativeTimeFormat يقدم formatToParts() لتقسيم تعبيرات الوقت النسبي.

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

const parts = formatter.formatToParts(-1, "day");
console.log(parts);

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

[
  { type: "literal", value: "yesterday" }
]

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

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

const parts = formatter.formatToParts(-3, "day");
console.log(parts);
// [
//   { type: "integer", value: "3" },
//   { type: "literal", value: " days ago" }
// ]

النوع integer يمثل القيمة الرقمية. أما النوع literal فيمثل وحدة الوقت النسبي والاتجاه.

تقسيم مدد الوقت المنسقة إلى أجزاء

المُنسق DurationFormat يقدم formatToParts() لتقسيم مدد الوقت المنسقة.

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

const parts = formatter.formatToParts({
  hours: 2,
  minutes: 30,
  seconds: 15
});
console.log(parts);

يُنتِج هذا مصفوفة من الكائنات مشابهة لـ:

[
  { type: "integer", value: "2" },
  { type: "literal", value: " hours, " },
  { type: "integer", value: "30" },
  { type: "literal", value: " minutes, " },
  { type: "integer", value: "15" },
  { type: "literal", value: " seconds" }
]

يمثل نوع integer القيم الرقمية. بينما يمثل نوع literal أسماء الوحدات والفواصل.

إنشاء HTML من أجزاء منسّقة

يمكنك إنشاء دالة قابلة لإعادة الاستخدام لمعالجة الأجزاء وتطبيق قواعد التنسيق بشكل متسق.

function formatWithStyles(parts, styleMap) {
  return parts
    .map(part => {
      const style = styleMap[part.type];
      if (style) {
        return `<span class="${style}">${part.value}</span>`;
      }
      return part.value;
    })
    .join("");
}

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

const parts = numberFormatter.formatToParts(1234.56);
const html = formatWithStyles(parts, {
  currency: "font-bold text-gray-700",
  integer: "text-2xl",
  fraction: "text-sm text-gray-500"
});

console.log(html);
// Output: "<span class="font-bold text-gray-700">$</span><span class="text-2xl">1</span>,<span class="text-2xl">234</span>.<span class="text-sm text-gray-500">56</span>"

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

فهم ترتيب الأجزاء الخاص بالمنطقة

تقوم مصفوفة الأجزاء بتطبيق قواعد التنسيق حسب المنطقة تلقائيًا. تختلف طريقة ترتيب المكوّنات وتنسيقها من منطقة لأخرى، لكن 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: "€" }
// ]

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

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

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

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

function formatAccessible(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(formatAccessible(1234.56));
// Output: "<span aria-label="1234.56 US dollars">$1,234.56</span>"

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

دمج 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>
  );
}

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

متى تستخدم formatToParts

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

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

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

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

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

أنواع الأجزاء الشائعة عبر أدوات التنسيق

تنتج أدوات التنسيق المختلفة أنواعًا متنوعة من الأجزاء، لكن بعض الأنواع تظهر في أكثر من أداة تنسيق:

  • literal: الفراغات أو علامات الترقيم أو أي نص آخر يُضاف أثناء التنسيق. تظهر في التواريخ، الأرقام، القوائم، والفترات الزمنية.
  • integer: أرقام كاملة. تظهر في الأرقام والأزمنة النسبية والفترات الزمنية.
  • decimal: فاصلة عشرية. تظهر في الأرقام.
  • fraction: أرقام عشرية. تظهر في الأرقام.

أنواع خاصة لكل أداة تشمل:

  • الأرقام: currency، group، percentSign، minusSign، plusSign، unit، compact، exponentInteger
  • التواريخ: weekday، era، year، month، day، hour، minute، second، dayPeriod، timeZoneName
  • القوائم: element
  • الأزمنة النسبية: القيم الرقمية تظهر كـ integer، والنصوص تظهر كـ literal

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