تطبيع البحث العربي: نقص الدعم لمتغيرات الهمزة، وأشكال الياء/الكاف، والتكافؤ الإملائي

مرحباً فريق Discourse،

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

:magnifying_glass_tilted_left: وصف المشكلة

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

أمثلة:

  • البحث عن إطلاق مقامي يعيد فقط التطابقات الدقيقة، بينما يتم استبعاد المشاركات التي تحتوي على اطلاق مقامي أو أطلاق مقامي أو إطلاق‌مقامي.
  • وبالمثل، فإن البحث عن ي (U+064A) لا يتطابق مع ی (U+06CC)، وك (U+0643) يفشل في التطابق مع ک (U+06A9)، على الرغم من تكافؤها الوظيفي في السياقات العربية/الفارسية.

لا يؤثر هذا فقط على اختلافات الهمزة (أ، إ، ء، ؤ، ئ) ولكن أيضًا على البدائل الشائعة مثل:

الحرف يونيكود التطبيع المقترح
أ، إ، ء، آ U+0623، U+0625، U+0621، U+0622 تطبيع إلى ا
ؤ U+0624 تطبيع إلى و
ئ U+0626 تطبيع إلى ي
ى U+0649 تطبيع إلى ي
ة U+0629 تطبيع إلى ه
ي مقابل ی U+064A مقابل U+06CC تطبيع إلى ی
ك مقابل ک U+0643 مقابل U+06A9 تطبيع إلى ک

تتفاقم هذه المشكلة عندما يتجاهل المستخدمون علامات التشكيل أو يستخدمون لوحات مفاتيح مختلفة، مما يؤدي إلى سلوك بحث مجزأ.


:gear: الحل المقترح

نوصي بتطبيق طبقة تطبيع مدركة لليونيكود أثناء الفهرسة وتحليل الاستعلامات. يمكن تحقيق ذلك من خلال:

  1. المعالجة المسبقة للمحتوى المفهرس واستعلامات المستخدم لتوحيد اختلافات الأحرف.
  2. تطبيق قواعد التطبيع المشابهة لتلك المستخدمة في مكتبات معالجة اللغة العربية الطبيعية أو محركات البحث (مثل Farasa أو Hazm أو أدوات تعيين مخصصة تعتمد على التعبيرات العادية).
  3. اختياريًا، دعم المطابقة التقريبية أو مسافة ليفنشتاين للمطابقات شبه الدقيقة.

إليك مثال مبسط لدالة تطبيع (بأسلوب Java):

public static String normalizeArabic(String text) {
  return text.replace("أ", "ا")
             .replace("إ", "ا")
             .replace("آ", "ا")
             .replace("ؤ", "و")
             .replace("ئ", "ي")
             .replace("ى", "ي")
             .replace("ة", "ه")
             .replace("ي", "ی")
             .replace("ك", "ک");
}

:folded_hands: طلب

هل يمكن النظر في تضمين هذا التطبيع في محرك البحث الأساسي أو كإضافة (plugin)؟ سيؤدي ذلك إلى تحسين قابلية الاستخدام بشكل كبير للمجتمعات العربية والفارسية التي تستخدم Discourse.

إذا كان هناك حل بديل أو إضافة تعالج هذا الأمر، سنكون ممتنين لأي توجيه.

شكراً لوقتكم ولبناء مثل هذه المنصة القوية.

مع خالص التقدير

إعجاب واحد (1)

هل لإعداد الموقع search_ignore_accents أي تأثير على هذه المشكلة؟

إعجاب واحد (1)

شكراً جزيلاً على مشاركتك والمساهمة في النقاش

للإجابة على سؤالك: نعم، تم تفعيل إعداد search_ignore_accents في منتدانا.
للأسف، هذا لا يحل المشكلة التي نواجهها. لا تزال نتائج البحث تفشل في مطابقة الأحرف العربية والفارسية المتكافئة إملائياً، لذا تستمر المشكلة على الرغم من هذا الإعداد.

إعجاب واحد (1)

أعتقد أن هذا طلب معقول لأنه سيحسن تجربة البحث للمواقع العربية والفارسية بشكل كبير. نود مراجعة طلب سحب (PR) ينفذ هذه الميزة، لذلك سأضع عليها علامة pr-welcome.

بالنسبة لأي شخص يقرر العمل على هذه الميزة: يجب أن يتم التحكم في جميع منطق التطبيع خلف إعداد موقع يتيح ذلك افتراضيًا للمواقع العربية والفارسية (انظر locale_default في site_settings.yml) ويجب أن يكون هذا الإعداد متوقفًا افتراضيًا لجميع المواقع الأخرى. يحتوي الأساس بالفعل على منطق تطبيع مشابه للأحرف المشددة (انظر lib/search.rb)، لذا سيكون ذلك مرجعًا مفيدًا عند تنفيذ هذه الميزة.

4 إعجابات

شكراً جزيلاً لك يا أسامة! يسعدني حقاً أن أرى أن هذا الاقتراح قد لاقى استحساناً.

إعجابَين (2)

بالنسبة لهذا الجزء من المشكلة، هل نتحدث عن تطبيع معيار يونيكود NFKC (لاختيار واحد)؟

(أنا لست متأكدًا حتى مما نفعله… أفترض أننا نقوم بتطبيع نص المنشور في خط أنابيب الطهي؟)

إعجاب واحد (1)

لست خبيرًا تقنيًا، ولكني كنت أبحث في هذه المشكلة لأنني أريد التأكد من عدم تفويت أي استعلامات بحث في منتدى ثنائي اللغة بالفارسية والعربية. نظرًا لأن Discourse يستخدم PostgreSQL، فإن التطبيع يصبح ضروريًا: قد يبحث المستخدم باستخدام أحرف فارسية، بينما يتم تخزين نفس الكلمة باستخدام أحرف عربية - أو العكس. بدون تطبيع مناسب، ستفشل عملية البحث.
بناءً على ما تعلمته، فإن استخدام تطبيع Unicode NFKC هو نقطة انطلاق قوية - فهو يتعامل مع العديد من حالات التوافق مثل الروابط، وأشكال العرض، والأرقام العربية/الفارسية.
ومع ذلك، بالنسبة للنص الفارسي والعربي، لا يكفي NFKC وحده. فهو لا يقوم بتطبيع العديد من المتغيرات الحرفية الهامة المتكافئة بصريًا ودلاليًا ولكنها تختلف على المستوى الثنائي.

أدناه، أقدم الإجراءات والرؤى التي توصلت إليها من خلال بحثي واستكشافي.


रणनी استراتيجية التصميم الشاملة

  1. طبق تطبيع Unicode NFKC أولاً للتعامل مع الروابط، وأشكال العرض، وتوحيد الأرقام.
  2. ثم طبق تعيينات الأحرف المخصصة بترتيب محدد (على سبيل المثال، تطبيع متغيرات الهمزة قبل الياء العربية).
  3. فصل سياسات التطبيع للتخزين مقابل البحث:
    • استخدم ملف تعريف محافظ للتخزين القياسي (احتفظ بـ ZWNJ، وتجنب التحولات الدلالية).
    • استخدم ملف تعريف متساهل للبحث (تجاهل ZWNJ، ووحد متغيرات الهمزة، وطبع الأرقام).
  4. يجب أن تكون جميع التعيينات قابلة للتكوين عبر جدول تعيين مركزي في قاعدة البيانات أو تجزئة Ruby في التطبيق.

:one: ملفات تعريف التطبيع

:green_circle: محافظ (للتخزين)

  • تحويل أدنى
  • طبق NFKC
  • طبع الكاف/الياء العربية إلى ما يعادلها بالفارسية
  • إزالة علامات التشكيل
  • احتفظ بـ ZWNJ
  • التخزين كـ original_text + normalized_conservative

:blue_circle: متساهل (للبحث)

  • مطابقة قوية
  • طبق جميع قواعد المحافظ
  • إزالة/تجاهل ZWNJ
  • طبع متغيرات الهمزة إلى الأحرف الأساسية
  • تحويل جميع الأرقام إلى ASCII
  • اختياريًا توحيد التاء المربوطة → الهاء
  • يستخدم لمعالجة استعلامات البحث مسبقًا

:two: جدول تعيين شامل

المصدر الهدف يونيكود ملاحظات
ك ک U+0643 → U+06A9 الكاف العربية → الكاف الفارسية
ي ی U+064A → U+06CC الياء العربية → الياء الفارسية
ى ی U+0649 → U+06CC متغير الياء النهائي
أ، إ، ٱ ا مختلف → U+0627 أشكال الهمزة → الألف
ؤ و U+0624 → U+0648 الهمزة على الواو
ئ ی U+0626 → U+06CC الهمزة على الياء
ء U+0621 إزالة أو الاحتفاظ (قابل للتكوين)
ة ه U+0629 → U+0647 التاء المربوطة → الهاء (اختياري)
ۀ هٔ U+06C0 ↔ U+0647+U+0654 تطبيع الشكل المركب
ڭ گ U+06AD → U+06AF المتغيرات الإقليمية
U+200C ZWNJ: الاحتفاظ في المحافظ، الإزالة في المتساهل
٤، ۴ 4 U+0664، U+06F4 → ASCII تطبيع الأرقام
علامات التشكيل U+064B–U+0652 إزالة جميع الحركات
ZWJ U+200D إزالة الروابط غير المرئية
مسافات متعددة مسافة واحدة تطبيع المسافات

:three: مقتطف تعيين سريع (لـ SQL أو Ruby)

ك → ک
ي → ی
ى → ی
أ → ا
إ → ا
ؤ → و
ئ → ی
ة → ه
ۀ → هٔ
ٱ → ا
٤, ۴ → 4
ZWNJ (U+200C) → (تمت إزالته في المتساهل)
الحركات (U+064B..U+0652) → تمت إزالتها
ZWJ (U+200D) → تمت إزالته

:four: التنفيذ في PostgreSQL

  • إنشاء جدول text_normalization_map
  • استخدام regexp_replace أو سلاسل TRANSLATE لتحسين الأداء
  • اختياريًا، التنفيذ في PL/Python أو PL/v8 لدعم Unicode
  • تطبيع كل من المحتوى المخزن والاستعلامات الواردة باستخدام نفس المنطق

استراتيجية الفهرسة

  • تخزين normalized_conservative للفهرسة القياسية
  • تطبيع الاستعلامات باستخدام normalize_persian_arabic(query, 'permissive')
  • إذا كنت تستخدم البحث المتساهل، فيجب أن يتطابق الفهرس مع نفس الملف الشخصي
  • اختياريًا، تخزين كلا الإصدارين للمقارنة المتبادلة

:five: مثال تجزئة Ruby (لـ Discourse)

NORMALIZATION_MAP = {
  "ك" => "ک",
  "ي" => "ی",
  "ى" => "ی",
  "أ" => "ا",
  "إ" => "ا",
  "ٱ" => "ا",
  "ؤ" => "و",
  "ئ" => "ی",
  "ة" => "ه",
  "ۀ" => "هٔ",
  "۴" => "4",
  "٤" => "4",
  "\u200C" => "", # ZWNJ
  "\u200D" => "", # ZWJ
}

:six: ملاحظات الأداء والعملية

  1. طبق NFKC في طبقة التطبيق (على سبيل المثال، Ruby unicode_normalize(:nfkc))
  2. استخدم فهارس منفصلة لملفات التعريف المحافظة مقابل المتساهلة
  3. تجنب التعيينات الإجبارية للأحرف الحساسة دلاليًا (مثل الهمزة، التاء المربوطة) ما لم يتم تكوينها صراحةً
  4. قم بإجراء اختبارات A/B على بيانات المنتدى الفعلية لقياس معدل الإصابات والإيجابيات الخاطئة
  5. وثق كل تعيين مع الأساس المنطقي والأمثلة
  6. حدد اختبارات الوحدة في كل من Ruby و SQL لكل تعيين

:seven: التوصية النهائية

  • استخدم Unicode NFKC كأساس
  • قم بتوسيعه باستخدام طبقة تعيين مخصصة
  • حافظ على ملفات تعريف مزدوجة للتخزين والبحث
  • نفذ التطبيع في كل من طبقات التطبيق وقاعدة البيانات
  • وثق واختبر كل تعيين
  • أنشئ فهارس مناسبة (GIN + to_tsvector) على الأعمدة المطبعة