واجهة برمجة التطبيقات Intl.PluralRules

كيفية التعامل مع صيغ الجمع بشكل صحيح في JavaScript

مقدمة

التعدد هو عملية عرض نص مختلف بناءً على العدد. في اللغة الإنجليزية، قد تعرض "1 item" لعنصر واحد و"2 items" لعناصر متعددة. يتعامل معظم المطورين مع هذا من خلال شرط بسيط يضيف حرف "s" للأعداد غير الواحد.

يفشل هذا النهج مع اللغات الأخرى غير الإنجليزية. تستخدم اللغة البولندية صيغًا مختلفة للعدد 1، و2-4، و5 أو أكثر. تحتوي اللغة العربية على صيغ للصفر، والواحد، والاثنين، والقليل، والكثير. تحتوي اللغة الويلزية على ستة صيغ متميزة. حتى عند البقاء في اللغة الإنجليزية، تتطلب صيغ الجمع غير المنتظمة مثل "person" إلى "people" معالجة خاصة.

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

كيف تتعامل اللغات مع صيغ الجمع

تختلف اللغات بشكل كبير في كيفية التعبير عن الكمية. تحتوي اللغة الإنجليزية على صيغتين: المفرد للواحد، والجمع لكل شيء آخر. يبدو هذا واضحًا حتى تواجه لغات بأنظمة مختلفة.

تستخدم اللغتان الروسية والبولندية ثلاث صيغ. ينطبق المفرد على عنصر واحد. تنطبق صيغة خاصة على الأعداد المنتهية بـ 2 أو 3 أو 4 (ولكن ليس 12 أو 13 أو 14). تستخدم جميع الأعداد الأخرى صيغة ثالثة.

تستخدم اللغة العربية ست صيغ: الصفر، والواحد، والاثنان، والقليل (3-10)، والكثير (11-99)، والأخرى (100+). تحتوي اللغة الويلزية أيضًا على ست صيغ بحدود رقمية مختلفة.

بعض اللغات مثل الصينية واليابانية لا تميز بين المفرد والجمع على الإطلاق. نفس الصيغة تعمل مع أي عدد.

واجهة Intl.PluralRules API تجرد هذه الاختلافات باستخدام أسماء فئات موحدة بناءً على قواعد الجمع في Unicode CLDR. الفئات الست هي: zero وone وtwo وfew وmany وother. ليست كل لغة تستخدم الفئات الست جميعها. الإنجليزية تستخدم فقط one وother. العربية تستخدم الفئات الست جميعها.

إنشاء نسخة من PluralRules لمحلية معينة

منشئ Intl.PluralRules يأخذ معرف محلية ويعيد كائناً يمكنه تحديد أي فئة جمع تنطبق على رقم معين.

const enRules = new Intl.PluralRules('en-US');

أنشئ نسخة واحدة لكل محلية وأعد استخدامها. إنشاء نسخة جديدة لكل عملية جمع يعد إهداراً. احفظ النسخة في متغير أو استخدم آلية تخزين مؤقت.

النوع الافتراضي هو cardinal، والذي يتعامل مع عد الأشياء. يمكنك أيضاً إنشاء قواعد للأرقام الترتيبية عن طريق تمرير كائن خيارات.

const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });

قواعد cardinal تنطبق على الأعداد مثل "تفاحة واحدة، تفاحتان". قواعد ordinal تنطبق على المواضع مثل "المركز الأول، المركز الثاني".

استخدام select() للحصول على فئة الجمع لرقم معين

تأخذ الدالة select() رقماً وتعيد فئة الجمع التي ينتمي إليها في اللغة المستهدفة.

const enRules = new Intl.PluralRules('en-US');

enRules.select(0);  // 'other'
enRules.select(1);  // 'one'
enRules.select(2);  // 'other'
enRules.select(5);  // 'other'

القيمة المعادة دائماً واحدة من أسماء الفئات الست: zero أو one أو two أو few أو many أو other. الإنجليزية تعيد فقط one وother لأن هذه هي الصيغ الوحيدة التي تستخدمها الإنجليزية.

بالنسبة للعربية، التي لديها قواعد أكثر تعقيداً، ترى الفئات الست جميعها قيد الاستخدام:

const arRules = new Intl.PluralRules('ar-EG');

arRules.select(0);   // 'zero'
arRules.select(1);   // 'one'
arRules.select(2);   // 'two'
arRules.select(6);   // 'few'
arRules.select(18);  // 'many'
arRules.select(100); // 'other'

ربط الفئات بالنصوص المترجمة

تخبرك واجهة برمجة التطبيقات فقط بالفئة المطبقة. أنت تقدم النص الفعلي لكل فئة. قم بتخزين أشكال النص في Map أو كائن، مفهرسة باسم الفئة.

const enRules = new Intl.PluralRules('en-US');
const enForms = new Map([
  ['one', 'item'],
  ['other', 'items'],
]);

function formatItems(count) {
  const category = enRules.select(count);
  const form = enForms.get(category);
  return `${count} ${form}`;
}

formatItems(1);  // '1 item'
formatItems(5);  // '5 items'

يفصل هذا النمط المنطق عن البيانات. يتعامل كائن PluralRules مع القواعد. يحتوي Map على الترجمات. تجمع الدالة بينهما.

بالنسبة للغات التي تحتوي على فئات أكثر، أضف المزيد من الإدخالات إلى Map:

const arRules = new Intl.PluralRules('ar-EG');
const arForms = new Map([
  ['zero', 'عناصر'],
  ['one', 'عنصر واحد'],
  ['two', 'عنصران'],
  ['few', 'عناصر'],
  ['many', 'عنصرًا'],
  ['other', 'عنصر'],
]);

function formatItems(count) {
  const category = arRules.select(count);
  const form = arForms.get(category);
  return `${count} ${form}`;
}

قم دائماً بتوفير إدخالات لكل فئة تستخدمها اللغة. تتسبب الفئات المفقودة في عمليات بحث غير محددة. إذا لم تكن متأكداً من الفئات التي تستخدمها اللغة، تحقق من قواعد الجمع في Unicode CLDR أو اختبر باستخدام واجهة برمجة التطبيقات عبر أرقام مختلفة.

التعامل مع الأعداد العشرية والكسرية

تعمل طريقة select() مع الأرقام العشرية. تتعامل اللغة الإنجليزية مع الأعداد العشرية على أنها جمع، حتى للقيم بين 0 و2.

const enRules = new Intl.PluralRules('en-US');

enRules.select(1);    // 'one'
enRules.select(1.0);  // 'one'
enRules.select(1.5);  // 'other'
enRules.select(0.5);  // 'other'

اللغات الأخرى لديها قواعد مختلفة للأعداد العشرية. يتعامل البعض مع أي عدد عشري على أنه جمع، بينما يستخدم البعض الآخر قواعد أكثر دقة بناءً على الجزء الكسري.

إذا كانت واجهة المستخدم الخاصة بك تعرض كميات كسرية مثل "1.5 GB" أو "2.7 miles"، قم بتمرير الرقم الكسري مباشرة إلى select(). لا تقم بالتقريب أولاً إلا إذا كانت واجهة المستخدم تقرب قيمة العرض.

تنسيق الأرقام الترتيبية مثل 1st و2nd و3rd

تشير الأرقام الترتيبية إلى الموضع أو الرتبة. تشكل اللغة الإنجليزية الأرقام الترتيبية بإضافة لواحق: 1st و2nd و3rd و4th. النمط ليس ببساطة "إضافة th" لأن 1 و2 و3 لها أشكال خاصة، والأرقام المنتهية بـ 1 و2 أو 3 تتبع قواعد خاصة (21st و22nd و23rd) باستثناء عندما تنتهي بـ 11 و12 أو 13 (11th و12th و13th).

تتعامل واجهة Intl.PluralRules API مع هذه القواعد عند تحديد type: 'ordinal'.

const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });

enOrdinalRules.select(1);   // 'one'
enOrdinalRules.select(2);   // 'two'
enOrdinalRules.select(3);   // 'few'
enOrdinalRules.select(4);   // 'other'
enOrdinalRules.select(11);  // 'other'
enOrdinalRules.select(21);  // 'one'
enOrdinalRules.select(22);  // 'two'
enOrdinalRules.select(23);  // 'few'

ربط الفئات باللواحق الترتيبية:

const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
const enOrdinalSuffixes = new Map([
  ['one', 'st'],
  ['two', 'nd'],
  ['few', 'rd'],
  ['other', 'th'],
]);

function formatOrdinal(n) {
  const category = enOrdinalRules.select(n);
  const suffix = enOrdinalSuffixes.get(category);
  return `${n}${suffix}`;
}

formatOrdinal(1);   // '1st'
formatOrdinal(2);   // '2nd'
formatOrdinal(3);   // '3rd'
formatOrdinal(4);   // '4th'
formatOrdinal(11);  // '11th'
formatOrdinal(21);  // '21st'

اللغات الأخرى لديها أنظمة ترتيبية مختلفة تماماً. تستخدم الفرنسية "1er" للأول و"2e" لجميع الأعداد الأخرى. الإسبانية لديها أعداد ترتيبية خاصة بالجنس. توفر الواجهة الفئة، وأنت توفر الأشكال المترجمة.

التعامل مع النطاقات باستخدام selectRange()

تحدد طريقة selectRange() فئة الجمع لنطاق من الأرقام، مثل "1-5 عناصر" أو "10-20 نتيجة". بعض اللغات لديها قواعد جمع مختلفة للنطاقات عن الأعداد الفردية.

const enRules = new Intl.PluralRules('en-US');

enRules.selectRange(1, 5);   // 'other'
enRules.selectRange(0, 1);   // 'other'

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

const slRules = new Intl.PluralRules('sl');

slRules.selectRange(102, 201);  // 'few'

const ptRules = new Intl.PluralRules('pt');

ptRules.selectRange(102, 102);  // 'other'

استخدم selectRange() عند عرض النطاقات بشكل صريح في واجهة المستخدم. للأعداد الفردية، استخدم select().

الدمج مع Intl.NumberFormat لعرض الأرقام المترجمة

غالباً ما تظهر صيغ الجمع جنباً إلى جنب مع الأرقام المنسقة. استخدم Intl.NumberFormat لتنسيق الرقم وفقاً لاصطلاحات اللغة، ثم استخدم Intl.PluralRules لاختيار النص الصحيح.

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 formatCount(count) {
  const formattedNumber = numberFormat.format(count);
  const category = pluralRules.select(count);
  const form = forms.get(category);
  return `${formattedNumber} ${form}`;
}

formatCount(1);      // '1 item'
formatCount(1000);   // '1,000 items'
formatCount(1.5);    // '1.5 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 formatCount(count) {
  const formattedNumber = numberFormat.format(count);
  const category = pluralRules.select(count);
  const form = forms.get(category);
  return `${formattedNumber} ${form}`;
}

formatCount(1);      // '1 Artikel'
formatCount(1000);   // '1.000 Artikel'
formatCount(1.5);    // '1,5 Artikel'

يضمن هذا النمط أن كلاً من تنسيق الرقم والشكل النصي يتطابقان مع توقعات المستخدم للغة.

التعامل مع حالة الصفر بشكل صريح عند الحاجة

تختلف طريقة جمع الصفر حسب اللغة. الإنجليزية عادة تستخدم صيغة الجمع: "0 items"، "0 results". بعض اللغات تستخدم صيغة المفرد للصفر. وأخرى لديها فئة صفر مميزة.

تُرجع واجهة Intl.PluralRules API الفئة المناسبة للصفر بناءً على قواعد اللغة. في الإنجليزية، يُرجع الصفر 'other'، والذي يُربط بصيغة الجمع:

const enRules = new Intl.PluralRules('en-US');

enRules.select(0);  // 'other'

في اللغة العربية، للصفر فئة خاصة به:

const arRules = new Intl.PluralRules('ar-EG');

arRules.select(0);  // 'zero'

يجب أن يأخذ النص الخاص بك هذا في الاعتبار. بالنسبة للإنجليزية، قد ترغب في عرض "لا توجد عناصر" بدلاً من "0 عنصر" لتحسين تجربة المستخدم. تعامل مع هذا قبل استدعاء قواعد الجمع:

function formatItems(count) {
  if (count === 0) {
    return 'No items';
  }
  const category = enRules.select(count);
  const form = enForms.get(category);
  return `${count} ${form}`;
}

بالنسبة للعربية، قدم صيغة صفر محددة في ترجماتك:

const arForms = new Map([
  ['zero', 'لا توجد عناصر'],
  ['one', 'عنصر واحد'],
  ['two', 'عنصران'],
  ['few', 'عناصر'],
  ['many', 'عنصرًا'],
  ['other', 'عنصر'],
]);

هذا يحترم الاصطلاحات اللغوية لكل لغة مع السماح لك بتخصيص حالة الصفر لتحسين تجربة المستخدم.

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

إنشاء نسخة PluralRules يتضمن تحليل اللغة وتحميل بيانات قواعد الجمع. قم بذلك مرة واحدة لكل لغة، وليس في كل استدعاء دالة أو دورة عرض.

// Good: create once, reuse
const enRules = new Intl.PluralRules('en-US');
const enForms = new Map([
  ['one', 'item'],
  ['other', 'items'],
]);

function formatItems(count) {
  const category = enRules.select(count);
  const form = enForms.get(category);
  return `${count} ${form}`;
}

إذا كنت تدعم لغات متعددة، أنشئ نسخاً لكل لغة وخزنها في Map أو ذاكرة تخزين مؤقتة:

const rulesCache = new Map();

function getPluralRules(locale) {
  if (!rulesCache.has(locale)) {
    rulesCache.set(locale, new Intl.PluralRules(locale));
  }
  return rulesCache.get(locale);
}

const rules = getPluralRules('en-US');

هذا النمط يوزع تكلفة التهيئة على العديد من الاستدعاءات.

دعم المتصفحات والتوافق

يتم دعم Intl.PluralRules في جميع المتصفحات الحديثة منذ عام 2019. يشمل ذلك Chrome 63+، وFirefox 58+، وSafari 13+، وEdge 79+. لا يتم دعمه في Internet Explorer.

بالنسبة للتطبيقات التي تستهدف المتصفحات الحديثة، يمكنك استخدام Intl.PluralRules بدون polyfill. إذا كنت بحاجة إلى دعم المتصفحات الأقدم، فإن polyfills متاحة من خلال حزم مثل intl-pluralrules على npm.

طريقة selectRange() أحدث ولها دعم محدود قليلاً. وهي متاحة في Chrome 106+، وFirefox 116+، وSafari 15.4+، وEdge 106+. تحقق من التوافق إذا كنت تستخدم selectRange() وتحتاج إلى دعم إصدارات متصفح أقدم.

تجنب ترميز صيغ الجمع بشكل ثابت في المنطق

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

// Avoid this pattern
function formatItems(count) {
  if (count === 1) {
    return `${count} item`;
  }
  return `${count} items`;
}

استخدم Intl.PluralRules وبنية بيانات لتخزين الصيغ. يحافظ هذا على كودك مستقلاً عن اللغة ويسهل إضافة لغات جديدة من خلال توفير ترجمات جديدة.

// Prefer this pattern
const rules = new Intl.PluralRules('en-US');
const forms = new Map([
  ['one', 'item'],
  ['other', 'items'],
]);

function formatItems(count) {
  const category = rules.select(count);
  const form = forms.get(category);
  return `${count} ${form}`;
}

يعمل هذا النمط بشكل متطابق لأي لغة. فقط مثيل القواعد وخريطة الصيغ Map يتغيران.

اختبر مع لغات متعددة وحالات استثنائية

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

اختبر الأعداد التي تؤدي إلى فئات مختلفة:

  • صفر
  • واحد
  • اثنان
  • قليل (3-10 في العربية)
  • كثير (11-99 في العربية)
  • أعداد كبيرة (100+)
  • قيم عشرية (0.5، 1.5، 2.3)
  • أعداد سالبة إذا كانت واجهتك تعرضها

إذا كنت تستخدم قواعد الترتيب، اختبر الأعداد التي تؤدي إلى لواحق مختلفة: 1، 2، 3، 4، 11، 21، 22، 23. يضمن هذا معالجة الحالات الخاصة بشكل صحيح.

الاختبار مع لغات متعددة مبكراً يمنع المفاجآت عند إضافة لغات جديدة لاحقاً. كما يتحقق من أن بنية البيانات الخاصة بك تتضمن جميع الفئات الضرورية وأن منطقك يتعامل معها بشكل صحيح.