كيف تختار صيغة الجمع الصحيحة للغات المختلفة؟
استخدم Intl.PluralRules في جافاسكريبت للاختيار بين عنصر واحد، عنصرين، بضعة عناصر، عناصر كثيرة بناءً على قواعد خاصة باللغة
مقدمة
عند عرض نص يحتوي على كميات، تحتاج إلى رسائل مختلفة لأعداد مختلفة. في اللغة الإنجليزية، تكتب "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". ثالثًا، والأكثر أهمية، اللغات الأخرى لديها قواعد جمع مختلفة تمامًا لا يمكن لهذا الكود التعامل معها.
توفر جافا سكريبت 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" لتحديد صيغ الجمع للأعداد الترتيبية مثل "1st" و"2nd" و"3rd".
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"]
تختبر هذه التقنية الأعداد الصحيحة والقيم النصفية عبر نطاق معين. وهي تلتقط الفئات التي يحتاج كائن الرسائل الخاص بك إلى تضمينها للغة محلية معينة.