واجهة برمجة التطبيقات 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. يكون type إما "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 له تكلفة إضافية. قم بإنشاء المنسقات مرة واحدة وأعد استخدامها:
// Create once
const listFormatter = new Intl.ListFormat("en", { type: "conjunction" });
// Reuse many times
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 {
// Fallback for older browsers
return items.join(", ");
}
لتوافق أوسع، استخدم polyfill مثل @formatjs/intl-listformat. قم بتثبيته فقط للبيئات التي تحتاج إليه:
if (typeof Intl.ListFormat === "undefined") {
await import("@formatjs/intl-listformat/polyfill");
}
نظرًا لدعم المتصفحات الحالي، يمكن لمعظم التطبيقات استخدام Intl.ListFormat مباشرة دون الحاجة إلى polyfills.
الأخطاء الشائعة التي يجب تجنبها
إنشاء formatters جديدة بشكل متكرر يهدر الموارد:
// Inefficient
function display(items) {
return new Intl.ListFormat("en").format(items);
}
// Efficient
const formatter = new Intl.ListFormat("en");
function display(items) {
return formatter.format(items);
}
استخدام array.join() للنصوص الموجهة للمستخدم يخلق مشاكل في الترجمة:
// Breaks in other languages
const text = items.join(", ");
// Works across languages
const formatter = new Intl.ListFormat(userLocale);
const text = formatter.format(items);
افتراض أن قواعد الربط الإنجليزية تنطبق عالميًا يؤدي إلى مخرجات غير صحيحة في اللغات الأخرى. قم دائمًا بتمرير لغة المستخدم إلى المُنشئ.
عدم معالجة المصفوفات الفارغة يمكن أن يسبب مخرجات غير متوقعة:
// Defensive
function formatItems(items) {
if (items.length === 0) {
return "No items";
}
return formatter.format(items);
}
بينما يُرجع format([]) سلسلة نصية فارغة، فإن معالجة الحالة الفارغة بشكل صريح يحسن تجربة المستخدم.
متى تستخدم Intl.ListFormat
استخدم Intl.ListFormat عند عرض عناصر متعددة في النص. يشمل ذلك مسارات التنقل، والفلاتر المحددة، وأخطاء التحقق، وقوائم المستخدمين، وعلامات الفئات، وقوائم الميزات.
لا تستخدمه لعرض البيانات المنظمة مثل الجداول أو قوائم الخيارات. تلك المكونات لها متطلبات تنسيق خاصة بها خارج قواعد قوائم النص.
تحل واجهة API محل أنماط الدمج والربط اليدوي للسلاسل النصية. في أي وقت تكتب فيه join(", ") للنصوص الموجهة للمستخدم، فكر فيما إذا كان Intl.ListFormat يوفر دعمًا أفضل للغات.