احصل على جميع صيغ الجمع المتاحة في لغة معينة
اكتشف فئات الجمع التي تحتاج إلى توفير ترجمات لها
مقدمة
عند بناء تطبيقات متعددة اللغات، تحتاج إلى توفير صيغ نصية مختلفة للكميات المختلفة. في اللغة الإنجليزية، تكتب "1 item" و"2 items". يبدو هذا بسيطاً حتى تبدأ في دعم لغات أخرى.
تستخدم اللغة الروسية ثلاث صيغ مختلفة حسب العدد. بينما تستخدم اللغة العربية ست صيغ. بعض اللغات تستخدم نفس الصيغة لجميع الأعداد. قبل أن تتمكن من توفير ترجمات لهذه الصيغ، تحتاج إلى معرفة الصيغ الموجودة في كل لغة.
توفر JavaScript طريقة لاكتشاف فئات الجمع التي تستخدمها لغة معينة. تُرجع الدالة resolvedOptions() على نسخة من PluralRules خاصية pluralCategories التي تسرد جميع صيغ الجمع التي تحتاجها اللغة. يخبرك هذا بالضبط بالترجمات التي يجب توفيرها دون تخمين أو الحفاظ على جداول قواعد خاصة باللغة.
ما هي فئات الجمع
فئات الجمع هي أسماء موحدة لصيغ الجمع المختلفة المستخدمة عبر اللغات. يحدد Unicode CLDR (مستودع بيانات اللغة المشتركة) ست فئات: zero وone وtwo وfew وmany وother.
لا تستخدم كل لغة جميع الفئات الست. تستخدم اللغة الإنجليزية فئتين فقط: one وother. تنطبق الفئة one على العدد 1، وتنطبق other على كل شيء آخر.
تستخدم اللغة العربية جميع الفئات الست. تنطبق الفئة zero على 0، وone على 1، وtwo على 2، وfew على أعداد مثل 3-10، وmany على أعداد مثل 11-99، وother على أعداد مثل 100 وما فوق.
تستخدم اللغة الروسية ثلاث فئات: one للأعداد المنتهية بـ 1 (باستثناء 11)، وfew للأعداد المنتهية بـ 2-4 (باستثناء 12-14)، وmany لكل شيء آخر.
تستخدم اليابانية والصينية فئة other فقط لأن هذه اللغات لا تميز بين صيغ المفرد والجمع.
تمثل هذه الفئات القواعد اللغوية لكل لغة. عند تقديم الترجمات، تقوم بإنشاء نص واحد لكل فئة تستخدمها اللغة.
الحصول على فئات الجمع باستخدام resolvedOptions
تُرجع الدالة resolvedOptions() على نسخة PluralRules كائنًا يحتوي على معلومات حول القواعد، بما في ذلك فئات الجمع التي يستخدمها اللوكال.
const enRules = new Intl.PluralRules('en-US');
const options = enRules.resolvedOptions();
console.log(options.pluralCategories);
// Output: ["one", "other"]
الخاصية pluralCategories هي مصفوفة من النصوص. كل نص هو أحد أسماء الفئات الستة القياسية. تحتوي المصفوفة فقط على الفئات التي يستخدمها اللوكال فعليًا.
بالنسبة للإنجليزية، تحتوي المصفوفة على one و other لأن الإنجليزية تميز بين صيغ المفرد والجمع.
بالنسبة للغة ذات قواعد أكثر تعقيدًا، تحتوي المصفوفة على فئات أكثر:
const arRules = new Intl.PluralRules('ar-EG');
const options = arRules.resolvedOptions();
console.log(options.pluralCategories);
// Output: ["zero", "one", "two", "few", "many", "other"]
تستخدم العربية الفئات الست جميعها، لذا تحتوي المصفوفة على القيم الست جميعها.
عرض فئات الجمع للوكالات مختلفة
تحتوي اللغات المختلفة على قواعد جمع مختلفة، مما يعني أنها تستخدم مجموعات مختلفة من الفئات. قارن بين عدة لغات لرؤية التباين:
const locales = ['en-US', 'ar-EG', 'ru-RU', 'pl-PL', 'ja-JP', 'zh-CN'];
locales.forEach(locale => {
const rules = new Intl.PluralRules(locale);
const categories = rules.resolvedOptions().pluralCategories;
console.log(`${locale}: [${categories.join(', ')}]`);
});
// Output:
// en-US: [one, other]
// ar-EG: [zero, one, two, few, many, other]
// ru-RU: [one, few, many, other]
// pl-PL: [one, few, many, other]
// ja-JP: [other]
// zh-CN: [other]
تحتوي الإنجليزية على فئتين. تحتوي العربية على ست فئات. تحتوي الروسية والبولندية على أربع فئات لكل منهما. تحتوي اليابانية والصينية على فئة واحدة فقط لأنهما لا تميزان صيغ الجمع على الإطلاق.
يوضح هذا التباين سبب عدم إمكانية افتراض أن كل لغة تعمل مثل الإنجليزية. تحتاج إلى التحقق من الفئات التي يستخدمها كل لوكال وتقديم ترجمات مناسبة لكل منها.
فهم معنى الفئات لكل لوكال
يعني اسم الفئة نفسه أشياء مختلفة في لغات مختلفة. تنطبق فئة one في الإنجليزية على الرقم 1 فقط. في الروسية، تنطبق one على الأرقام المنتهية بـ 1 باستثناء 11، لذا تشمل 1 و 21 و 31 و 101 وهكذا.
اختبر أي الأرقام تُطابق أي الفئات في اللغات المختلفة:
const enRules = new Intl.PluralRules('en-US');
const ruRules = new Intl.PluralRules('ru-RU');
const numbers = [0, 1, 2, 3, 5, 11, 21, 22, 100];
console.log('English:');
numbers.forEach(n => {
console.log(` ${n}: ${enRules.select(n)}`);
});
console.log('Russian:');
numbers.forEach(n => {
console.log(` ${n}: ${ruRules.select(n)}`);
});
// Output:
// English:
// 0: other
// 1: one
// 2: other
// 3: other
// 5: other
// 11: other
// 21: other
// 22: other
// 100: other
// Russian:
// 0: many
// 1: one
// 2: few
// 3: few
// 5: many
// 11: many
// 21: one
// 22: few
// 100: many
في الإنجليزية، فقط الرقم 1 يستخدم فئة one. في الروسية، كل من 1 و21 يستخدمان one لأنهما ينتهيان بـ 1. الأرقام 2 و3 و22 تستخدم few لأنها تنتهي بـ 2-4. الأرقام 0 و5 و11 و100 تستخدم many.
يوضح هذا أنه لا يمكنك التنبؤ بأي فئة تنطبق على رقم دون معرفة قواعد اللغة. يخبرك المصفوفة pluralCategories بالفئات الموجودة، وتخبرك الدالة select() بأي فئة تنطبق على كل رقم.
الحصول على الفئات للأرقام الترتيبية
الأرقام الترتيبية مثل الأول والثاني والثالث لها قواعد جمع خاصة بها تختلف عن الأرقام الأساسية. أنشئ نسخة PluralRules مع type: 'ordinal' للحصول على الفئات للأرقام الترتيبية:
const enCardinalRules = new Intl.PluralRules('en-US', { type: 'cardinal' });
const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
console.log('Cardinal:', enCardinalRules.resolvedOptions().pluralCategories);
// Output: Cardinal: ["one", "other"]
console.log('Ordinal:', enOrdinalRules.resolvedOptions().pluralCategories);
// Output: Ordinal: ["one", "two", "few", "other"]
تستخدم الأرقام الأساسية الإنجليزية فئتين. تستخدم الأرقام الترتيبية الإنجليزية أربع فئات لأن الأرقام الترتيبية تحتاج إلى التمييز بين الأول والثاني والثالث وجميع الأرقام الأخرى.
تُطابق الفئات الترتيبية اللواحق الترتيبية:
const enOrdinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
const numbers = [1, 2, 3, 4, 11, 21, 22, 23];
numbers.forEach(n => {
const category = enOrdinalRules.select(n);
console.log(`${n}: ${category}`);
});
// Output:
// 1: one
// 2: two
// 3: few
// 4: other
// 11: other
// 21: one
// 22: two
// 23: few
تتوافق الفئة one مع اللاحقة st (الأول، الحادي والعشرون)، وtwo مع nd (الثاني، الثاني والعشرون)، وfew مع rd (الثالث، الثالث والعشرون)، وother مع th (الرابع، الحادي عشر).
اللغات المختلفة لها فئات ترتيبية مختلفة:
const locales = ['en-US', 'es-ES', 'fr-FR'];
locales.forEach(locale => {
const rules = new Intl.PluralRules(locale, { type: 'ordinal' });
const categories = rules.resolvedOptions().pluralCategories;
console.log(`${locale}: [${categories.join(', ')}]`);
});
// Output:
// en-US: [one, two, few, other]
// es-ES: [other]
// fr-FR: [one, other]
تستخدم الإسبانية فئة ترتيبية واحدة فقط لأن الأرقام الترتيبية الإسبانية تتبع نمطاً أبسط. تستخدم الفرنسية فئتين للتمييز بين الأول وجميع المواضع الأخرى.
استخدام فئات الجمع لبناء خرائط الترجمة
عندما تعرف الفئات التي تستخدمها اللغة، يمكنك بناء خريطة ترجمة بالعدد الصحيح تماماً من الإدخالات:
function buildTranslationMap(locale, translations) {
const rules = new Intl.PluralRules(locale);
const categories = rules.resolvedOptions().pluralCategories;
const map = new Map();
categories.forEach(category => {
if (translations[category]) {
map.set(category, translations[category]);
} else {
console.warn(`Missing translation for category "${category}" in locale "${locale}"`);
}
});
return map;
}
const enTranslations = {
one: 'item',
other: 'items'
};
const arTranslations = {
zero: 'لا توجد عناصر',
one: 'عنصر واحد',
two: 'عنصران',
few: 'عناصر',
many: 'عنصرًا',
other: 'عنصر'
};
const enMap = buildTranslationMap('en-US', enTranslations);
const arMap = buildTranslationMap('ar-EG', arTranslations);
console.log(enMap);
// Output: Map(2) { 'one' => 'item', 'other' => 'items' }
console.log(arMap);
// Output: Map(6) { 'zero' => 'لا توجد عناصر', 'one' => 'عنصر واحد', ... }
تتحقق هذه الدالة من أنك قدمت ترجمات لجميع الفئات المطلوبة وتحذرك إذا كانت أي منها مفقودة. يمنع هذا أخطاء وقت التشغيل عندما يتم استخدام فئة ولكن ليس لها ترجمة.
التحقق من اكتمال الترجمة
استخدم فئات الجمع للتحقق من أن ترجماتك تتضمن جميع الأشكال الضرورية قبل النشر في الإنتاج:
function validateTranslations(locale, translations) {
const rules = new Intl.PluralRules(locale);
const requiredCategories = rules.resolvedOptions().pluralCategories;
const providedCategories = Object.keys(translations);
const missing = requiredCategories.filter(cat => !providedCategories.includes(cat));
const extra = providedCategories.filter(cat => !requiredCategories.includes(cat));
if (missing.length > 0) {
console.error(`Locale ${locale} is missing categories: ${missing.join(', ')}`);
return false;
}
if (extra.length > 0) {
console.warn(`Locale ${locale} has unused categories: ${extra.join(', ')}`);
}
return true;
}
const enTranslations = {
one: 'item',
other: 'items'
};
const incompleteArTranslations = {
one: 'عنصر واحد',
other: 'عنصر'
};
validateTranslations('en-US', enTranslations);
// Output: true
validateTranslations('ar-EG', incompleteArTranslations);
// Output: Locale ar-EG is missing categories: zero, two, few, many
// Output: false
يكتشف هذا التحقق الترجمات المفقودة أثناء التطوير بدلاً من اكتشافها عندما يواجه المستخدمون نصاً غير مترجم.
بناء واجهات ترجمة ديناميكية
عند بناء أدوات للمترجمين، استعلم عن فئات الجمع لإظهار الأشكال التي تحتاج إلى ترجمة بالضبط:
function generateTranslationForm(locale, key) {
const rules = new Intl.PluralRules(locale);
const categories = rules.resolvedOptions().pluralCategories;
const form = document.createElement('div');
form.className = 'translation-form';
const heading = document.createElement('h3');
heading.textContent = `Translate "${key}" for ${locale}`;
form.appendChild(heading);
categories.forEach(category => {
const label = document.createElement('label');
label.textContent = `${category}:`;
const input = document.createElement('input');
input.type = 'text';
input.name = `${key}.${category}`;
input.placeholder = `Enter ${category} form`;
const wrapper = document.createElement('div');
wrapper.appendChild(label);
wrapper.appendChild(input);
form.appendChild(wrapper);
});
return form;
}
const enForm = generateTranslationForm('en-US', 'items');
const arForm = generateTranslationForm('ar-EG', 'items');
document.body.appendChild(enForm);
document.body.appendChild(arForm);
ينشئ هذا نموذجاً بالعدد الصحيح من حقول الإدخال لكل لغة. تحصل الإنجليزية على حقلين (one وother)، بينما تحصل العربية على ستة حقول (zero وone وtwo وfew ومany وother).
مقارنة الفئات عبر اللغات
عند إدارة الترجمات لعدة لغات، قارن الفئات التي تستخدمها لفهم تعقيد الترجمة:
function compareLocalePluralCategories(locales) {
const comparison = {};
locales.forEach(locale => {
const rules = new Intl.PluralRules(locale);
const categories = rules.resolvedOptions().pluralCategories;
comparison[locale] = categories;
});
return comparison;
}
const locales = ['en-US', 'es-ES', 'ar-EG', 'ru-RU', 'ja-JP'];
const comparison = compareLocalePluralCategories(locales);
console.log(comparison);
// Output:
// {
// 'en-US': ['one', 'other'],
// 'es-ES': ['one', 'other'],
// 'ar-EG': ['zero', 'one', 'two', 'few', 'many', 'other'],
// 'ru-RU': ['one', 'few', 'many', 'other'],
// 'ja-JP': ['other']
// }
يوضح هذا أن الإنجليزية والإسبانية لديهما نفس فئات الجمع، مما يسهل إعادة استخدام هياكل الترجمة بينهما. تتطلب العربية عملاً أكثر بكثير في الترجمة لأنها تستخدم ستة فئات.
التحقق مما إذا كانت اللغة تستخدم فئة معينة
قبل استخدام فئة جمع معينة في الكود الخاص بك، تحقق مما إذا كانت اللغة تستخدمها فعلياً:
function localeUsesCategory(locale, category) {
const rules = new Intl.PluralRules(locale);
const categories = rules.resolvedOptions().pluralCategories;
return categories.includes(category);
}
console.log(localeUsesCategory('en-US', 'zero'));
// Output: false
console.log(localeUsesCategory('ar-EG', 'zero'));
// Output: true
console.log(localeUsesCategory('ja-JP', 'one'));
// Output: false
يمنعك هذا من افتراض أن كل لغة لديها فئة zero أو فئة one. استخدم هذا الفحص لتنفيذ منطق خاص بالفئة بشكل آمن.
فهم فئة other
تستخدم كل لغة فئة other. تعمل هذه الفئة كحالة افتراضية عندما لا تنطبق أي فئة أخرى.
في اللغة الإنجليزية، تغطي other جميع الأعداد باستثناء 1. في اللغة العربية، تغطي other الأعداد الكبيرة مثل 100 وما فوق. في اللغة اليابانية، تغطي other جميع الأعداد لأن اليابانية لا تميز صيغ الجمع.
قدم دائمًا ترجمة لفئة other. هذه الفئة مضمونة الوجود في كل لغة وسيتم استخدامها عندما لا تتطابق أي فئة أكثر تحديدًا.
const locales = ['en-US', 'ar-EG', 'ru-RU', 'ja-JP'];
locales.forEach(locale => {
const rules = new Intl.PluralRules(locale);
const categories = rules.resolvedOptions().pluralCategories;
const hasOther = categories.includes('other');
console.log(`${locale} uses "other": ${hasOther}`);
});
// Output:
// en-US uses "other": true
// ar-EG uses "other": true
// ru-RU uses "other": true
// ja-JP uses "other": true
الحصول على جميع الخيارات المحلولة معًا
تُرجع الدالة resolvedOptions() أكثر من مجرد فئات الجمع. فهي تتضمن معلومات حول اللغة والنوع وخيارات تنسيق الأرقام:
const rules = new Intl.PluralRules('de-DE', {
type: 'cardinal',
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
const options = rules.resolvedOptions();
console.log(options);
// Output:
// {
// locale: 'de-DE',
// type: 'cardinal',
// pluralCategories: ['one', 'other'],
// minimumIntegerDigits: 1,
// minimumFractionDigits: 2,
// maximumFractionDigits: 2,
// minimumSignificantDigits: undefined,
// maximumSignificantDigits: undefined
// }
الخاصية pluralCategories هي جزء واحد من المعلومات في كائن الخيارات المحلولة. تخبرك الخصائص الأخرى بالضبط بالتكوين الذي يستخدمه مثيل PluralRules، بما في ذلك أي خيارات تم تعيينها إلى قيم افتراضية.
التخزين المؤقت لفئات الجمع لتحسين الأداء
إنشاء مثيلات PluralRules واستدعاء resolvedOptions() له تكلفة. قم بتخزين النتائج مؤقتًا لكل لغة بدلاً من الاستعلام عنها بشكل متكرر:
const categoriesCache = new Map();
function getPluralCategories(locale, type = 'cardinal') {
const key = `${locale}:${type}`;
if (categoriesCache.has(key)) {
return categoriesCache.get(key);
}
const rules = new Intl.PluralRules(locale, { type });
const categories = rules.resolvedOptions().pluralCategories;
categoriesCache.set(key, categories);
return categories;
}
const enCardinal = getPluralCategories('en-US', 'cardinal');
const enOrdinal = getPluralCategories('en-US', 'ordinal');
const arCardinal = getPluralCategories('ar-EG', 'cardinal');
console.log('en-US cardinal:', enCardinal);
console.log('en-US ordinal:', enOrdinal);
console.log('ar-EG cardinal:', arCardinal);
// Subsequent calls use cached results
const enCardinal2 = getPluralCategories('en-US', 'cardinal');
// No new PluralRules instance created
هذا النمط مهم بشكل خاص في التطبيقات التي تقوم بتنسيق العديد من السلاسل النصية المُجمَّعة أو تدعم العديد من اللغات.
دعم المتصفحات والتوافق
تمت إضافة الخاصية pluralCategories على resolvedOptions() إلى JavaScript في عام 2020. وهي مدعومة في Chrome 106+، وFirefox 116+، وSafari 15.4+، وEdge 106+.
المتصفحات القديمة التي تدعم Intl.PluralRules ولكن ليس pluralCategories ستُرجع undefined لهذه الخاصية. تحقق من وجودها قبل استخدامها:
function getPluralCategories(locale) {
const rules = new Intl.PluralRules(locale);
const options = rules.resolvedOptions();
if (options.pluralCategories) {
return options.pluralCategories;
}
// Fallback for older browsers
return ['one', 'other'];
}
يفترض هذا البديل نظامًا بسيطًا من فئتين، والذي يعمل مع اللغة الإنجليزية والعديد من اللغات الأوروبية ولكن قد لا يكون صحيحًا للغات ذات القواعد الأكثر تعقيدًا. للحصول على توافق أفضل، قدم بدائل خاصة باللغة أو استخدم polyfill.