كيف تختار صيغة الجمع الصحيحة للغات المختلفة؟
استخدم Intl.PluralRules في JavaScript للاختيار بين عنصر واحد، عنصرين، عدة عناصر، عناصر كثيرة بناءً على قواعد خاصة باللغة
مقدمة
عندما تعرض نصاً يحتوي على كميات، تحتاج إلى رسائل مختلفة لأعداد مختلفة. في الإنجليزية، تكتب "1 file" لكن "2 files". النهج الأبسط يدمج رقماً مع كلمة ويضيف "s" عند الحاجة.
function formatFileCount(count) {
return count === 1 ? `${count} file` : `${count} files`;
}
يفشل هذا النهج بثلاث طرق. أولاً، ينتج إنجليزية غير صحيحة للصفر ("0 files" يجب أن تكون "no files"). ثانياً، ينهار مع صيغ الجمع المعقدة مثل "1 child, 2 children" أو "1 person, 2 people". ثالثاً، والأهم، اللغات الأخرى لديها قواعد جمع مختلفة تماماً لا يمكن لهذا الكود التعامل معها.
توفر JavaScript واجهة Intl.PluralRules لحل هذه المشكلة. تحدد هذه الواجهة البرمجية أي صيغة جمع يجب استخدامها لأي رقم في أي لغة، متبعة معيار Unicode CLDR المستخدم من قبل أنظمة الترجمة الاحترافية في جميع أنحاء العالم.
لماذا تحتاج اللغات المختلفة إلى صيغ جمع مختلفة
تستخدم الإنجليزية صيغتي جمع. تكتب "1 book" و"2 books". تتغير الكلمة عندما يكون العدد واحداً بالضبط مقابل أي رقم آخر.
اللغات الأخرى تعمل بشكل مختلف. تستخدم البولندية ثلاث صيغ بناءً على قواعد معقدة. تستخدم الروسية أربع صيغ. تستخدم العربية ست صيغ. بعض اللغات تستخدم صيغة واحدة فقط لجميع الكميات.
فيما يلي أمثلة توضح كيف تتغير كلمة "تفاحة" بناءً على الكمية في لغات مختلفة:
الإنجليزية: 1 apple، 2 apples، 5 apples، 0 apples
البولندية: 1 jabłko، 2 jabłka، 5 jabłek، 0 jabłek
الروسية: 1 яблоко، 2 яблока، 5 яблок، 0 яблок
العربية: تستخدم ستة أشكال مختلفة حسب ما إذا كان لديك صفر أو واحد أو اثنان أو قليل أو كثير أو كميات أخرى
يحدد معيار Unicode CLDR القواعد الدقيقة لموعد استخدام كل شكل في كل لغة. لا يمكنك حفظ هذه القواعد أو برمجتها بشكل ثابت في تطبيقك. أنت بحاجة إلى واجهة برمجة تطبيقات تعرفها.
ما هي فئات الجمع في CLDR
يحدد معيار Unicode CLDR ست فئات للجمع تغطي جميع اللغات:
zero: يُستخدم في بعض اللغات للعناصر الصفرية تحديداًone: يُستخدم للصيغ المفردةtwo: يُستخدم في اللغات التي لها صيغة المثنىfew: يُستخدم للكميات الصغيرة في بعض اللغاتmany: يُستخدم للكميات الأكبر أو الكسور في بعض اللغاتother: الصيغة الافتراضية، تُستخدم عندما لا تنطبق أي فئة أخرى
كل لغة تستخدم فئة other. معظم اللغات تستخدم فئتين أو ثلاث فئات فقط إجمالاً. الفئات لا تتوافق مباشرة مع الكميات. على سبيل المثال، في البولندية، الرقم 5 يستخدم فئة many، وكذلك 0 و25 و1.5.
القواعد المحددة التي تربط الأرقام بالفئات تختلف حسب اللغة. تتعامل JavaScript مع هذا التعقيد من خلال واجهة برمجة التطبيقات Intl.PluralRules.
كيفية تحديد صيغة الجمع المناسبة
يحدد كائن Intl.PluralRules الفئة الجمعية التي ينتمي إليها رقم في لغة معينة. تقوم بإنشاء كائن PluralRules مع لغة محددة، ثم تستدعي طريقة select() الخاصة به مع رقم.
const rules = new Intl.PluralRules('en-US');
console.log(rules.select(0)); // "other"
console.log(rules.select(1)); // "one"
console.log(rules.select(2)); // "other"
console.log(rules.select(5)); // "other"
في اللغة الإنجليزية، تُرجع select() القيمة "one" للرقم 1 والقيمة "other" لكل شيء آخر.
تستخدم اللغة البولندية ثلاث فئات بقواعد أكثر تعقيداً:
const rules = new Intl.PluralRules('pl-PL');
console.log(rules.select(0)); // "many"
console.log(rules.select(1)); // "one"
console.log(rules.select(2)); // "few"
console.log(rules.select(5)); // "many"
console.log(rules.select(22)); // "few"
console.log(rules.select(25)); // "many"
تستخدم اللغة العربية ست فئات:
const rules = new Intl.PluralRules('ar-EG');
console.log(rules.select(0)); // "zero"
console.log(rules.select(1)); // "one"
console.log(rules.select(2)); // "two"
console.log(rules.select(3)); // "few"
console.log(rules.select(11)); // "many"
console.log(rules.select(100)); // "other"
تُرجع الدالة select() سلسلة نصية تحدد الفئة. يمكنك استخدام هذه السلسلة لاختيار الرسالة المناسبة للعرض.
كيفية ربط فئات الجمع بالرسائل
بعد تحديد فئة الجمع، تحتاج إلى اختيار الرسالة الصحيحة لعرضها للمستخدم. أنشئ كائناً يربط كل فئة برسالتها، ثم استخدم سلسلة الفئة للبحث عن الرسالة.
const messages = {
one: '{count} file',
other: '{count} files'
};
function formatFileCount(count, locale) {
const rules = new Intl.PluralRules(locale);
const category = rules.select(count);
const message = messages[category];
return message.replace('{count}', count);
}
console.log(formatFileCount(1, 'en-US')); // "1 file"
console.log(formatFileCount(5, 'en-US')); // "5 files"
يعمل هذا النمط مع أي لغة. بالنسبة للغة البولندية، توفر رسائل لجميع الفئات الثلاث التي تستخدمها اللغة:
const messages = {
one: '{count} plik',
few: '{count} pliki',
many: '{count} plików'
};
function formatFileCount(count, locale) {
const rules = new Intl.PluralRules(locale);
const category = rules.select(count);
const message = messages[category];
return message.replace('{count}', count);
}
console.log(formatFileCount(1, 'pl-PL')); // "1 plik"
console.log(formatFileCount(2, 'pl-PL')); // "2 pliki"
console.log(formatFileCount(5, 'pl-PL')); // "5 plików"
console.log(formatFileCount(22, 'pl-PL')); // "22 pliki"
يظل هيكل الكود متطابقاً عبر اللغات. يتغير كائن الرسائل فقط. يتيح هذا الفصل للمترجمين توفير الرسائل الصحيحة للغتهم دون تعديل الكود.
كيفية التعامل مع فئات الجمع المفقودة
قد لا يتضمن كائن الرسائل جميع الفئات الست المحتملة. تستخدم معظم اللغات فئتين أو ثلاث فئات فقط. عندما تُرجع select() فئة غير موجودة في كائن الرسائل، ارجع إلى فئة other.
function formatFileCount(count, locale, messages) {
const rules = new Intl.PluralRules(locale);
const category = rules.select(count);
const message = messages[category] || messages.other;
return message.replace('{count}', count);
}
const englishMessages = {
one: '{count} file',
other: '{count} files'
};
console.log(formatFileCount(1, 'en-US', englishMessages)); // "1 file"
console.log(formatFileCount(5, 'en-US', englishMessages)); // "5 files"
يضمن هذا النمط عمل الكود حتى عندما يكون كائن الرسائل غير مكتمل. تتوفر فئة other دائماً في كل لغة، مما يجعلها خياراً احتياطياً آمناً.
كيفية استخدام قواعد الجمع مع الأرقام الترتيبية
يقبل المُنشئ Intl.PluralRules خيار type الذي يغير كيفية تحديد الفئات. النوع الافتراضي هو "cardinal"، المستخدم لعد العناصر. اضبط type: "ordinal" لتحديد صيغ الجمع للأرقام الترتيبية مثل "الأول" و"الثاني" و"الثالث".
const cardinalRules = new Intl.PluralRules('en-US', { type: 'cardinal' });
console.log(cardinalRules.select(1)); // "one"
console.log(cardinalRules.select(2)); // "other"
console.log(cardinalRules.select(3)); // "other"
const ordinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
console.log(ordinalRules.select(1)); // "one"
console.log(ordinalRules.select(2)); // "two"
console.log(ordinalRules.select(3)); // "few"
console.log(ordinalRules.select(4)); // "other"
تحدد القواعد الأساسية "عنصر واحد، عنصران". تحدد القواعد الترتيبية "المركز الأول، المركز الثاني، المركز الثالث، المركز الرابع". تختلف الفئات المُرجعة لأن الأنماط النحوية تختلف.
كيفية تنسيق الكميات الكسرية
تعمل طريقة select() مع الأرقام العشرية. لدى اللغات المختلفة قواعد محددة لكيفية تعيين الكسور إلى فئات الجمع.
const rules = new Intl.PluralRules('en-US');
console.log(rules.select(1)); // "one"
console.log(rules.select(1.0)); // "one"
console.log(rules.select(1.5)); // "other"
console.log(rules.select(0.5)); // "other"
في اللغة الإنجليزية، يستخدم 1.0 صيغة المفرد، لكن 1.5 يستخدم صيغة الجمع. بعض اللغات لديها قواعد مختلفة للكسور، حيث تعاملها كفئة منفصلة.
const messages = {
one: '{count} file',
other: '{count} files'
};
const rules = new Intl.PluralRules('en-US');
const count = 1.5;
const category = rules.select(count);
const message = messages[category];
console.log(message.replace('{count}', count)); // "1.5 files"
مرر الرقم العشري مباشرة إلى select(). تطبق الطريقة قواعد اللغة الصحيحة تلقائيًا.
كيفية إنشاء منسق جمع قابل لإعادة الاستخدام
بدلاً من تكرار نفس النمط في جميع أنحاء تطبيقك، أنشئ دالة قابلة لإعادة الاستخدام تغلف منطق اختيار الجمع.
class PluralFormatter {
constructor(locale) {
this.locale = locale;
this.rules = new Intl.PluralRules(locale);
}
format(count, messages) {
const category = this.rules.select(count);
const message = messages[category] || messages.other;
return message.replace('{count}', count);
}
}
const formatter = new PluralFormatter('en-US');
const fileMessages = {
one: '{count} file',
other: '{count} files'
};
const itemMessages = {
one: '{count} item',
other: '{count} items'
};
console.log(formatter.format(1, fileMessages)); // "1 file"
console.log(formatter.format(5, fileMessages)); // "5 files"
console.log(formatter.format(1, itemMessages)); // "1 item"
console.log(formatter.format(3, itemMessages)); // "3 items"
ينشئ هذا الصف كائن PluralRules مرة واحدة ويعيد استخدامه لعمليات تنسيق متعددة. يمكنك توسيعه لدعم ميزات أكثر تقدمًا مثل تنسيق العدد باستخدام Intl.NumberFormat قبل إدراجه في الرسالة.
كيفية دمج قواعد الجمع مع أنظمة الترجمة
تخزن أنظمة الترجمة الاحترافية الرسائل مع عناصر نائبة لفئات الجمع. عند ترجمة النص، توفر جميع صيغ الجمع التي تحتاجها لغتك.
const translations = {
'en-US': {
fileCount: {
one: '{count} file',
other: '{count} files'
},
downloadComplete: {
one: 'Download of {count} file complete',
other: 'Download of {count} files complete'
}
},
'pl-PL': {
fileCount: {
one: '{count} plik',
few: '{count} pliki',
many: '{count} plików'
},
downloadComplete: {
one: 'Pobieranie {count} pliku zakończone',
few: 'Pobieranie {count} plików zakończone',
many: 'Pobieranie {count} plików zakończone'
}
}
};
class Translator {
constructor(locale, translations) {
this.locale = locale;
this.translations = translations[locale] || {};
this.rules = new Intl.PluralRules(locale);
}
translate(key, count) {
const messages = this.translations[key];
if (!messages) return key;
const category = this.rules.select(count);
const message = messages[category] || messages.other;
return message.replace('{count}', count);
}
}
const translator = new Translator('en-US', translations);
console.log(translator.translate('fileCount', 1)); // "1 file"
console.log(translator.translate('fileCount', 5)); // "5 files"
console.log(translator.translate('downloadComplete', 1)); // "Download of 1 file complete"
console.log(translator.translate('downloadComplete', 5)); // "Download of 5 files complete"
const polishTranslator = new Translator('pl-PL', translations);
console.log(polishTranslator.translate('fileCount', 1)); // "1 plik"
console.log(polishTranslator.translate('fileCount', 2)); // "2 pliki"
console.log(polishTranslator.translate('fileCount', 5)); // "5 plików"
يفصل هذا النمط بيانات الترجمة عن منطق الكود. يوفر المترجمون الرسائل لكل فئة جمع تستخدمها لغتهم. يطبق الكود الخاص بك القواعد تلقائيًا.
كيفية التحقق من فئات الجمع التي تستخدمها اللغة
تُرجع طريقة resolvedOptions() معلومات حول كائن PluralRules، لكنها لا تسرد الفئات التي تستخدمها اللغة. للعثور على جميع الفئات التي تستخدمها اللغة، اختبر نطاقًا من الأرقام واجمع الفئات الفريدة المُرجعة.
function getPluralCategories(locale) {
const rules = new Intl.PluralRules(locale);
const categories = new Set();
for (let i = 0; i <= 100; i++) {
categories.add(rules.select(i));
categories.add(rules.select(i + 0.5));
}
return Array.from(categories).sort();
}
console.log(getPluralCategories('en-US')); // ["one", "other"]
console.log(getPluralCategories('pl-PL')); // ["few", "many", "one"]
console.log(getPluralCategories('ar-EG')); // ["few", "many", "one", "other", "two", "zero"]
تختبر هذه التقنية الأعداد الصحيحة والقيم النصفية عبر نطاق معين. وهي تلتقط الفئات التي يجب أن يتضمنها كائن الرسائل الخاص بك للغة محددة.