Ich bin kein technischer Experte, aber ich habe mich mit diesem Thema beschäftigt, da ich sicherstellen möchte, dass keine Suchanfragen auf einem persisch-arabischen zweisprachigen Discourse-Forum übersehen werden. Da Discourse PostgreSQL verwendet, wird die Normalisierung unerlässlich: Ein Benutzer könnte mit persischen Zeichen suchen, während dasselbe Wort in arabischen Zeichen gespeichert ist – oder umgekehrt. Ohne ordnungsgemäße Normalisierung schlägt die Suche fehl.
Basierend auf dem, was ich gelernt habe, ist die Unicode NFKC-Normalisierung ein solider Ausgangspunkt – sie behandelt viele Kompatibilitätsfälle wie Ligaturen, Darstellungsformen und arabische/persische Ziffern.
NFKC allein reicht jedoch für persischen und arabischen Text nicht aus. Es normalisiert mehrere kritische Zeichenvarianten nicht, die visuell und semantisch äquivalent sind, sich aber auf binärer Ebene unterscheiden.
Unten skizziere ich die Verfahren und Erkenntnisse, zu denen ich durch meine Forschung und Erkundung gelangt bin.
Gesamtentwurfsstrategie
- Zuerst Unicode NFKC-Normalisierung anwenden, um Ligaturen, Darstellungsformen und Ziffernvereinheitlichung zu behandeln.
- Dann benutzerdefinierte Zeichenzuordnungen anwenden in einer definierten Reihenfolge (z. B. Hamza-Varianten vor arabischem Ya normalisieren).
- Separate Normalisierungsrichtlinien für Speicherung vs. Suche:
- Verwenden Sie ein konservatives Profil für die kanonische Speicherung (ZWNJ beibehalten, semantische Verschiebungen vermeiden).
- Verwenden Sie ein permissives Profil für die Suche (ZWNJ ignorieren, Hamza-Varianten vereinheitlichen, Ziffern normalisieren).
- Alle Zuordnungen sollten über eine zentralisierte Zuordnungstabelle in der Datenbank oder einen Ruby-Hash in der Anwendung konfigurierbar sein.
Normalisierungsprofile
Konservativ (für Speicherung)
- Minimale Transformation
- NFKC anwenden
- Arabisches Kaf/Ya in persische Entsprechungen normalisieren
- Diakritika entfernen
- ZWNJ beibehalten
- Speichern als
original_text+normalized_conservative
Permissiv (für Suche)
- Aggressives Matching
- Alle konservativen Regeln anwenden
- ZWNJ entfernen/ignorieren
- Hamza-Varianten in Basisbuchstaben normalisieren
- Alle Ziffern in ASCII konvertieren
- Optional Taa Marbuta → Heh vereinheitlichen
- Wird für die Vorverarbeitung von Abfragen verwendet
Umfassende Zuordnungstabelle
| Quelle | Ziel | Unicode | Hinweise |
|---|---|---|---|
ك |
ک |
U+0643 → U+06A9 | Arabisches Kaf → Persisches Kaf |
ي |
ی |
U+064A → U+06CC | Arabisches Ya → Persisches Ya |
ى |
ی |
U+0649 → U+06CC | Finale Ya-Variante |
أ, إ, ٱ |
ا |
Verschiedene → U+0627 | Hamza-Formen → Alef |
ؤ |
و |
U+0624 → U+0648 | Hamza Waw |
ئ |
ی |
U+0626 → U+06CC | Hamza Ya |
ء |
— | U+0621 | Entfernen oder beibehalten (konfigurierbar) |
ة |
ه |
U+0629 → U+0647 | Taa Marbuta → Heh (optional) |
ۀ |
هٔ |
U+06C0 ↔ U+0647+U+0654 | Zusammengesetzte Form normalisieren |
ڭ |
گ |
U+06AD → U+06AF | Regionale Varianten |
|
— | U+200C | ZWNJ: in Konservativ beibehalten, in Permissiv entfernen |
٤, ۴ |
4 |
U+0664, U+06F4 → ASCII | Ziffern normalisieren |
| Diakritika | — | U+064B–U+0652 | Alle Harakat entfernen |
| ZWJ | — | U+200D | Unsichtbare Verbinder entfernen |
| Mehrere Leerzeichen | Einzelnes Leerzeichen | — | Leerzeichen normalisieren |
Schneller Zuordnungs-Snippet (für SQL oder Ruby)
ك → ک
ي → ی
ى → ی
أ → ا
إ → ا
ؤ → و
ئ → ی
ة → ه
ۀ → هٔ
ٱ → ا
٤, ۴ → 4
ZWNJ (U+200C) → (in permissiv entfernt)
Harakat (U+064B..U+0652) → entfernt
ZWJ (U+200D) → entfernt
Implementierung in PostgreSQL
- Erstellen Sie eine Tabelle
text_normalization_map. - Verwenden Sie
regexp_replaceoderTRANSLATE-Ketten für die Leistung. - Implementieren Sie optional in PL/Python oder PL/v8 für Unicode-Unterstützung.
- Normalisieren Sie sowohl gespeicherte Inhalte als auch eingehende Abfragen mit derselben Logik.
Indexierungsstrategie
- Speichern Sie
normalized_conservativefür die kanonische Indexierung. - Normalisieren Sie Abfragen mit
normalize_persian_arabic(query, 'permissive'). - Wenn Sie permissive Suche verwenden, muss der Index demselben Profil entsprechen.
- Speichern Sie optional beide Versionen für den Quervergleich.
Ruby Hash Beispiel (für Discourse)
NORMALIZATION_MAP = {
"ك" => "ک",
"ي" => "ی",
"ى" => "ی",
"أ" => "ا",
"إ" => "ا",
"ٱ" => "ا",
"ؤ" => "و",
"ئ" => "ی",
"ة" => "ه",
"ۀ" => "هٔ",
"۴" => "4",
"٤" => "4",
"\u200C" => "", # ZWNJ
"\u200D" => "", # ZWJ
}
Leistungs- und praktische Hinweise
- Wenden Sie NFKC in der Anwendungsschicht an (z. B. Ruby
unicode_normalize(:nfkc)). - Verwenden Sie separate Indizes für konservative vs. permissive Profile.
- Vermeiden Sie erzwungene Zuordnungen von semantisch sensiblen Zeichen (z. B. Hamza, Taa Marbuta), es sei denn, sie sind explizit konfiguriert.
- Führen Sie A/B-Tests mit echten Forendaten durch, um Trefferquote und Fehlalarme zu messen.
- Dokumentieren Sie jede Zuordnung mit Begründung und Beispielen.
- Definieren Sie Unit-Tests sowohl in Ruby als auch in SQL für jede Zuordnung.
Abschließende Empfehlung
- Verwenden Sie Unicode NFKC als Basis.
- Erweitern Sie es mit einer benutzerdefinierten Zuordnungsschicht.
- Pflegen Sie duale Profile für Speicherung und Suche.
- Implementieren Sie die Normalisierung sowohl in der Anwendungs- als auch in der Datenbankebene.
- Dokumentieren und testen Sie jede Zuordnung.
- Erstellen Sie geeignete Indizes (
GIN + to_tsvector) für normalisierte Spalten.