واجهة برمجة التطبيقات Intl.Collator
فرز ومقارنة السلاسل النصية بشكل صحيح عبر اللغات
مقدمة
يبدو فرز السلاسل النصية في JavaScript أمراً مباشراً حتى تواجه نصوصاً دولية. تستخدم المقارنة الافتراضية للسلاسل النصية قيم نقاط Unicode، مما ينتج نتائج غير صحيحة للعديد من اللغات. توفر واجهة برمجة التطبيقات Intl.Collator مقارنة للسلاسل النصية تراعي اللغة المحلية وتحترم قواعد الفرز الثقافية وتتعامل مع الأحرف الخاصة بشكل صحيح.
لماذا يفشل الفرز الافتراضي
لنأخذ مثالاً على فرز قائمة من الأسماء الألمانية:
const names = ["Zoe", "Ava", "Ärzte", "Änder"];
console.log(names.sort());
// ["Ava", "Zoe", "Änder", "Ärzte"]
هذه النتيجة خاطئة بالنسبة للمتحدثين بالألمانية. في اللغة الألمانية، يجب أن تُفرز الأحرف ذات العلامات الصوتية مثل ä بالقرب من الحرف الأساسي a، وليس في النهاية. تنبع المشكلة من مقارنة JavaScript لقيم نقاط Unicode، حيث يأتي Ä (U+00C4) بعد Z (U+005A).
تمتلك اللغات المختلفة قواعد فرز مختلفة. تفرز السويدية ä في نهاية الأبجدية، بينما تفرزها الألمانية بالقرب من a، وتتعامل الفرنسية مع الأحرف المشكّلة بشكل مختلف. تتجاهل المقارنة الثنائية هذه الاتفاقيات الثقافية.
كيف يعمل ترتيب السلاسل النصية
الترتيب هو عملية مقارنة وترتيب السلاسل النصية وفقاً لقواعد خاصة باللغة. تحدد خوارزمية ترتيب Unicode كيفية مقارنة السلاسل النصية من خلال تحليل الأحرف والعلامات الصوتية وحالة الأحرف وعلامات الترقيم بشكل منفصل.
عند مقارنة سلسلتين نصيتين، تُرجع دالة الترتيب رقماً:
- قيمة سالبة: السلسلة النصية الأولى تأتي قبل الثانية
- صفر: السلسلتان النصيتان متكافئتان لمستوى الحساسية الحالي
- قيمة موجبة: السلسلة النصية الأولى تأتي بعد الثانية
يعمل نمط المقارنة الثلاثية هذا مع Array.sort ويتيح تحكماً دقيقاً في الاختلافات المهمة.
استخدام localeCompare للفرز الأساسي المراعي للغة المحلية
توفر طريقة localeCompare مقارنة للسلاسل النصية تراعي اللغة المحلية:
const names = ["Zoe", "Ava", "Ärzte", "Änder"];
console.log(names.sort((a, b) => a.localeCompare(b, "de")));
// ["Ava", "Änder", "Ärzte", "Zoe"]
ينتج هذا ترتيبًا ألمانيًا صحيحًا. يحدد المعامل الأول اللغة المحلية، وتتعامل localeCompare مع القواعد الثقافية تلقائيًا.
يمكنك تمرير خيارات كمعامل ثالث:
const items = ["File10", "File2", "File1"];
console.log(items.sort((a, b) =>
a.localeCompare(b, "en", { numeric: true })
));
// ["File1", "File2", "File10"]
يتيح خيار numeric الترتيب الطبيعي حيث يأتي "2" قبل "10". بدونه، سيتم ترتيب "10" قبل "2" لأن "1" يأتي قبل "2".
مشكلة الأداء مع localeCompare المتكررة
تعالج كل استدعاء لـ localeCompare إعدادات اللغة المحلية من الصفر. عند ترتيب المصفوفات الكبيرة، يؤدي هذا إلى عبء كبير:
// Inefficient: processes locale for every comparison
const sorted = items.sort((a, b) => a.localeCompare(b, "de"));
يتطلب ترتيب 1000 عنصر حوالي 10000 مقارنة. تعيد كل مقارنة إنشاء تكوين اللغة المحلية، مما يضاعف تكلفة الأداء. يصبح هذا العبء ملحوظًا في واجهات المستخدم التي تحتوي على مجموعات بيانات كبيرة.
استخدام Intl.Collator لمقارنة السلاسل النصية بكفاءة
ينشئ Intl.Collator كائن مقارنة قابل لإعادة الاستخدام يعالج إعدادات اللغة المحلية مرة واحدة:
const collator = new Intl.Collator("de");
const sorted = items.sort((a, b) => collator.compare(a, b));
يخزن مثيل collator تكوين اللغة المحلية وقواعد المقارنة. تستخدم طريقة compare هذه القواعد المحسوبة مسبقًا لكل مقارنة، مما يلغي عبء التهيئة المتكررة.
تتراوح تحسينات الأداء من 60% إلى 80% عند ترتيب المصفوفات الكبيرة مقارنة باستدعاءات localeCompare المتكررة.
الوصول إلى طريقة compare مباشرة
يمكنك تمرير طريقة compare مباشرة إلى sort:
const collator = new Intl.Collator("de");
const sorted = items.sort(collator.compare);
يعمل هذا لأن compare مرتبطة بمثيل collator. تتلقى الطريقة سلسلتين نصيتين وتعيد نتيجة المقارنة، مطابقة للتوقيع الذي يتوقعه Array.sort.
فهم مستويات الحساسية
يتحكم خيار sensitivity في الاختلافات بين الأحرف التي تهم أثناء المقارنة. هناك أربعة مستويات:
حساسية الأساس
تتجاهل حساسية الأساس علامات التشكيل وحالة الأحرف:
const collator = new Intl.Collator("en", { sensitivity: "base" });
console.log(collator.compare("a", "a")); // 0
console.log(collator.compare("a", "á")); // 0
console.log(collator.compare("a", "A")); // 0
console.log(collator.compare("a", "b")); // -1
تختلف الأحرف الأساسية فقط. يعمل هذا المستوى بشكل جيد للبحث الغامض حيث قد لا يكتب المستخدمون علامات التشكيل بشكل صحيح.
حساسية علامات التشكيل
تأخذ حساسية علامات التشكيل في الاعتبار علامات التشكيل ولكنها تتجاهل حالة الأحرف:
const collator = new Intl.Collator("en", { sensitivity: "accent" });
console.log(collator.compare("a", "a")); // 0
console.log(collator.compare("a", "á")); // -1
console.log(collator.compare("a", "A")); // 0
console.log(collator.compare("á", "A")); // 1
تختلف الأحرف المشكّلة وغير المشكّلة. تتطابق الأحرف الكبيرة والصغيرة من نفس الحرف.
حساسية حالة الأحرف
تأخذ حساسية حالة الأحرف في الاعتبار حالة الأحرف ولكنها تتجاهل علامات التشكيل:
const collator = new Intl.Collator("en", { sensitivity: "case" });
console.log(collator.compare("a", "a")); // 0
console.log(collator.compare("a", "á")); // 0
console.log(collator.compare("a", "A")); // -1
console.log(collator.compare("á", "Á")); // -1
تهم الفروق في حالة الأحرف ولكن يتم تجاهل علامات التشكيل. هذا المستوى أقل شيوعًا في الممارسة العملية.
حساسية المتغيرات
تأخذ حساسية المتغيرات في الاعتبار جميع الفروق:
const collator = new Intl.Collator("en", { sensitivity: "variant" });
console.log(collator.compare("a", "a")); // 0
console.log(collator.compare("a", "á")); // -1
console.log(collator.compare("a", "A")); // -1
console.log(collator.compare("á", "Á")); // -1
هذا هو الإعداد الافتراضي للفرز. ينتج عن كل اختلاف في الأحرف نتيجة مقارنة مميزة.
اختيار الحساسية بناءً على حالة الاستخدام
تحتاج السيناريوهات المختلفة إلى مستويات حساسية مختلفة:
- فرز القوائم: استخدم حساسية المتغيرات للحفاظ على ترتيب صارم
- البحث في المحتوى: استخدم الحساسية الأساسية للمطابقة بغض النظر عن علامات التشكيل أو حالة الأحرف
- تصفية الخيارات: استخدم حساسية علامات التشكيل عندما لا يجب أن تهم حالة الأحرف
- البحث الحساس لحالة الأحرف: استخدم حساسية حالة الأحرف عندما لا يجب أن تهم علامات التشكيل
يوفر خيار الاستخدام إعدادات حساسية افتراضية للسيناريوهات الشائعة.
استخدام خيار الاستخدام لأوضاع الفرز والبحث
يحسّن خيار الاستخدام سلوك المقارن للفرز أو البحث:
// Optimized for sorting
const sortCollator = new Intl.Collator("en", { usage: "sort" });
// Optimized for searching
const searchCollator = new Intl.Collator("en", { usage: "search" });
يستخدم وضع الفرز افتراضيًا حساسية المتغيرات، مما يضمن أن كل اختلاف ينتج عنه ترتيب متسق. يحسّن وضع البحث لإيجاد التطابقات، ويستخدم عادةً حساسية أكثر مرونة.
للبحث غير الحساس لحالة الأحرف وعلامات التشكيل:
const collator = new Intl.Collator("en", {
usage: "search",
sensitivity: "base"
});
const items = ["Apple", "Äpfel", "Banana"];
const matches = items.filter(item =>
collator.compare(item, "apple") === 0
);
console.log(matches); // ["Apple"]
يتيح هذا النمط المطابقة التقريبية حيث لا يحتاج المستخدمون إلى كتابة الأحرف الدقيقة.
تمكين الفرز الرقمي للترتيب الطبيعي
يعامل الخيار الرقمي الأرقام المضمنة كقيم رقمية:
const collator = new Intl.Collator("en", { numeric: true });
const files = ["File1", "File10", "File2"];
console.log(files.sort(collator.compare));
// ["File1", "File2", "File10"]
بدون الفرز الرقمي، سيتم ترتيب "File10" قبل "File2" لأن السلسلة النصية "10" تبدأ بـ "1". يقوم الفرز الرقمي بتحليل تسلسلات الأرقام ومقارنتها رياضياً.
ينتج عن هذا ترتيب طبيعي يتطابق مع التوقعات البشرية لأسماء الملفات وأرقام الإصدارات والقوائم المرقمة.
التعامل مع الأرقام العشرية باستخدام الفرز الرقمي
يحتوي الفرز الرقمي على قيد فيما يتعلق بالأرقام العشرية:
const collator = new Intl.Collator("en", { numeric: true });
const values = ["1.5", "1.10", "1.2"];
console.log(values.sort(collator.compare));
// ["1.2", "1.5", "1.10"]
تُعامل العلامة العشرية كعلامة ترقيم، وليست جزءاً من الرقم. يتم فرز كل مقطع بين علامات الترقيم بشكل منفصل. لفرز الأرقام العشرية، قم بتحليل القيم إلى أرقام واستخدم المقارنة الرقمية.
التحكم في ترتيب الأحرف الكبيرة والصغيرة باستخدام caseFirst
يحدد خيار caseFirst ما إذا كانت الأحرف الكبيرة أو الصغيرة تُفرز أولاً:
// Uppercase first
const upperFirst = new Intl.Collator("en", { caseFirst: "upper" });
console.log(["a", "A", "b", "B"].sort(upperFirst.compare));
// ["A", "a", "B", "b"]
// Lowercase first
const lowerFirst = new Intl.Collator("en", { caseFirst: "lower" });
console.log(["a", "A", "b", "B"].sort(lowerFirst.compare));
// ["a", "A", "b", "B"]
القيمة الافتراضية هي false، والتي تستخدم الترتيب الافتراضي للغة المحلية. لا يكون لهذا الخيار أي تأثير عندما تكون sensitivity هي base أو accent لأن هذه المستويات تتجاهل حالة الأحرف.
تجاهل علامات الترقيم أثناء المقارنة
يتخطى خيار ignorePunctuation علامات الترقيم أثناء المقارنة:
const collator = new Intl.Collator("en", { ignorePunctuation: true });
console.log(collator.compare("hello", "he-llo")); // 0
console.log(collator.compare("hello", "hello!")); // 0
القيمة الافتراضية لهذا الخيار هي true للغة التايلاندية وfalse للغات الأخرى. استخدمه عندما لا يجب أن تؤثر علامات الترقيم على ترتيب السلاسل النصية أو مطابقتها.
تحديد أنواع الترتيب للقواعد الخاصة باللغة
تدعم بعض اللغات المحلية أنواعاً متعددة من الترتيب للفرز المتخصص:
// Chinese pinyin ordering
const pinyin = new Intl.Collator("zh-CN-u-co-pinyin");
// German phonebook ordering
const phonebook = new Intl.Collator("de-DE-u-co-phonebk");
// Emoji grouping
const emoji = new Intl.Collator("en-u-co-emoji");
يتم تحديد نوع الترتيب في سلسلة اللغة المحلية باستخدام صيغة امتداد Unicode. تشمل الأنواع الشائعة:
pinyin: الفرز الصيني حسب النطق المكتوب بالحروف اللاتينيةstroke: الفرز الصيني حسب عدد الضرباتphonebk: ترتيب دليل الهاتف الألمانيtrad: قواعد الفرز التقليدية لبعض اللغاتemoji: تجميع الرموز التعبيرية حسب الفئة
تحقق من Intl.supportedValuesOf للحصول على أنواع الترتيب المتاحة في بيئتك.
إعادة استخدام مثيلات المقارن عبر تطبيقك
أنشئ مثيلات المقارن مرة واحدة وأعد استخدامها في جميع أنحاء تطبيقك:
// utils/collation.js
export const germanCollator = new Intl.Collator("de");
export const searchCollator = new Intl.Collator("en", {
sensitivity: "base"
});
export const numericCollator = new Intl.Collator("en", {
numeric: true
});
// In your components
import { germanCollator } from "./utils/collation";
const sorted = names.sort(germanCollator.compare);
يعمل هذا النمط على تحقيق أقصى قدر من الأداء والحفاظ على سلوك مقارنة متسق عبر قاعدة التعليمات البرمجية الخاصة بك.
ترتيب مصفوفات الكائنات حسب الخاصية
استخدم المقارن في دالة مقارنة تصل إلى خصائص الكائن:
const collator = new Intl.Collator("de");
const users = [
{ name: "Zoe" },
{ name: "Änder" },
{ name: "Ava" }
];
const sorted = users.sort((a, b) =>
collator.compare(a.name, b.name)
);
يعمل هذا النهج مع أي بنية كائن. استخرج السلاسل النصية للمقارنة ومررها إلى المقارن.
مقارنة أداء Intl.Collator مع localeCompare
يوفر Intl.Collator أداءً أفضل عند ترتيب مجموعات البيانات الكبيرة:
// Slower: recreates locale settings for each comparison
const slow = items.sort((a, b) => a.localeCompare(b, "de"));
// Faster: reuses precomputed locale settings
const collator = new Intl.Collator("de");
const fast = items.sort(collator.compare);
بالنسبة للمصفوفات الصغيرة (أقل من 100 عنصر)، يكون الفرق ضئيلاً. بالنسبة للمصفوفات الكبيرة (آلاف العناصر)، يمكن أن يكون Intl.Collator أسرع بنسبة 60-80%.
يوجد استثناء واحد في المتصفحات المستندة إلى V8 مثل Chrome. يحتوي localeCompare على تحسين للسلاسل النصية ASCII فقط باستخدام جداول البحث. عند ترتيب سلاسل نصية ASCII بحتة، قد يكون أداء localeCompare مماثلاً لـ Intl.Collator.
معرفة متى تستخدم Intl.Collator مقابل localeCompare
استخدم Intl.Collator عندما:
- ترتيب المصفوفات الكبيرة (مئات أو آلاف العناصر)
- الترتيب بشكل متكرر (يبدل المستخدم ترتيب الفرز، القوائم الافتراضية)
- بناء أدوات مقارنة قابلة لإعادة الاستخدام
- يكون الأداء مهماً لحالة الاستخدام الخاصة بك
استخدم localeCompare عندما:
- إجراء مقارنات لمرة واحدة
- ترتيب المصفوفات الصغيرة (أقل من 100 عنصر)
- تفوق البساطة مخاوف الأداء
- تحتاج إلى مقارنة مضمنة بدون إعداد
تدعم كلتا الواجهتين نفس الخيارات وتنتجان نتائج متطابقة. الفرق يتعلق فقط بالأداء وتنظيم التعليمات البرمجية.
التحقق من الخيارات المحلولة
تُرجع طريقة resolvedOptions الخيارات الفعلية المستخدمة بواسطة المقارن:
const collator = new Intl.Collator("de", { sensitivity: "base" });
console.log(collator.resolvedOptions());
// {
// locale: "de",
// usage: "sort",
// sensitivity: "base",
// ignorePunctuation: false,
// collation: "default",
// numeric: false,
// caseFirst: "false"
// }
يساعد هذا في تصحيح سلوك الترتيب وفهم القيم الافتراضية. قد تختلف اللغة المحلية المحددة عن اللغة المحلية المطلوبة إذا كان النظام لا يدعم اللغة المحلية بالضبط.
التحقق من دعم اللغة المحلية
تحقق من اللغات المحلية المدعومة في البيئة الحالية:
const supported = Intl.Collator.supportedLocalesOf(["de", "fr", "xx"]);
console.log(supported); // ["de", "fr"]
تعود اللغات المحلية غير المدعومة إلى الإعداد الافتراضي للنظام. تساعد هذه الطريقة في اكتشاف عدم توفر اللغة المحلية المطلوبة.
دعم المتصفح والبيئة
يتم دعم Intl.Collator على نطاق واسع منذ سبتمبر 2017. جميع المتصفحات الحديثة وإصدارات Node.js تدعمه. تعمل واجهة برمجة التطبيقات بشكل متسق عبر البيئات المختلفة.
قد يكون لبعض أنواع الترتيب والخيارات دعم محدود في المتصفحات القديمة. اختبر الوظائف الحرجة أو راجع جداول التوافق في MDN إذا كنت تدعم بيئات قديمة.
الأخطاء الشائعة التي يجب تجنبها
لا تنشئ مرتبًا جديدًا لكل مقارنة:
// Wrong: creates collator repeatedly
items.sort((a, b) => new Intl.Collator("de").compare(a, b));
// Right: create once, reuse
const collator = new Intl.Collator("de");
items.sort(collator.compare);
لا تفترض أن الترتيب الافتراضي يعمل مع النصوص الدولية:
// Wrong: breaks for non-ASCII characters
names.sort();
// Right: use locale-aware sorting
names.sort(new Intl.Collator("de").compare);
لا تنسَ تحديد الحساسية للبحث:
// Wrong: variant sensitivity requires exact match
const collator = new Intl.Collator("en");
items.filter(item => collator.compare(item, "apple") === 0);
// Right: base sensitivity for fuzzy matching
const collator = new Intl.Collator("en", { sensitivity: "base" });
items.filter(item => collator.compare(item, "apple") === 0);
حالات الاستخدام العملية
استخدم Intl.Collator لـ:
- ترتيب المحتوى الذي ينشئه المستخدم (الأسماء، العناوين، العناوين)
- تنفيذ ميزات البحث والإكمال التلقائي
- بناء جداول البيانات مع أعمدة قابلة للترتيب
- إنشاء قوائم مفلترة وخيارات القوائم المنسدلة
- ترتيب أسماء الملفات وأرقام الإصدارات
- التنقل الأبجدي في قوائم جهات الاتصال
- واجهات التطبيقات متعددة اللغات
أي واجهة تعرض نصًا مرتبًا للمستخدمين تستفيد من الترتيب الواعي باللغة المحلية. يضمن هذا أن يبدو تطبيقك أصليًا وصحيحًا بغض النظر عن لغة المستخدم.