كيفية اختيار صيغة الجمع للنطاقات مثل 1-3 عناصر
استخدم جافا سكريبت لتحديد صيغة الجمع الصحيحة عند عرض نطاقات الأرقام
مقدمة
تُوصل النطاقات أن قيمة ما تقع بين نقطتي نهاية. تعرض واجهات المستخدم النطاقات في سياقات مثل نتائج البحث التي تظهر "تم العثور على 10-15 تطابقًا"، أو أنظمة المخزون التي تظهر "1-3 عناصر متاحة"، أو المرشحات التي تظهر "حدد 2-5 خيارات". تجمع هذه النطاقات بين رقمين مع نص وصفي يجب أن يتوافق نحويًا مع النطاق.
عند عرض عدد واحد، تختار بين صيغ المفرد والجمع: "عنصر واحد" مقابل "عنصران" أو "عناصر". تمتلك اللغات قواعد تحدد الصيغة المناسبة بناءً على العدد. تختلف هذه القواعد حسب اللغة. تستخدم اللغة الإنجليزية المفرد للواحد والجمع لجميع الأعداد الأخرى. تستخدم اللغة البولندية صيغًا مختلفة للعدد 1، والأعداد 2-4، و5 أو أكثر. تحتوي اللغة العربية على ست صيغ متميزة بناءً على العدد.
تقدم النطاقات تحديًا مختلفًا. تعتمد صيغة الجمع على قيمتي البداية والنهاية، وليس على رقم واحد فقط. في اللغة الإنجليزية، "1-2 items" تستخدم الجمع حتى لو بدأ النطاق بالرقم 1. تمتلك اللغات المختلفة قواعد مختلفة لتحديد صيغة الجمع التي تنطبق على النطاق. تتعامل طريقة selectRange() في Intl.PluralRules مع هذه القواعد الخاصة باللغة تلقائيًا.
لماذا تحتاج النطاقات إلى قواعد جمع مختلفة
استخدام طريقة select() على رقم واحد من نطاق لا يعمل بشكل صحيح لجميع اللغات. قد تفكر في استخدام القيمة النهائية للنطاق، لكن هذا ينتج نتائج غير صحيحة في العديد من اللغات.
فكر في اللغة الإنجليزية مع النطاق 0-1. استخدام select() على القيمة النهائية يعيد "one"، مما يشير إلى أنه يجب عليك عرض "0-1 item". هذا غير صحيح نحويًا. الصيغة الصحيحة هي "0-1 items" بالجمع.
const rules = new Intl.PluralRules("en-US");
console.log(rules.select(1));
// الناتج: "one"
// لكن "0-1 item" غير صحيح
// الصحيح: "0-1 items"
تمتلك اللغات المختلفة قواعد صريحة للنطاقات لا تتطابق مع قواعدها للأعداد الفردية. في اللغة السلوفينية، يستخدم النطاق 102-201 صيغة "few"، بينما تستخدم الأرقام الفردية في ذلك النطاق صيغًا مختلفة.
const slRules = new Intl.PluralRules("sl");
console.log(slRules.select(102));
// الناتج: "few"
console.log(slRules.select(201));
// الناتج: "few"
console.log(slRules.selectRange(102, 201));
// الناتج: "few"
تستخدم بعض اللغات قيمة البداية لتحديد الصيغة، وتستخدم أخرى قيمة النهاية، وتستخدم أخرى كلتا القيمتين معًا. تغلف طريقة selectRange() هذه القواعد الخاصة باللغة حتى لا تحتاج إلى تنفيذها يدويًا.
إنشاء نسخة PluralRules للنطاقات
قم بإنشاء نسخة من Intl.PluralRules بنفس الطريقة التي تستخدمها للأعداد المفردة. توفر النسخة كلاً من select() للأرقام المفردة وselectRange() للنطاقات.
const rules = new Intl.PluralRules("en-US");
يمكنك تحديد خيارات عند إنشاء النسخة. تنطبق هذه الخيارات على كل من الأعداد المفردة والنطاقات.
const rules = new Intl.PluralRules("en-US", {
type: "cardinal"
});
الخيار type يكون افتراضيًا "cardinal"، والذي يتعامل مع عد الكائنات. يمكنك أيضًا استخدام "ordinal" للأرقام الترتيبية، على الرغم من أن نطاقات الأرقام الترتيبية أقل شيوعًا في واجهات المستخدم.
أعد استخدام نفس النسخة عبر استدعاءات متعددة. إنشاء نسخة جديدة لكل عملية جمع هو إهدار للموارد. قم بتخزين النسخة في متغير أو تخزينها مؤقتًا حسب اللغة.
استخدام selectRange لتحديد فئة الجمع للنطاقات
تأخذ طريقة selectRange() رقمين يمثلان بداية ونهاية النطاق. تُرجع سلسلة نصية تشير إلى فئة الجمع المطبقة: "zero" أو "one" أو "two" أو "few" أو "many" أو "other".
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(0, 1));
// الناتج: "other"
console.log(rules.selectRange(1, 2));
// الناتج: "other"
console.log(rules.selectRange(5, 10));
// الناتج: "other"
في اللغة الإنجليزية، تستخدم النطاقات دائمًا تقريبًا فئة "other"، والتي تتوافق مع صيغة الجمع. هذا يتطابق مع كيفية تعبير متحدثي اللغة الإنجليزية بشكل طبيعي عن النطاقات باستخدام الأسماء الجمع.
اللغات ذات صيغ الجمع الأكثر تُرجع فئات مختلفة بناءً على قواعدها المحددة.
const arRules = new Intl.PluralRules("ar-EG");
console.log(arRules.selectRange(0, 0));
// الناتج: "zero"
console.log(arRules.selectRange(1, 1));
// الناتج: "one"
console.log(arRules.selectRange(2, 2));
// الناتج: "two"
console.log(arRules.selectRange(3, 10));
// الناتج: "few"
القيمة المرجعة هي دائمًا واحدة من أسماء فئات الجمع القياسية الستة. يقوم الكود الخاص بك بربط هذه الفئات بالنص المترجم المناسب.
ربط فئات النطاق بالسلاسل النصية المترجمة
قم بتخزين الأشكال النصية لكل فئة جمع في بنية بيانات. استخدم الفئة التي تعيدها دالة selectRange() للبحث عن النص المناسب.
const rules = new Intl.PluralRules("en-US");
const forms = new Map([
["one", "item"],
["other", "items"]
]);
function formatRange(start, end) {
const category = rules.selectRange(start, end);
const form = forms.get(category);
return `${start}-${end} ${form}`;
}
console.log(formatRange(1, 3));
// Output: "1-3 items"
console.log(formatRange(0, 1));
// Output: "0-1 items"
console.log(formatRange(5, 10));
// Output: "5-10 items"
هذا النمط يفصل منطق صيغ الجمع عن النص المترجم. تتعامل نسخة Intl.PluralRules مع قواعد اللغة. يحتفظ الـ Map بالترجمات. تقوم الدالة بدمجهما معًا.
بالنسبة للغات التي تحتوي على المزيد من فئات الجمع، أضف إدخالات لكل فئة تستخدمها اللغة.
const arRules = new Intl.PluralRules("ar-EG");
const arForms = new Map([
["zero", "عناصر"],
["one", "عنصر"],
["two", "عنصران"],
["few", "عناصر"],
["many", "عنصرًا"],
["other", "عنصر"]
]);
function formatRange(start, end) {
const category = arRules.selectRange(start, end);
const form = arForms.get(category);
return `${start}-${end} ${form}`;
}
console.log(formatRange(0, 0));
// Output: "0-0 عناصر"
console.log(formatRange(1, 1));
// Output: "1-1 عنصر"
قدم دائمًا نصًا لكل فئة تستخدمها اللغة. تحقق من قواعد الجمع في Unicode CLDR أو اختبر باستخدام واجهة البرمجة عبر نطاقات مختلفة لتحديد الفئات المطلوبة.
كيفية تعامل اللغات المختلفة مع جمع النطاقات
كل لغة لها قواعدها الخاصة لتحديد صيغة الجمع للنطاقات. تعكس هذه القواعد كيفية تعبير المتحدثين الأصليين بشكل طبيعي عن النطاقات في تلك اللغة.
const enRules = new Intl.PluralRules("en-US");
console.log(enRules.selectRange(1, 3));
// Output: "other"
const slRules = new Intl.PluralRules("sl");
console.log(slRules.selectRange(102, 201));
// Output: "few"
const ptRules = new Intl.PluralRules("pt");
console.log(ptRules.selectRange(102, 102));
// Output: "other"
const ruRules = new Intl.PluralRules("ru");
console.log(ruRules.selectRange(1, 2));
// Output: "few"
تستخدم اللغة الإنجليزية باستمرار فئة "other" للنطاقات، مما يجعل النطاقات دائمًا في صيغة الجمع. تطبق اللغة السلوفينية قواعد أكثر تعقيدًا بناءً على الأرقام المحددة في النطاق. تستخدم البرتغالية فئة "other" لمعظم النطاقات. تستخدم الروسية فئة "few" لنطاقات معينة.
توضح هذه الاختلافات سبب فشل ترميز منطق الجمع بشكل ثابت للتطبيقات الدولية. تغلف واجهة البرمجة المعرفة حول كيفية تعامل كل لغة مع النطاقات.
الدمج مع Intl.NumberFormat للتنسيق الكامل
تحتاج التطبيقات الحقيقية إلى تنسيق كل من الأرقام والنص. استخدم Intl.NumberFormat لتنسيق نقاط نهاية النطاق وفقًا لاتفاقيات اللغة المحلية، ثم استخدم selectRange() لاختيار صيغة الجمع الصحيحة.
const locale = "en-US";
const numberFormat = new Intl.NumberFormat(locale);
const pluralRules = new Intl.PluralRules(locale);
const forms = new Map([
["one", "item"],
["other", "items"]
]);
function formatRange(start, end) {
const startFormatted = numberFormat.format(start);
const endFormatted = numberFormat.format(end);
const category = pluralRules.selectRange(start, end);
const form = forms.get(category);
return `${startFormatted}-${endFormatted} ${form}`;
}
console.log(formatRange(1, 3));
// Output: "1-3 items"
console.log(formatRange(1000, 5000));
// Output: "1,000-5,000 items"
يضيف منسق الأرقام فواصل الآلاف. تحدد قواعد الجمع الصيغة الصحيحة. تجمع الدالة بين كليهما لإنتاج مخرجات منسقة بشكل صحيح.
تستخدم اللغات المحلية المختلفة اتفاقيات تنسيق أرقام مختلفة.
const locale = "de-DE";
const numberFormat = new Intl.NumberFormat(locale);
const pluralRules = new Intl.PluralRules(locale);
const forms = new Map([
["one", "Artikel"],
["other", "Artikel"]
]);
function formatRange(start, end) {
const startFormatted = numberFormat.format(start);
const endFormatted = numberFormat.format(end);
const category = pluralRules.selectRange(start, end);
const form = forms.get(category);
return `${startFormatted}-${endFormatted} ${form}`;
}
console.log(formatRange(1000, 5000));
// Output: "1.000-5.000 Artikel"
تستخدم اللغة الألمانية النقاط كفواصل آلاف بدلاً من الفواصل. يتعامل منسق الأرقام مع هذا تلقائيًا. تحدد قواعد الجمع أي صيغة من "Artikel" يجب استخدامها.
مقارنة selectRange مع select للقيم المفردة
تتعامل طريقة select() مع العدد المفرد، بينما تتعامل selectRange() مع النطاقات. استخدم select() عند عرض كمية واحدة وselectRange() عند عرض نطاق بين قيمتين.
const rules = new Intl.PluralRules("en-US");
// Single count
console.log(rules.select(1));
// Output: "one"
console.log(rules.select(2));
// Output: "other"
// Range
console.log(rules.selectRange(1, 2));
// Output: "other"
console.log(rules.selectRange(0, 1));
// Output: "other"
بالنسبة للعدد المفرد، تعتمد القواعد فقط على ذلك الرقم الواحد. بالنسبة للنطاقات، تأخذ القواعد في الاعتبار كلا النقطتين النهائيتين. في اللغة الإنجليزية، يستخدم النطاق الذي يبدأ من 1 صيغة الجمع، على الرغم من أن العدد المفرد 1 يستخدم صيغة المفرد.
تُظهر بعض اللغات اختلافات أكثر وضوحًا بين قواعد العدد المفرد وقواعد النطاق.
const slRules = new Intl.PluralRules("sl");
// Single counts in Slovenian
console.log(slRules.select(1));
// Output: "one"
console.log(slRules.select(2));
// Output: "two"
console.log(slRules.select(5));
// Output: "few"
// Range in Slovenian
console.log(slRules.selectRange(102, 201));
// Output: "few"
تستخدم اللغة السلوفينية "one" و"two" و"few" للأعداد المفردة المختلفة بناءً على قواعد معقدة. بالنسبة للنطاقات، فإنها تطبق منطقًا مختلفًا يأخذ في الاعتبار كلا الرقمين معًا.
التعامل مع النطاقات حيث تكون القيمة البدائية والنهائية متساوية
عندما تكون القيم البدائية والنهائية متطابقة، فأنت تعرض نطاقًا بدون عرض. تستخدم بعض التطبيقات هذا لتمثيل قيمة دقيقة في سياق يتوقع فيه وجود نطاقات.
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(5, 5));
// الناتج: "other"
console.log(rules.selectRange(1, 1));
// الناتج: "one"
عندما تكون كلتا القيمتين تساوي 1، تُرجع اللغة الإنجليزية "one"، مما يشير إلى أنه يجب استخدام صيغة المفرد. عندما تكون كلتا القيمتين أي رقم آخر، تُرجع اللغة الإنجليزية "other"، مما يشير إلى صيغة الجمع.
هذا السلوك منطقي إذا كنت تعرض النطاق كـ "1-1 عنصر" أو ببساطة "عنصر واحد". للقيم غير 1، تعرض "5-5 عناصر" أو "5 عناصر".
في الممارسة العملية، قد ترغب في اكتشاف متى تتساوى القيمة البدائية مع النهائية وعرض قيمة واحدة بدلاً من نطاق.
const rules = new Intl.PluralRules("en-US");
const forms = new Map([
["one", "عنصر"],
["other", "عناصر"]
]);
function formatRange(start, end) {
if (start === end) {
const category = rules.select(start);
const form = forms.get(category);
return `${start} ${form}`;
}
const category = rules.selectRange(start, end);
const form = forms.get(category);
return `${start}-${end} ${form}`;
}
console.log(formatRange(1, 1));
// الناتج: "1 عنصر"
console.log(formatRange(5, 5));
// الناتج: "5 عناصر"
console.log(formatRange(1, 3));
// الناتج: "1-3 عناصر"
يستخدم هذا النهج select() للقيم المتساوية وselectRange() للنطاقات الفعلية. يبدو الناتج أكثر طبيعية لأنه يتجنب عرض "1-1" أو "5-5".
التعامل مع الحالات الاستثنائية باستخدام selectRange
تتحقق طريقة selectRange() من صحة المدخلات. إذا كانت أي من المعلمات undefined أو null أو لا يمكن تحويلها إلى رقم صالح، فإن الطريقة تطرح خطأً.
const rules = new Intl.PluralRules("en-US");
try {
console.log(rules.selectRange(1, undefined));
} catch (error) {
console.log(error.name);
// الناتج: "TypeError"
}
try {
console.log(rules.selectRange(NaN, 5));
} catch (error) {
console.log(error.name);
// الناتج: "RangeError"
}
تحقق من صحة المدخلات قبل تمريرها إلى selectRange(). هذا مهم بشكل خاص عند العمل مع مدخلات المستخدم أو البيانات من مصادر خارجية.
function formatRange(start, end) {
if (typeof start !== "number" || typeof end !== "number") {
throw new Error("يجب أن تكون القيم البدائية والنهائية أرقامًا");
}
if (isNaN(start) || isNaN(end)) {
throw new Error("يجب أن تكون القيم البدائية والنهائية أرقامًا صالحة");
}
const category = rules.selectRange(start, end);
const form = forms.get(category);
return `${start}-${end} ${form}`;
}
تقبل الطريقة الأرقام، وقيم BigInt، أو السلاسل النصية التي يمكن تحليلها كأرقام.
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(1, 5));
// الناتج: "other"
console.log(rules.selectRange(1n, 5n));
// الناتج: "other"
console.log(rules.selectRange("1", "5"));
// الناتج: "other"
يتم تحليل المدخلات النصية كأرقام. هذا يوفر مرونة في كيفية استدعاء الطريقة، ولكن يجب أن تفضل تمرير أنواع الأرقام الفعلية عندما يكون ذلك ممكنًا للوضوح.
التعامل مع نطاقات الأرقام العشرية
تعمل طريقة selectRange() مع الأرقام العشرية. هذا مفيد عند عرض نطاقات الكميات الكسرية مثل القياسات أو الإحصاءات.
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(1.5, 2.5));
// الناتج: "other"
console.log(rules.selectRange(0.5, 1.0));
// الناتج: "other"
console.log(rules.selectRange(1.0, 1.5));
// الناتج: "other"
تتعامل اللغة الإنجليزية مع جميع هذه النطاقات العشرية على أنها جمع. قد تحتوي اللغات الأخرى على قواعد مختلفة للنطاقات العشرية.
عند تنسيق النطاقات العشرية، قم بدمج selectRange() مع Intl.NumberFormat المهيأ للدقة العشرية المناسبة.
const locale = "en-US";
const numberFormat = new Intl.NumberFormat(locale, {
minimumFractionDigits: 1,
maximumFractionDigits: 1
});
const pluralRules = new Intl.PluralRules(locale);
const forms = new Map([
["one", "kilometer"],
["other", "kilometers"]
]);
function formatRange(start, end) {
const startFormatted = numberFormat.format(start);
const endFormatted = numberFormat.format(end);
const category = pluralRules.selectRange(start, end);
const form = forms.get(category);
return `${startFormatted}-${endFormatted} ${form}`;
}
console.log(formatRange(1.5, 2.5));
// الناتج: "1.5-2.5 kilometers"
console.log(formatRange(0.5, 1.0));
// الناتج: "0.5-1.0 kilometers"
يضمن منسق الأرقام عرضًا عشريًا متسقًا. تحدد قواعد الجمع الصيغة الصحيحة بناءً على القيم العشرية.
دعم المتصفح والتوافق
طريقة selectRange() حديثة نسبيًا مقارنة ببقية واجهة برمجة التطبيقات Intl. أصبحت متاحة في عام 2023 كجزء من مواصفات Intl.NumberFormat v3.
يشمل دعم المتصفح Chrome 106 والإصدارات الأحدث، Firefox 116 والإصدارات الأحدث، Safari 15.4 والإصدارات الأحدث، وEdge 106 والإصدارات الأحدث. الطريقة غير متاحة في Internet Explorer أو إصدارات المتصفح القديمة.
بالنسبة للتطبيقات التي تستهدف المتصفحات الحديثة، يمكنك استخدام selectRange() بدون polyfill. إذا كنت بحاجة إلى دعم المتصفحات القديمة، تحقق من وجود الطريقة قبل استخدامها.
const rules = new Intl.PluralRules("en-US");
if (typeof rules.selectRange === "function") {
// استخدم selectRange لجمع النطاق
console.log(rules.selectRange(1, 3));
} else {
// الرجوع إلى select مع القيمة النهائية
console.log(rules.select(3));
}
يستخدم هذا النهج البديل select() على القيمة النهائية عندما تكون selectRange() غير متاحة. هذا ليس مثاليًا لغويًا لجميع اللغات، ولكنه يوفر تقريبًا معقولًا للمتصفحات القديمة.
تتوفر polyfills من خلال حزم مثل @formatjs/intl-pluralrules إذا كنت بحاجة إلى دعم شامل للبيئات القديمة.
متى تستخدم selectRange مقابل select
استخدم selectRange() عندما تعرض واجهة المستخدم الخاصة بك نطاقًا بشكل صريح مع ظهور قيم البداية والنهاية للمستخدم. يشمل ذلك سياقات مثل نتائج البحث التي تظهر "تم العثور على 10-15 تطابقًا"، أو المخزون الذي يظهر "1-3 عناصر متوفرة"، أو عوامل التصفية التي تظهر "حدد 2-5 خيارات".
استخدم select() عند عرض عدد واحد، حتى لو كان هذا العدد يمثل قيمة تقريبية أو ملخصة. على سبيل المثال، "حوالي 10 نتائج" تستخدم select(10) لأنك تعرض رقمًا واحدًا، وليس نطاقًا.
إذا كان النطاق الخاص بك يتم عرضه باستخدام Intl.NumberFormat.formatRange() للأرقام، استخدم selectRange() للنص المصاحب. هذا يضمن الاتساق بين تنسيق الأرقام وصيغة الجمع في النص.
const locale = "en-US";
const numberFormat = new Intl.NumberFormat(locale);
const pluralRules = new Intl.PluralRules(locale);
const forms = new Map([
["one", "result"],
["other", "results"]
]);
function formatSearchResults(start, end) {
const rangeFormatted = numberFormat.formatRange(start, end);
const category = pluralRules.selectRange(start, end);
const form = forms.get(category);
return `Found ${rangeFormatted} ${form}`;
}
console.log(formatSearchResults(10, 15));
// Output: "Found 10–15 results"
يستخدم هذا النمط formatRange() من Intl.NumberFormat لتنسيق الأرقام وselectRange() من Intl.PluralRules لاختيار النص. تعمل كلتا الطريقتين على النطاقات، مما يضمن المعالجة الصحيحة لجميع اللغات.