# Normalización de búsqueda en árabe: Falta compatibilidad con variantes de Hamza, formas de Ya/Kaf y equivalencia ortográfica

**URL:** https://meta.discourse.org/t/arabic-search-normalization-missing-support-for-hamza-variants-ya-kaf-forms-and-orthographic-equivalence/384253
**Category:** Feature
**Tags:** search, pr-welcome, localization, rtl
**Created:** [29 Septiembre, 2025 08:27 UTC](https://meta.discourse.org/t/arabic-search-normalization-missing-support-for-hamza-variants-ya-kaf-forms-and-orthographic-equivalence/384253 "2025-09-29T08:27:57Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![serkhelesheyi](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/serkhelesheyi/32/497726_2.png) [@serkhelesheyi](https://meta.discourse.org/u/serkhelesheyi)
#### Post date: [29 Septiembre, 2025 08:27 UTC](https://meta.discourse.org/t/arabic-search-normalization-missing-support-for-hamza-variants-ya-kaf-forms-and-orthographic-equivalence/384253/1 "2025-09-29T08:27:57Z")

</div>

Hola equipo de Discourse,

Estamos ejecutando un foro multilingüe con un contenido significativo en árabe y persa, y hemos encontrado una limitación crítica en la funcionalidad de búsqueda relacionada con la normalización ortográfica del árabe.

## 🔍 Descripción del problema

El script árabe incluye múltiples representaciones Unicode para caracteres semánticamente idénticos. Desafortunadamente, el motor de búsqueda actual de Discourse parece tratar estas variantes como distintas, lo que lleva a resultados de búsqueda incompletos o engañosos.

### Ejemplos:

- Buscar `إطلاق مقامي` solo devuelve coincidencias exactas, mientras que las publicaciones que contienen `اطلاق مقامي`, `أطلاق مقامي` o `إطلاق‌مقامي` se excluyen.
- De manera similar, buscar `ي` (U+064A) no coincide con `ی` (U+06CC), y `ك` (U+0643) no coincide con `ک` (U+06A9), a pesar de su equivalencia funcional en contextos árabes/persas.

Esto afecta no solo a las variantes de hamza (`أ`, `إ`, `ء`, `ؤ`, `ئ`) sino también a sustituciones comunes como:

| Carácter | Unicode | Normalización Sugerida |
| --- | --- | --- |
| `أ`, `إ`, `ء`, `آ` | U+0623, U+0625, U+0621, U+0622 | Normalizar a `ا` |
| `ؤ` | U+0624 | Normalizar a `و` |
| `ئ` | U+0626 | Normalizar a `ي` |
| `ى` | U+0649 | Normalizar a `ي` |
| `ة` | U+0629 | Normalizar a `ه` |
| `ي` vs `ی` | U+064A vs U+06CC | Normalizar a `ی` |
| `ك` vs `ک` | U+0643 vs U+06A9 | Normalizar a `ک` |

Este problema se agrava cuando los usuarios omiten los diacríticos o utilizan diferentes diseños de teclado, lo que resulta en un comportamiento de búsqueda fragmentado.

* * *

## ⚙ Solución Propuesta

Recomendamos implementar una capa de normalización consciente de Unicode durante la indexación y el análisis de consultas. Esto se puede lograr mediante:

1. **Preprocesamiento del contenido indexado y las consultas de usuario** para unificar las variantes de caracteres.
2. **Aplicación de reglas de normalización** similares a las utilizadas en bibliotecas de PNL árabes o motores de búsqueda (por ejemplo, Farasa, Hazm o mapeadores personalizados basados en expresiones regulares).
3. Opcionalmente, **soporte de coincidencia difusa** o distancia de Levenshtein para coincidencias casi exactas.

Aquí hay un ejemplo simplificado de una función de normalización (estilo Java):

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

```

* * *

## 🙏 Solicitud

¿Se podría considerar la inclusión de esta normalización en el motor de búsqueda principal o como un plugin? Mejoraría significativamente la usabilidad para las comunidades de habla árabe y persa que utilizan Discourse.

Si existe una solución alternativa o un plugin que aborde esto, agradeceríamos cualquier orientación.

Gracias por su tiempo y por construir una plataforma tan potente.

Saludos cordiales

---

<div class="post-metadata">

### Author: ![Moin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moin/32/554653_2.png) [@Moin](https://meta.discourse.org/u/Moin)
#### Post date: [29 Septiembre, 2025 08:30 UTC](https://meta.discourse.org/t/arabic-search-normalization-missing-support-for-hamza-variants-ya-kaf-forms-and-orthographic-equivalence/384253/2 "2025-09-29T08:30:15Z")

</div>

> [@serkhelesheyi](#):
>
> De manera similar, la búsqueda de `ي` (U+064A) no coincide con `ی` (U+06CC), y `ك` (U+0643) no coincide con `ک` (U+06A9), a pesar de su equivalencia funcional en contextos árabe/persas.

¿Tiene la configuración del sitio `search_ignore_accents` algún efecto en este problema?

> [@Moin](#):
>
> ### search\_ignore\_accents
> 
> **Descripción** : Las marcas diacríticas en las consultas de búsqueda se ignoran si esta configuración está habilitada, lo que permite a los usuarios encontrar resultados incluso si no introducen los acentos correctos.  
> **Predeterminado** : `False`

---

<div class="post-metadata">

### Author: ![serkhelesheyi](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/serkhelesheyi/32/497726_2.png) [@serkhelesheyi](https://meta.discourse.org/u/serkhelesheyi)
#### Post date: [29 Septiembre, 2025 08:42 UTC](https://meta.discourse.org/t/arabic-search-normalization-missing-support-for-hamza-variants-ya-kaf-forms-and-orthographic-equivalence/384253/3 "2025-09-29T08:42:56Z")

</div>

Muchas gracias por participar y contribuir a la discusión.

Para responder a tu pregunta: sí, la configuración `search_ignore_accents` está habilitada en nuestro foro.  
Desafortunadamente, no resuelve el problema que estamos enfrentando. Los resultados de búsqueda aún no coinciden con caracteres árabes y persas ortográficamente equivalentes, por lo que el problema persiste a pesar de esa configuración.

---

<div class="post-metadata">

### Author: ![Osama](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/osama/32/98013_2.png) [@Osama](https://meta.discourse.org/u/Osama)
#### Post date: [1 Octubre, 2025 22:25 UTC](https://meta.discourse.org/t/arabic-search-normalization-missing-support-for-hamza-variants-ya-kaf-forms-and-orthographic-equivalence/384253/7 "2025-10-01T22:25:30Z")

</div>

Creo que esta es una solicitud razonable, ya que mejoraría enormemente la experiencia de búsqueda para los sitios en árabe y persa. Nos encantaría revisar una PR que implemente esta función, así que le pondré un #pr-welcome.

Para cualquiera que decida trabajar en esta función: toda la lógica de normalización debe estar controlada por una configuración del sitio que la habilite por defecto para los sitios en árabe y persa (ver `locale_default` en `site_settings.yml`) y todas las demás locales deben tener esta configuración desactivada por defecto. Core ya tiene una lógica de normalización similar para caracteres acentuados (ver [`lib/search.rb`](https://github.com/discourse/discourse/blob/5fee59b2203b3630a0a1f58bc1a52016b9ee6d4a/lib/search.rb#L74-L84)), por lo que sería una referencia útil al implementar esta función.

---

<div class="post-metadata">

### Author: ![serkhelesheyi](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/serkhelesheyi/32/497726_2.png) [@serkhelesheyi](https://meta.discourse.org/u/serkhelesheyi)
#### Post date: [1 Octubre, 2025 22:44 UTC](https://meta.discourse.org/t/arabic-search-normalization-missing-support-for-hamza-variants-ya-kaf-forms-and-orthographic-equivalence/384253/8 "2025-10-01T22:44:44Z")

</div>

¡Muchas gracias, Osama! Me alegra mucho ver que esta sugerencia fue bien recibida.

---

<div class="post-metadata">

### Author: ![supermathie](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/supermathie/32/507518_2.png) [@supermathie](https://meta.discourse.org/u/supermathie)
#### Post date: [2 Octubre, 2025 01:22 UTC](https://meta.discourse.org/t/arabic-search-normalization-missing-support-for-hamza-variants-ya-kaf-forms-and-orthographic-equivalence/384253/9 "2025-10-02T01:22:19Z")

</div>

> [@serkhelesheyi](#):
>
> **Preprocesamiento tanto del contenido indexado como de las consultas de usuario** para unificar las variantes de caracteres.

Para _esta parte_ del problema, ¿estamos hablando de una normalización del estándar Unicode NFKC (por poner un ejemplo)?

(Ni siquiera estoy seguro de lo que hacemos… ¿Supongo que normalizamos el texto de las publicaciones en el proceso de cocción?)

---

<div class="post-metadata">

### Author: ![serkhelesheyi](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/serkhelesheyi/32/497726_2.png) [@serkhelesheyi](https://meta.discourse.org/u/serkhelesheyi)
#### Post date: [2 Octubre, 2025 07:31 UTC](https://meta.discourse.org/t/arabic-search-normalization-missing-support-for-hamza-variants-ya-kaf-forms-and-orthographic-equivalence/384253/10 "2025-10-02T07:31:45Z")

</div>

No soy un experto técnico, pero he estado investigando este problema porque quiero asegurarme de que no se pierdan consultas de búsqueda en un foro bilingüe persa-árabe de Discourse. Dado que Discourse utiliza **PostgreSQL** , la normalización se vuelve esencial: un usuario podría buscar usando caracteres persas, mientras que la misma palabra se almacena usando caracteres árabes, o viceversa. Sin una normalización adecuada, la búsqueda fallará.  
Según lo que he aprendido, usar la normalización NFKC de Unicode es un buen punto de partida: maneja muchos casos de compatibilidad como ligaduras, formas de presentación y dígitos árabes/persas.

Sin embargo, para el texto persa y árabe, **NFKC por sí solo no es suficiente**. No normaliza varias variantes de caracteres críticas que son visual y semánticamente equivalentes pero difieren a nivel binario.

**A continuación, describo los procedimientos y las ideas a las que he llegado a través de mi investigación y exploración.**

* * *

## 🔧 Estrategia General de Diseño

1. **Aplicar primero la normalización NFKC de Unicode** para manejar ligaduras, formas de presentación y unificación de dígitos.
2. **Luego aplicar mapeos de caracteres personalizados** en un orden definido (por ejemplo, normalizar variantes de Hamza antes que Ya árabe).
3. **Separar las políticas de normalización para almacenamiento vs. búsqueda** :
  - Usar un perfil **Conservador** para el almacenamiento canónico (preservar ZWNJ, evitar cambios semánticos).
  - Usar un perfil **Permisivo** para la búsqueda (ignorar ZWNJ, unificar variantes de Hamza, normalizar dígitos).

4. Todos los mapeos deben ser **configurables** a través de una tabla de mapeo centralizada en la base de datos o un hash de Ruby en la aplicación.

* * *

## 1️⃣ Perfiles de Normalización

### 🟢 Conservador (para almacenamiento)

- Transformación mínima
- Aplicar NFKC
- Normalizar Kaf/Ya árabe a equivalentes persas
- Eliminar diacríticos
- Preservar ZWNJ
- Almacenar como `texto_original` + `normalizado_conservador`

### 🔵 Permisivo (para búsqueda)

- Coincidencia agresiva
- Aplicar todas las reglas Conservadoras
- Eliminar/ignorar ZWNJ
- Normalizar variantes de Hamza a letras base
- Convertir todos los dígitos a ASCII
- Opcionalmente unificar Taa Marbuta → Heh
- Usado para preprocesamiento de consultas

* * *

## 2️⃣ Tabla de Mapeo Integral

| Fuente | Destino | Unicode | Notas |
| --- | --- | --- | --- |
| `ك` | `ک` | U+0643 → U+06A9 | Kaf árabe → Kaf persa |
| `ي` | `ی` | U+064A → U+06CC | Ya árabe → Ya persa |
| `ى` | `ی` | U+0649 → U+06CC | Variante final de Ya |
| `أ`, `إ`, `ٱ` | `ا` | Varios → U+0627 | Formas de Hamza → Alef |
| `ؤ` | `و` | U+0624 → U+0648 | Hamza Waw |
| `ئ` | `ی` | U+0626 → U+06CC | Hamza Ya |
| `ء` | — | U+0621 | Eliminar o preservar (configurable) |
| `ة` | `ه` | U+0629 → U+0647 | Taa Marbuta → Heh (opcional) |
| `ۀ` | `هٔ` | U+06C0 ↔ U+0647+U+0654 | Normalizar forma compuesta |
| `ڭ` | `گ` | U+06AD → U+06AF | Variantes regionales |
| `‌` | — | U+200C | ZWNJ: preservar en Conservador, eliminar en Permisivo |
| `٤`, `۴` | `4` | U+0664, U+06F4 → ASCII | Normalizar dígitos |
| Diacríticos | — | U+064B–U+0652 | Eliminar todos los harakat |
| ZWJ | — | U+200D | Eliminar uniones invisibles |
| Espacios múltiples | Espacio simple | — | Normalizar espaciado |

* * *

## 3️⃣ Fragmento de Mapeo Rápido (para SQL o Ruby)

```plaintext
ك → ک
ي → ی
ى → ی
أ → ا
إ → ا
ؤ → و
ئ → ی
ة → ه
ۀ → هٔ
ٱ → ا
٤, ۴ → 4
ZWNJ (U+200C) → (eliminado en permisivo)
Harakat (U+064B..U+0652) → eliminado
ZWJ (U+200D) → eliminado

```

* * *

## 4️⃣ Implementación en PostgreSQL

- Crear una tabla `text_normalization_map`
- Usar `regexp_replace` o cadenas `TRANSLATE` para rendimiento
- Opcionalmente implementar en PL/Python o PL/v8 para soporte Unicode
- Normalizar tanto el contenido almacenado como las consultas entrantes utilizando la misma lógica

### Estrategia de Indexación

- Almacenar `normalized_conservative` para indexación canónica
- Normalizar consultas con `normalize_persian_arabic(query, 'permissive')`
- Si se usa búsqueda permisiva, el índice debe coincidir con el mismo perfil
- Opcionalmente almacenar ambas versiones para comparación cruzada

* * *

## 5️⃣ Ejemplo de Hash de Ruby (para Discourse)

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

```

* * *

## 6️⃣ Notas de Rendimiento y Prácticas

1. Aplicar **NFKC en la capa de aplicación** (por ejemplo, `unicode_normalize(:nfkc)` de Ruby)
2. Usar **índices separados** para perfiles conservadores vs. permisivos
3. Evitar mapeos forzados de caracteres semánticamente sensibles (por ejemplo, Hamza, Taa Marbuta) a menos que se configuren explícitamente
4. Ejecutar **pruebas A/B** con datos reales del foro para medir la tasa de aciertos y los falsos positivos
5. Documentar cada mapeo con justificación y ejemplos
6. Definir **pruebas unitarias** tanto en Ruby como en SQL para cada mapeo

* * *

## 7️⃣ Recomendación Final

- Usar **Unicode NFKC** como base
- Extenderlo con una **capa de mapeo personalizada**
- Mantener **perfiles duales** para almacenamiento y búsqueda
- Implementar la normalización tanto en la capa de la aplicación como en la de la base de datos
- Documentar y probar cada mapeo
- Construir índices apropiados (`GIN + to_tsvector`) en columnas normalizadas
