كيفية اختيار صيغة الجمع للنطاقات مثل 1-3 عناصر
استخدم JavaScript لاختيار صيغة الجمع الصحيحة عند عرض نطاقات الأرقام
مقدمة
تُوضح النطاقات أن القيمة تقع بين نقطتي نهاية. تعرض واجهات المستخدم النطاقات في سياقات مثل نتائج البحث التي تُظهر "تم العثور على 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));
// Output: "one"
// But "0-1 item" is incorrect
// Correct: "0-1 items"
اللغات المختلفة لديها قواعد صريحة للنطاقات لا تتطابق مع قواعدها للأعداد المفردة. في اللغة السلوفينية، يستخدم النطاق 102-201 صيغة "القليل"، بينما تستخدم الأرقام الفردية في هذا النطاق صيغاً مختلفة.
const slRules = new Intl.PluralRules("sl");
console.log(slRules.select(102));
// Output: "few"
console.log(slRules.select(201));
// Output: "few"
console.log(slRules.selectRange(102, 201));
// Output: "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));
// Output: "other"
console.log(rules.selectRange(1, 2));
// Output: "other"
console.log(rules.selectRange(5, 10));
// Output: "other"
في اللغة الإنجليزية، تستخدم النطاقات دائماً تقريباً فئة "other"، والتي تتوافق مع صيغة الجمع. يتطابق هذا مع الطريقة التي يعبر بها المتحدثون بالإنجليزية بشكل طبيعي عن النطاقات باستخدام الأسماء الجمع.
اللغات التي تحتوي على صيغ جمع أكثر تُرجع فئات مختلفة بناءً على قواعدها المحددة.
const arRules = new Intl.PluralRules("ar-EG");
console.log(arRules.selectRange(0, 0));
// Output: "zero"
console.log(arRules.selectRange(1, 1));
// Output: "one"
console.log(arRules.selectRange(2, 2));
// Output: "two"
console.log(arRules.selectRange(3, 10));
// Output: "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"
تستخدم السلوفينية "واحد" و"اثنان" و"قليل" لأعداد مفردة مختلفة بناءً على قواعد معقدة. بالنسبة للنطاقات، تطبق منطقاً مختلفاً يأخذ في الاعتبار كلا الرقمين معاً.
التعامل مع النطاقات حيث البداية والنهاية متساويتان
عندما تكون قيم البداية والنهاية متساوية، فإنك تعرض نطاقاً بدون عرض. تستخدم بعض التطبيقات هذا لتمثيل قيمة دقيقة في سياق يُتوقع فيه وجود نطاقات.
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(5, 5));
// Output: "other"
console.log(rules.selectRange(1, 1));
// Output: "one"
عندما تساوي كلتا القيمتين 1، تُرجع الإنجليزية "one"، مما يشير إلى أنه يجب استخدام صيغة المفرد. عندما تكون كلتا القيمتين أي رقم آخر، تُرجع الإنجليزية "other"، مما يشير إلى صيغة الجمع.
هذا السلوك منطقي إذا كنت تعرض النطاق كـ "عنصر واحد 1-1" أو ببساطة "عنصر واحد". بالنسبة للقيم الأخرى غير 1، تعرض "5-5 عناصر" أو "5 عناصر".
في الممارسة العملية، قد ترغب في اكتشاف متى تكون قيمة البداية مساوية للنهاية وعرض قيمة واحدة بدلاً من نطاق.
const rules = new Intl.PluralRules("en-US");
const forms = new Map([
["one", "item"],
["other", "items"]
]);
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));
// Output: "1 item"
console.log(formatRange(5, 5));
// Output: "5 items"
console.log(formatRange(1, 3));
// Output: "1-3 items"
يستخدم هذا النهج 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);
// Output: "TypeError"
}
try {
console.log(rules.selectRange(NaN, 5));
} catch (error) {
console.log(error.name);
// Output: "RangeError"
}
تحقق من صحة مدخلاتك قبل تمريرها إلى selectRange(). هذا مهم بشكل خاص عند العمل مع مدخلات المستخدم أو البيانات من مصادر خارجية.
function formatRange(start, end) {
if (typeof start !== "number" || typeof end !== "number") {
throw new Error("Start and end must be numbers");
}
if (isNaN(start) || isNaN(end)) {
throw new Error("Start and end must be valid numbers");
}
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));
// Output: "other"
console.log(rules.selectRange(1n, 5n));
// Output: "other"
console.log(rules.selectRange("1", "5"));
// Output: "other"
يتم تحليل المدخلات النصية كأرقام. يتيح هذا المرونة في كيفية استدعاء الدالة، ولكن يجب أن تفضل تمرير أنواع الأرقام الفعلية عندما يكون ذلك ممكناً من أجل الوضوح.
التعامل مع النطاقات العشرية
تعمل الدالة selectRange() مع الأرقام العشرية. هذا مفيد عند عرض نطاقات الكميات الكسرية مثل القياسات أو الإحصائيات.
const rules = new Intl.PluralRules("en-US");
console.log(rules.selectRange(1.5, 2.5));
// Output: "other"
console.log(rules.selectRange(0.5, 1.0));
// Output: "other"
console.log(rules.selectRange(1.0, 1.5));
// Output: "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));
// Output: "1.5-2.5 kilometers"
console.log(formatRange(0.5, 1.0));
// Output: "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") {
// Use selectRange for range pluralization
console.log(rules.selectRange(1, 3));
} else {
// Fall back to select with the end value
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 لاختيار النص. تعمل كلتا الطريقتين على النطاقات، مما يضمن المعالجة الصحيحة لجميع اللغات.