واجهة برمجة Intl.ListFormat

تنسيق المصفوفات إلى قوائم مقروءة متوافقة مع اللغات المحلية

مقدمة

عند عرض عناصر متعددة للمستخدمين، غالبًا ما يقوم المطورون بدمج المصفوفات باستخدام الفواصل وإضافة "و" قبل العنصر الأخير:

const users = ["Alice", "Bob", "Charlie"];
const message = users.slice(0, -1).join(", ") + ", and " + users[users.length - 1];
// "Alice, Bob, and Charlie"

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

ما الذي تفعله Intl.ListFormat

تقوم Intl.ListFormat بتحويل المصفوفات إلى قوائم مقروءة تتبع القواعد النحوية وعلامات الترقيم لأي لغة. وهي تتعامل مع ثلاثة أنواع من القوائم التي تظهر في جميع اللغات:

  • قوائم العطف تستخدم "و" لربط العناصر ("أ، ب، و ج")
  • قوائم الفصل تستخدم "أو" لتقديم البدائل ("أ، ب، أو ج")
  • قوائم الوحدات تنسق القياسات بدون أدوات ربط ("5 قدم، 2 بوصة")

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

الاستخدام الأساسي

قم بإنشاء منسق مع لغة وخيارات، ثم استدعاء format() مع مصفوفة:

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

const items = ["bread", "milk", "eggs"];
console.log(formatter.format(items));
// "bread, milk, and eggs"

يتعامل المنسق مع المصفوفات من أي طول، بما في ذلك الحالات الخاصة:

formatter.format([]);              // ""
formatter.format(["bread"]);       // "bread"
formatter.format(["bread", "milk"]); // "bread and milk"

أنواع القوائم تتحكم في أدوات الربط

يحدد خيار type أداة الربط التي تظهر في القائمة المنسقة.

قوائم العطف

استخدم type: "conjunction" للقوائم التي تنطبق فيها جميع العناصر معًا. هذا هو النوع الافتراضي:

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

console.log(formatter.format(["HTML", "CSS", "JavaScript"]));
// "HTML, CSS, and JavaScript"

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

قوائم الفصل

استخدم type: "disjunction" للقوائم التي تقدم بدائل أو خيارات:

const formatter = new Intl.ListFormat("en", { type: "disjunction" });

console.log(formatter.format(["credit card", "debit card", "PayPal"]));
// "credit card, debit card, or PayPal"

يظهر هذا في قوائم الخيارات، ورسائل الخطأ مع حلول متعددة، وأي سياق يختار فيه المستخدمون عنصرًا واحدًا.

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

استخدم type: "unit" للقياسات والقيم التقنية التي يجب أن تظهر بدون أدوات ربط:

const formatter = new Intl.ListFormat("en", { type: "unit" });

console.log(formatter.format(["5 feet", "2 inches"]));
// "5 feet, 2 inches"

تعمل قوائم الوحدات للقياسات، والمواصفات التقنية، والقيم المركبة.

أنماط القوائم تتحكم في الإطناب

يضبط خيار style مدى إطناب التنسيق. توجد ثلاثة أنماط: long، وshort، وnarrow.

const items = ["Monday", "Wednesday", "Friday"];

const long = new Intl.ListFormat("en", { style: "long" });
console.log(long.format(items));
// "Monday, Wednesday, and Friday"

const short = new Intl.ListFormat("en", { style: "short" });
console.log(short.format(items));
// "Monday, Wednesday, and Friday"

const narrow = new Intl.ListFormat("en", { style: "narrow" });
console.log(narrow.format(items));
// "Monday, Wednesday, Friday"

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

كيف تقوم اللغات المختلفة بتنسيق القوائم

تمتلك كل لغة قواعد تنسيق قوائم مميزة. يتعامل Intl.ListFormat مع هذه الاختلافات تلقائيًا.

تستخدم اللغة الإنجليزية الفواصل والمسافات وأدوات الربط:

const en = new Intl.ListFormat("en");
console.log(en.format(["Tokyo", "Paris", "London"]));
// "Tokyo, Paris, and London"

تستخدم اللغة الألمانية نفس بنية الفواصل ولكن بأدوات ربط مختلفة:

const de = new Intl.ListFormat("de");
console.log(de.format(["Tokyo", "Paris", "London"]));
// "Tokyo, Paris und London"

تستخدم اللغة اليابانية فواصل وأدوات مختلفة:

const ja = new Intl.ListFormat("ja");
console.log(ja.format(["東京", "パリ", "ロンドン"]));
// "東京、パリ、ロンドン"

تستخدم اللغة الصينية علامات ترقيم مختلفة تمامًا:

const zh = new Intl.ListFormat("zh");
console.log(zh.format(["东京", "巴黎", "伦敦"]));
// "东京、巴黎和伦敦"

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

استخدام formatToParts للعرض المخصص

تُرجع طريقة formatToParts() مصفوفة من الكائنات بدلاً من سلسلة نصية. يمثل كل كائن جزءًا واحدًا من القائمة المنسقة:

const formatter = new Intl.ListFormat("en");
const parts = formatter.formatToParts(["red", "green", "blue"]);

console.log(parts);
// [
//   { type: "element", value: "red" },
//   { type: "literal", value: ", " },
//   { type: "element", value: "green" },
//   { type: "literal", value: ", and " },
//   { type: "element", value: "blue" }
// ]

لكل جزء type وvalue. يكون النوع "element" لعناصر القائمة أو "literal" لعلامات الترقيم وأدوات الربط.

تتيح هذه البنية العرض المخصص حيث تحتاج العناصر والحروف إلى أنماط مختلفة:

const formatter = new Intl.ListFormat("en");
const items = ["Alice", "Bob", "Charlie"];

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

console.log(html);
// "<strong>Alice</strong>, <strong>Bob</strong>, and <strong>Charlie</strong>"

يحافظ هذا النهج على علامات الترقيم الصحيحة للغة مع تطبيق العرض المخصص على عناصر القائمة الفعلية.

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

إنشاء نسخ من Intl.ListFormat له تكلفة أداء. قم بإنشاء المنسقات مرة واحدة وأعد استخدامها:

// إنشاء مرة واحدة
const listFormatter = new Intl.ListFormat("en", { type: "conjunction" });

// إعادة الاستخدام عدة مرات
function displayUsers(users) {
  return listFormatter.format(users.map(u => u.name));
}

function displayTags(tags) {
  return listFormatter.format(tags);
}

بالنسبة للتطبيقات ذات اللغات المتعددة، قم بتخزين المنسقات في خريطة:

const formatters = new Map();

function getListFormatter(locale, options) {
  const key = `${locale}-${options.type}-${options.style}`;
  if (!formatters.has(key)) {
    formatters.set(key, new Intl.ListFormat(locale, options));
  }
  return formatters.get(key);
}

const formatter = getListFormatter("en", { type: "conjunction", style: "long" });
console.log(formatter.format(["a", "b", "c"]));

يقلل هذا النمط من تكاليف التهيئة المتكررة مع دعم لغات وتكوينات متعددة.

تنسيق رسائل الخطأ

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

const formatter = new Intl.ListFormat("en", { type: "disjunction" });

function validatePassword(password) {
  const errors = [];

  if (password.length < 8) {
    errors.push("at least 8 characters");
  }
  if (!/[A-Z]/.test(password)) {
    errors.push("an uppercase letter");
  }
  if (!/[0-9]/.test(password)) {
    errors.push("a number");
  }

  if (errors.length > 0) {
    return `Password must contain ${formatter.format(errors)}.`;
  }

  return null;
}

console.log(validatePassword("weak"));
// "Password must contain at least 8 characters, an uppercase letter, or a number."

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

عرض العناصر المحددة

عندما يقوم المستخدمون بتحديد عناصر متعددة، قم بتنسيق التحديد باستخدام قوائم الوصل:

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

function getSelectionMessage(selectedFiles) {
  if (selectedFiles.length === 0) {
    return "No files selected";
  }

  if (selectedFiles.length === 1) {
    return `${selectedFiles[0]} selected`;
  }

  return `${formatter.format(selectedFiles)} selected`;
}

console.log(getSelectionMessage(["report.pdf", "data.csv", "notes.txt"]));
// "report.pdf, data.csv, and notes.txt selected"

يعمل هذا النمط لتحديدات الملفات، واختيارات التصفية، وتحديدات الفئات، وأي واجهة متعددة التحديد.

التعامل مع القوائم الطويلة

بالنسبة للقوائم ذات العناصر الكثيرة، يمكن اعتبار اقتطاع القائمة قبل التنسيق:

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

function formatUserList(users) {
  if (users.length <= 3) {
    return formatter.format(users);
  }

  const visible = users.slice(0, 2);
  const remaining = users.length - 2;

  return `${formatter.format(visible)}, and ${remaining} others`;
}

console.log(formatUserList(["Alice", "Bob", "Charlie", "David", "Eve"]));
// "Alice, Bob, and 3 others"

هذا يحافظ على سهولة القراءة مع الإشارة إلى العدد الإجمالي. يعتمد الحد الدقيق على قيود الواجهة لديك.

دعم المتصفحات والبدائل

تعمل Intl.ListFormat في جميع المتصفحات الحديثة منذ أبريل 2021. يشمل الدعم Chrome 72+، Firefox 78+، Safari 14.1+، و Edge 79+.

تحقق من الدعم باستخدام اكتشاف الميزات:

if (typeof Intl.ListFormat !== "undefined") {
  const formatter = new Intl.ListFormat("en");
  return formatter.format(items);
} else {
  // بديل للمتصفحات القديمة
  return items.join(", ");
}

للحصول على توافق أوسع، استخدم polyfill مثل @formatjs/intl-listformat. قم بتثبيته فقط للبيئات التي تحتاجه:

if (typeof Intl.ListFormat === "undefined") {
  await import("@formatjs/intl-listformat/polyfill");
}

نظرًا لدعم المتصفحات الحالي، يمكن لمعظم التطبيقات استخدام Intl.ListFormat مباشرة بدون polyfills.

أخطاء شائعة يجب تجنبها

إنشاء منسقات جديدة بشكل متكرر يهدر الموارد:

// غير فعّال
function display(items) {
  return new Intl.ListFormat("en").format(items);
}

// فعّال
const formatter = new Intl.ListFormat("en");
function display(items) {
  return formatter.format(items);
}

استخدام array.join() للنصوص المعروضة للمستخدم يخلق مشاكل في الترجمة:

// يتعطل في لغات أخرى
const text = items.join(", ");

// يعمل عبر جميع اللغات
const formatter = new Intl.ListFormat(userLocale);
const text = formatter.format(items);

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

عدم معالجة المصفوفات الفارغة قد يسبب مخرجات غير متوقعة:

// دفاعي
function formatItems(items) {
  if (items.length === 0) {
    return "No items";
  }
  return formatter.format(items);
}

بينما format([]) يعيد سلسلة فارغة، فإن المعالجة الصريحة للحالة الفارغة تحسن تجربة المستخدم.

متى تستخدم Intl.ListFormat

استخدم Intl.ListFormat عند عرض عناصر متعددة في النص. يشمل ذلك مسارات التنقل، والفلاتر المحددة، وأخطاء التحقق، وقوائم المستخدمين، وعلامات الفئات، وقوائم الميزات.

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

تحل واجهة البرمجة هذه محل أنماط الدمج والربط اليدوي للنصوص. في أي وقت تفكر فيه بكتابة join(", ") للنصوص المعروضة للمستخدم، فكر فيما إذا كانت Intl.ListFormat توفر دعمًا أفضل للغات المحلية.