كيفية تنسيق القوائم باستخدام "أو" في JavaScript
استخدم Intl.ListFormat مع نوع الفصل لتنسيق البدائل بشكل صحيح في أي لغة
مقدمة
غالبًا ما تقدم التطبيقات للمستخدمين خيارات أو بدائل. يقبل مكون تحميل الملفات ملفات "PNG أو JPEG أو SVG". يسمح نموذج الدفع بـ "بطاقة ائتمان أو بطاقة خصم أو PayPal" كطرق دفع. تقترح رسالة خطأ إصلاح "اسم المستخدم أو كلمة المرور أو عنوان البريد الإلكتروني" لحل فشل المصادقة.
تستخدم هذه القوائم "أو" للإشارة إلى البدائل. يؤدي تنسيقها يدويًا باستخدام دمج السلاسل النصية إلى فشل في اللغات الأخرى لأن اللغات المختلفة لها قواعد ترقيم مختلفة، وكلمات مختلفة لـ "أو"، واصطلاحات مختلفة لوضع الفواصل. تقوم واجهة برمجة التطبيقات Intl.ListFormat مع نوع الفصل بتنسيق قوائم البدائل هذه بشكل صحيح لأي لغة.
ما هي القوائم الفصلية
تقدم القائمة الفصلية بدائل حيث ينطبق عادةً خيار واحد. تعني كلمة "فصل" الانفصال أو البدائل. في اللغة الإنجليزية، تستخدم القوائم الفصلية "or" كأداة عطف:
const paymentMethods = ["credit card", "debit card", "PayPal"];
// Desired output: "credit card, debit card, or PayPal"
يختلف هذا عن القوائم الوصلية التي تستخدم "and" للإشارة إلى أن جميع العناصر تنطبق معًا. تنقل القوائم الفصلية الاختيار، بينما تنقل القوائم الوصلية الجمع.
تتضمن السياقات الشائعة للقوائم الفصلية خيارات الدفع، وقيود تنسيق الملفات، واقتراحات استكشاف الأخطاء وإصلاحها، وبدائل مرشحات البحث، وأي واجهة يختار فيها المستخدمون خيارًا واحدًا من عدة إمكانيات.
لماذا يفشل التنسيق اليدوي
يكتب المتحدثون بالإنجليزية القوائم الفصلية على شكل "A, B, or C" مع فواصل بين العناصر و"or" قبل العنصر الأخير. ينكسر هذا النمط في اللغات الأخرى:
// Hardcoded English pattern
const items = ["apple", "orange", "banana"];
const text = items.slice(0, -1).join(", ") + ", or " + items[items.length - 1];
// "apple, orange, or banana"
ينتج هذا الكود مخرجات غير صحيحة في الإسبانية والفرنسية والألمانية ومعظم اللغات الأخرى. كل لغة لها قواعد تنسيق مميزة للقوائم الفصلية.
تستخدم الإسبانية "o" بدون فاصلة قبلها:
Expected: "manzana, naranja o plátano"
English pattern produces: "manzana, naranja, or plátano"
تستخدم الفرنسية "ou" بدون فاصلة قبلها:
Expected: "pomme, orange ou banane"
English pattern produces: "pomme, orange, or banane"
تستخدم الألمانية "oder" بدون فاصلة قبلها:
Expected: "Apfel, Orange oder Banane"
English pattern produces: "Apfel, Orange, or Banane"
تستخدم اليابانية الأداة "か" (ka) مع علامات ترقيم مختلفة:
Expected: "りんご、オレンジ、またはバナナ"
English pattern produces: "りんご、オレンジ、 or バナナ"
تتجاوز هذه الاختلافات مجرد استبدال الكلمات. فموضع علامات الترقيم وقواعد المسافات والأدوات النحوية تختلف جميعها حسب اللغة. لا يمكن لدمج السلاسل النصية يدويًا التعامل مع هذا التعقيد.
استخدام Intl.ListFormat مع نوع الفصل
تقوم واجهة برمجة التطبيقات Intl.ListFormat بتنسيق القوائم وفقًا لقواعد خاصة باللغة. اضبط خيار type على "disjunction" لتنسيق القوائم البديلة:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
const paymentMethods = ["credit card", "debit card", "PayPal"];
console.log(formatter.format(paymentMethods));
// "credit card, debit card, or PayPal"
يتعامل المنسق مع أي طول للمصفوفة:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
console.log(formatter.format([]));
// ""
console.log(formatter.format(["credit card"]));
// "credit card"
console.log(formatter.format(["credit card", "PayPal"]));
// "credit card or PayPal"
console.log(formatter.format(["credit card", "debit card", "PayPal"]));
// "credit card, debit card, or PayPal"
تطبق واجهة برمجة التطبيقات تلقائيًا علامات الترقيم وأدوات العطف الصحيحة لكل حالة.
فهم أنماط الفصل
يتحكم خيار style في مستوى التفصيل في التنسيق. توجد ثلاثة أنماط: long، وshort، وnarrow. النمط long هو الافتراضي.
const items = ["email", "phone", "SMS"];
const long = new Intl.ListFormat("en", {
type: "disjunction",
style: "long"
});
console.log(long.format(items));
// "email, phone, or SMS"
const short = new Intl.ListFormat("en", {
type: "disjunction",
style: "short"
});
console.log(short.format(items));
// "email, phone, or SMS"
const narrow = new Intl.ListFormat("en", {
type: "disjunction",
style: "narrow"
});
console.log(narrow.format(items));
// "email, phone, or SMS"
في الإنجليزية، تنتج الأنماط الثلاثة جميعها مخرجات متطابقة للقوائم الفاصلة. تظهر اللغات الأخرى تنوعًا أكبر. تستخدم الألمانية "oder" في النمط الطويل وقد تختصر في النمط الضيق. تصبح الاختلافات أكثر وضوحًا في اللغات ذات مستويات الرسمية المتعددة أو أدوات العطف الأطول.
يزيل النمط الضيق عادةً المسافات أو يستخدم أدوات عطف أقصر لتوفير المساحة في التخطيطات المقيدة. استخدم النمط الطويل للنص القياسي، والنمط القصير للعروض المدمجة بشكل معتدل، والنمط الضيق لقيود المساحة الضيقة مثل واجهات الهاتف المحمول أو الجداول المدمجة.
كيف تظهر القوائم الفاصلة في اللغات المختلفة
تقوم كل لغة بتنسيق القوائم الفصلية وفقاً لاتفاقياتها الخاصة. يتعامل Intl.ListFormat مع هذه الاختلافات تلقائياً.
تستخدم الإنجليزية الفواصل مع "or":
const en = new Intl.ListFormat("en", { type: "disjunction" });
console.log(en.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG, or SVG"
تستخدم الإسبانية الفواصل مع "o" وبدون فاصلة قبل حرف العطف الأخير:
const es = new Intl.ListFormat("es", { type: "disjunction" });
console.log(es.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG o SVG"
تستخدم الفرنسية الفواصل مع "ou" وبدون فاصلة قبل حرف العطف الأخير:
const fr = new Intl.ListFormat("fr", { type: "disjunction" });
console.log(fr.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG ou SVG"
تستخدم الألمانية الفواصل مع "oder" وبدون فاصلة قبل حرف العطف الأخير:
const de = new Intl.ListFormat("de", { type: "disjunction" });
console.log(de.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG oder SVG"
تستخدم اليابانية علامات ترقيم وجزيئات مختلفة:
const ja = new Intl.ListFormat("ja", { type: "disjunction" });
console.log(ja.format(["PNG", "JPEG", "SVG"]));
// "PNG、JPEG、またはSVG"
تستخدم الصينية علامات الترقيم الصينية:
const zh = new Intl.ListFormat("zh", { type: "disjunction" });
console.log(zh.format(["PNG", "JPEG", "SVG"]));
// "PNG、JPEG或SVG"
توضح هذه الأمثلة كيف تتكيف واجهة برمجة التطبيقات مع الاتفاقيات النحوية وعلامات الترقيم لكل لغة. يعمل نفس الكود عبر جميع اللغات عندما تقدم اللغة المحلية المناسبة.
تنسيق خيارات الدفع
تعرض نماذج الدفع خيارات متعددة لطرق الدفع. قم بتنسيقها باستخدام القوائم الفصلية:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function getPaymentMessage(methods) {
if (methods.length === 0) {
return "No payment methods available";
}
return `Pay with ${formatter.format(methods)}.`;
}
const methods = ["credit card", "debit card", "PayPal", "Apple Pay"];
console.log(getPaymentMessage(methods));
// "Pay with credit card, debit card, PayPal, or Apple Pay."
بالنسبة للتطبيقات الدولية، قم بتمرير اللغة المحلية للمستخدم:
const userLocale = navigator.language; // e.g., "fr-FR"
const formatter = new Intl.ListFormat(userLocale, { type: "disjunction" });
function getPaymentMessage(methods) {
if (methods.length === 0) {
return "No payment methods available";
}
return `Pay with ${formatter.format(methods)}.`;
}
يعمل هذا النهج في تدفقات الدفع ومحددات طرق الدفع وأي واجهة يختار فيها المستخدمون كيفية الدفع.
تنسيق قيود رفع الملفات
تحدد مكونات رفع الملفات أنواع الملفات التي يقبلها النظام:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function getAcceptedFormatsMessage(formats) {
if (formats.length === 0) {
return "No file formats accepted";
}
if (formats.length === 1) {
return `Accepted format: ${formats[0]}`;
}
return `Accepted formats: ${formatter.format(formats)}`;
}
const imageFormats = ["PNG", "JPEG", "SVG", "WebP"];
console.log(getAcceptedFormatsMessage(imageFormats));
// "Accepted formats: PNG, JPEG, SVG, or WebP"
const documentFormats = ["PDF", "DOCX"];
console.log(getAcceptedFormatsMessage(documentFormats));
// "Accepted formats: PDF or DOCX"
يعمل هذا النمط لرفع الصور وإرسال المستندات وأي إدخال ملف مع قيود التنسيق.
تنسيق اقتراحات استكشاف الأخطاء وإصلاحها
غالبًا ما تقترح رسائل الخطأ طرقًا متعددة لحل المشكلة. اعرض هذه الاقتراحات كقوائم فصلية:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function getAuthenticationError(missingFields) {
if (missingFields.length === 0) {
return "Authentication failed";
}
return `Please check your ${formatter.format(missingFields)} and try again.`;
}
console.log(getAuthenticationError(["username", "password"]));
// "Please check your username or password and try again."
console.log(getAuthenticationError(["email", "username", "password"]));
// "Please check your email, username, or password and try again."
توضح القائمة الفصلية أن المستخدمين بحاجة إلى إصلاح أي من الحقول المذكورة، وليس بالضرورة جميعها.
تنسيق بدائل مرشحات البحث
تعرض واجهات البحث المرشحات النشطة. عندما تقدم المرشحات بدائل، استخدم القوائم الفصلية:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function getFilterSummary(filters) {
if (filters.length === 0) {
return "No filters applied";
}
if (filters.length === 1) {
return `Showing results for: ${filters[0]}`;
}
return `Showing results for: ${formatter.format(filters)}`;
}
const categories = ["Electronics", "Books", "Clothing"];
console.log(getFilterSummary(categories));
// "Showing results for: Electronics, Books, or Clothing"
يعمل هذا مع مرشحات الفئات واختيارات الوسوم وأي واجهة مرشحات حيث تمثل القيم المحددة بدائل بدلاً من مجموعات.
إعادة استخدام المنسقات لتحسين الأداء
إنشاء مثيلات Intl.ListFormat له تكلفة إضافية. أنشئ المنسقات مرة واحدة وأعد استخدامها:
// Create once at module level
const disjunctionFormatter = new Intl.ListFormat("en", { type: "disjunction" });
// Reuse in multiple functions
function formatPaymentMethods(methods) {
return disjunctionFormatter.format(methods);
}
function formatFileTypes(types) {
return disjunctionFormatter.format(types);
}
function formatErrorSuggestions(suggestions) {
return disjunctionFormatter.format(suggestions);
}
بالنسبة للتطبيقات التي تدعم لغات متعددة، قم بتخزين المنسقات في ذاكرة تخزين مؤقت:
const formatters = new Map();
function getDisjunctionFormatter(locale) {
if (!formatters.has(locale)) {
formatters.set(
locale,
new Intl.ListFormat(locale, { type: "disjunction" })
);
}
return formatters.get(locale);
}
const formatter = getDisjunctionFormatter("en");
console.log(formatter.format(["A", "B", "C"]));
// "A, B, or C"
يقلل هذا النمط من تكاليف التهيئة مع دعم لغات متعددة في جميع أنحاء التطبيق.
استخدام formatToParts للعرض المخصص
تُرجع الدالة formatToParts() مصفوفة من الكائنات تمثل كل جزء من القائمة المنسقة. يتيح هذا تطبيق أنماط مخصصة:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
const parts = formatter.formatToParts(["PNG", "JPEG", "SVG"]);
console.log(parts);
// [
// { type: "element", value: "PNG" },
// { type: "literal", value: ", " },
// { type: "element", value: "JPEG" },
// { type: "literal", value: ", or " },
// { type: "element", value: "SVG" }
// ]
يحتوي كل جزء على type وvalue. يكون type إما "element" لعناصر القائمة أو "literal" لعلامات الترقيم وأدوات العطف.
استخدم هذا لتطبيق أنماط مختلفة على العناصر والحرفيات:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
const formats = ["PNG", "JPEG", "SVG"];
const html = formatter.formatToParts(formats)
.map(part => {
if (part.type === "element") {
return `<code>${part.value}</code>`;
}
return part.value;
})
.join("");
console.log(html);
// "<code>PNG</code>, <code>JPEG</code>, or <code>SVG</code>"
يحافظ هذا النهج على علامات الترقيم وأدوات العطف الصحيحة للغة مع تطبيق عرض مخصص على العناصر الفعلية.
دعم المتصفحات والتوافق
تعمل Intl.ListFormat في جميع المتصفحات الحديثة منذ أبريل 2021. يشمل الدعم Chrome 72+ وFirefox 78+ وSafari 14.1+ وEdge 79+.
تحقق من الدعم قبل استخدام الواجهة البرمجية:
if (typeof Intl.ListFormat !== "undefined") {
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
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");
}
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
نظرًا لدعم المتصفحات الحالي، يمكن لمعظم التطبيقات استخدام Intl.ListFormat مباشرة دون الحاجة إلى polyfills.
الأخطاء الشائعة التي يجب تجنبها
استخدام نوع الربط بدلاً من الفصل ينتج عنه معنى غير صحيح:
// Wrong: suggests all methods required
const wrong = new Intl.ListFormat("en", { type: "conjunction" });
console.log(`Pay with ${wrong.format(["credit card", "debit card"])}`);
// "Pay with credit card and debit card"
// Correct: suggests choosing one method
const correct = new Intl.ListFormat("en", { type: "disjunction" });
console.log(`Pay with ${correct.format(["credit card", "debit card"])}`);
// "Pay with credit card or debit card"
إنشاء منسقات جديدة بشكل متكرر يهدر الموارد:
// Inefficient
function formatOptions(options) {
return new Intl.ListFormat("en", { type: "disjunction" }).format(options);
}
// Efficient
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function formatOptions(options) {
return formatter.format(options);
}
كتابة "أو" بشكل ثابت في النصوص يمنع الترجمة:
// Breaks in other languages
const text = items.join(", ") + ", or other options";
// Works across languages
const formatter = new Intl.ListFormat(userLocale, { type: "disjunction" });
const allItems = [...items, "other options"];
const text = formatter.format(allItems);
عدم معالجة المصفوفات الفارغة قد يسبب مخرجات غير متوقعة:
// Defensive
function formatPaymentMethods(methods) {
if (methods.length === 0) {
return "No payment methods available";
}
return formatter.format(methods);
}
بينما يُرجع format([]) نصًا فارغًا، فإن معالجة الحالة الفارغة بشكل صريح يحسن تجربة المستخدم.
متى تستخدم القوائم الفصلية
استخدم القوائم الفصلية عند تقديم بدائل أو خيارات حيث ينطبق عادةً خيار واحد. يشمل ذلك اختيار طريقة الدفع، وقيود تنسيق الملفات، واقتراحات أخطاء المصادقة، وخيارات تصفية البحث، وخيارات نوع الحساب.
لا تستخدم القوائم الفصلية عندما يجب تطبيق جميع العناصر معًا. استخدم قوائم الربط بدلاً من ذلك. على سبيل المثال، "الاسم والبريد الإلكتروني وكلمة المرور مطلوبة" يستخدم الربط لأن جميع الحقول يجب توفيرها، وليس واحدًا فقط.
لا تستخدم القوائم الفصلية للتعدادات المحايدة دون دلالات الاختيار. عادةً ما تستخدم القياسات والمواصفات التقنية قوائم الوحدات بدلاً من الفصل أو الربط.
تحل واجهة API محل أنماط دمج النصوص اليدوية للبدائل. في أي وقت تكتب فيه كودًا يربط العناصر بـ "أو" للنصوص الموجهة للمستخدم، فكر فيما إذا كان Intl.ListFormat مع نوع الفصل يوفر دعمًا أفضل للغات المختلفة.