Normalizzazione della ricerca araba: supporto mancante per varianti Hamza, forme Ya/Kaf ed equivalenza ortografica

Ciao team di Discourse,

Stiamo gestendo un forum multilingue con un contenuto significativo in arabo e persiano e abbiamo riscontrato una limitazione critica nella funzionalità di ricerca relativa alla normalizzazione ortografica araba.

:magnifying_glass_tilted_left: Descrizione del problema

Lo script arabo include più rappresentazioni Unicode per caratteri semanticamente identici. Sfortunatamente, l’attuale motore di ricerca di Discourse sembra trattare queste varianti come distinte, il che porta a risultati di ricerca incompleti o fuorvianti.

Esempi:

  • La ricerca di إطلاق مقامي restituisce solo corrispondenze esatte, mentre i post contenenti اطلاق مقامي, أطلاق مقامي o إطلاق‌مقامي sono esclusi.
  • Allo stesso modo, la ricerca di ي (U+064A) non corrisponde a ی (U+06CC) e ك (U+0643) non corrisponde a ک (U+06A9), nonostante la loro equivalenza funzionale nei contesti arabo/persiano.

Ciò influisce non solo sulle varianti di hamza (أ, إ, ء, ؤ, ئ), ma anche sulle sostituzioni comuni come:

Carattere Unicode Normalizzazione suggerita
أ, إ, ء, آ U+0623, U+0625, U+0621, U+0622 Normalizzare a ا
ؤ U+0624 Normalizzare a و
ئ U+0626 Normalizzare a ي
ى U+0649 Normalizzare a ي
ة U+0629 Normalizzare a ه
ي vs ی U+064A vs U+06CC Normalizzare a ی
ك vs ک U+0643 vs U+06A9 Normalizzare a ک

Questo problema è aggravato quando gli utenti omettono i segni diacritici o utilizzano layout di tastiera diversi, con conseguente comportamento di ricerca frammentato.


:gear: Soluzione proposta

Si consiglia di implementare un livello di normalizzazione consapevole di Unicode durante l’indicizzazione e l’analisi delle query. Ciò può essere ottenuto mediante:

  1. Pre-elaborazione sia del contenuto indicizzato che delle query degli utenti per unificare le varianti dei caratteri.
  2. Applicazione di regole di normalizzazione simili a quelle utilizzate nelle librerie NLP arabe o nei motori di ricerca (ad esempio, Farasa, Hazm o mapper personalizzati basati su espressioni regolari).
  3. Opzionalmente, supporto per la corrispondenza approssimativa o la distanza di Levenshtein per corrispondenze quasi esatte.

Ecco un esempio semplificato di una funzione di normalizzazione (stile Java):

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

:folded_hands: Richiesta

Si potrebbe considerare l’inclusione di questa normalizzazione nel motore di ricerca principale o come plugin? Migliorerebbe significativamente l’usabilità per le comunità arabe e persiane che utilizzano Discourse.

Se esiste una soluzione alternativa o un plugin che affronta questo problema, apprezzeremmo qualsiasi indicazione.

Grazie per il vostro tempo e per aver costruito una piattaforma così potente.

Cordiali saluti

1 Mi Piace

L’impostazione del sito search_ignore_accents ha qualche effetto su questo problema?

1 Mi Piace

Grazie mille per essere intervenuto e aver contribuito alla discussione

Per rispondere alla tua domanda: sì, l’impostazione search_ignore_accents è abilitata sul nostro forum.
Sfortunatamente, non risolve il problema che stiamo affrontando. I risultati della ricerca non corrispondono ancora a caratteri arabi e persiani ortograficamente equivalenti, quindi il problema persiste nonostante tale impostazione.

1 Mi Piace

Penso che questa sia una richiesta ragionevole poiché migliorerebbe notevolmente l’esperienza di ricerca per i siti in arabo e persiano. Ci piacerebbe rivedere una PR che implementi questa funzionalità, quindi le metterò un #pr-welcome.

Per chiunque decida di lavorare su questa funzionalità: tutta la logica di normalizzazione dovrebbe essere protetta da un’impostazione del sito che la abiliti per impostazione predefinita per i siti in arabo e persiano (vedere locale_default in site_settings.yml) e tutte le altre localizzazioni dovrebbero avere questa impostazione disattivata per impostazione predefinita. Core ha già una logica di normalizzazione simile per i caratteri accentati (vedere lib/search.rb), quindi sarebbe un utile riferimento durante l’implementazione di questa funzionalità.

4 Mi Piace

Grazie mille, Osama! Sono davvero felice di vedere che questo suggerimento è stato ben accolto.

2 Mi Piace

Per questa parte del problema, stiamo parlando di una normalizzazione Unicode standard NFKC (per sceglierne una)?

(Non sono nemmeno sicuro di cosa facciamo… Presumo che normalizziamo il testo dei post nella pipeline di elaborazione?)

1 Mi Piace

Non sono un esperto tecnico, ma ho ricercato questo problema perché voglio assicurarmi che nessuna query di ricerca venga persa su un forum bilingue persiano-arabo di Discourse. Poiché Discourse utilizza PostgreSQL, la normalizzazione diventa essenziale: un utente potrebbe cercare usando caratteri persiani, mentre la stessa parola è memorizzata usando caratteri arabi, o viceversa. Senza una corretta normalizzazione, la ricerca fallirà.
Sulla base di ciò che ho appreso, l’utilizzo della normalizzazione Unicode NFKC è un buon punto di partenza: gestisce molti casi di compatibilità come legature, forme di presentazione e cifre arabe/persiane.

Tuttavia, per il testo persiano e arabo, NFKC da solo non è sufficiente. Non normalizza diverse varianti di caratteri critiche che sono visivamente e semanticamente equivalenti ma differiscono a livello binario.

Di seguito, delineo le procedure e le intuizioni che ho raggiunto attraverso la mia ricerca ed esplorazione.


:wrench: Strategia di Progettazione Generale

  1. Applicare prima la normalizzazione Unicode NFKC per gestire legature, forme di presentazione e unificazione delle cifre.
  2. Quindi applicare mappature di caratteri personalizzate in un ordine definito (ad esempio, normalizzare le varianti di Hamza prima di Ya arabo).
  3. Separare le policy di normalizzazione per l’archiviazione e la ricerca:
    • Utilizzare un profilo Conservativo per l’archiviazione canonica (preservare ZWNJ, evitare spostamenti semantici).
    • Utilizzare un profilo Permissivo per la ricerca (ignorare ZWNJ, unificare le varianti di Hamza, normalizzare le cifre).
  4. Tutte le mappature dovrebbero essere configurabili tramite una tabella di mappatura centralizzata nel database o un hash Ruby nell’applicazione.

:one: Profili di Normalizzazione

:green_circle: Conservativo (per l’archiviazione)

  • Trasformazione minima
  • Applicare NFKC
  • Normalizzare Kaf/Ya arabi in equivalenti persiani
  • Rimuovere i segni diacritici
  • Preservare ZWNJ
  • Archiviare come original_text + normalized_conservative

:blue_circle: Permissivo (per la ricerca)

  • Corrispondenza aggressiva
  • Applicare tutte le regole Conservativo
  • Rimuovere/ignorare ZWNJ
  • Normalizzare le varianti di Hamza in lettere di base
  • Convertire tutte le cifre in ASCII
  • Opzionalmente unificare Taa Marbuta → Heh
  • Utilizzato per la pre-elaborazione delle query

:two: Tabella di Mappatura Completa

Sorgente Destinazione Unicode Note
ك ک U+0643 → U+06A9 Kaf arabo → Kaf persiano
ي ی U+064A → U+06CC Ya arabo → Ya persiano
ى ی U+0649 → U+06CC Variante Ya finale
أ, إ, ٱ ا Varie → U+0627 Forme di Hamza → Alef
ؤ و U+0624 → U+0648 Hamza Waw
ئ ی U+0626 → U+06CC Hamza Ya
ء U+0621 Rimuovere o preservare (configurabile)
ة ه U+0629 → U+0647 Taa Marbuta → Heh (opzionale)
ۀ هٔ U+06C0 ↔ U+0647+U+0654 Normalizzare forma composta
ڭ گ U+06AD → U+06AF Varianti regionali
U+200C ZWNJ: preservare in Conservativo, rimuovere in Permissivo
٤, ۴ 4 U+0664, U+06F4 → ASCII Normalizzare cifre
Diacritici U+064B–U+0652 Rimuovere tutti gli harakat
ZWJ U+200D Rimuovere joiner invisibili
Spazi multipli Spazio singolo Normalizzare spaziature

:three: Snippet di Mappatura Veloce (per SQL o Ruby)

ك → ک
ي → ی
ى → ی
أ → ا
إ → ا
ؤ → و
ئ → ی
ة → ه
ۀ → هٔ
ٱ → ا
٤, ۴ → 4
ZWNJ (U+200C) → (rimosso in permissivo)
Harakat (U+064B..U+0652) → rimossi
ZWJ (U+200D) → rimosso

:four: Implementazione in PostgreSQL

  • Creare una tabella text_normalization_map
  • Utilizzare catene regexp_replace o TRANSLATE per le prestazioni
  • Opzionalmente implementare in PL/Python o PL/v8 per il supporto Unicode
  • Normalizzare sia il contenuto archiviato che le query in arrivo utilizzando la stessa logica

Strategia di Indicizzazione

  • Archiviare normalized_conservative per l’indicizzazione canonica
  • Normalizzare le query con normalize_persian_arabic(query, 'permissive')
  • Se si utilizza la ricerca permissiva, l’indice deve corrispondere allo stesso profilo
  • Opzionalmente archiviare entrambe le versioni per il confronto incrociato

:five: Esempio di Hash Ruby (per Discourse)

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

:six: Note sulle Prestazioni e Pratiche

  1. Applicare NFKC nello strato applicativo (ad esempio, Ruby unicode_normalize(:nfkc))
  2. Utilizzare indici separati per i profili conservativo vs. permissivo
  3. Evitare mappature forzate di caratteri semanticamente sensibili (ad esempio, Hamza, Taa Marbuta) a meno che non siano esplicitamente configurate
  4. Eseguire test A/B su dati reali del forum per misurare il tasso di successo e i falsi positivi
  5. Documentare ogni mappatura con motivazioni ed esempi
  6. Definire unit test sia in Ruby che in SQL per ogni mappatura

:seven: Raccomandazione Finale

  • Utilizzare Unicode NFKC come base
  • Estenderlo con uno strato di mappatura personalizzato
  • Mantenere profili duali per l’archiviazione e la ricerca
  • Implementare la normalizzazione sia nello strato dell’app che in quello del database
  • Documentare e testare ogni mappatura
  • Creare indici appropriati (GIN + to_tsvector) sulle colonne normalizzate