SPEC
RUWAT-FR-TRANSLIT-V1
Sur cette page
Norme éditoriale et technique canonique pour les noms français/latins des
narrateurs RUWAT. Publiée dans public.transliteration_standards
(code RUWAT-FR-TRANSLIT-V1, version 1.0.0) par la migration
supabase/migrations/20260824180000_add_transliteration_standard.sql.
0. Finalité#
texte français approximatif (Abdallah ibn Omar, Abdullah ibn Umar...)
-> retrouver عبد الله بن عمر
-> toujours AFFICHER une seule forme canonique : ʿAbd Allāh ibn ʿUmar
AFFICHAGE CANONIQUE STRICT ≠ RECHERCHE TOLÉRANTE. Les variantes
populaires (transliteration_aliases) servent uniquement au retrieval ;
elles ne déterminent jamais la forme éditoriale publiée
(transliteration_lexicon.canonical_latin).
Translittération ≠ identité. Une translittération est du texte, jamais
une preuve d'identité de personne. Rien dans ce référentiel ne crée, ne
fusionne ni ne valide de narrator_crosswalk/décision de réconciliation.
external_narrator ≠ narrator RUWAT.
1. Alphabet et caractères spéciaux#
| Arabe | Latin | Note |
|---|---|---|
| ع | ʿ | ayn — ne jamais confondre avec l'apostrophe ASCII ' |
| ء | ʾ | hamza, seulement quand représenté (voir § Hamza) |
| ح | ḥ | |
| ص | ṣ | |
| ض | ḍ | |
| ط | ṭ | |
| ظ | ẓ | |
| ا/ی longues | ā / ī / ū | voyelles longues |
| خ | kh | |
| غ | gh | |
| ش | sh | |
| ث | th | |
| ذ | dh | |
| ق | q | |
| ج | j |
Les caractères ʿ ʾ ḥ ṣ ḍ ṭ ẓ ā ī ū appartiennent à la forme canonique
d'affichage. Ils ne sont jamais requis pour retrouver un narrateur en
recherche (voir § Normalisation de recherche).
2. Hamza#
Ne pas ajouter mécaniquement ʾ devant chaque alif portant hamza :
- أحمد → Aḥmad (pas de ʾ)
- إبراهيم → Ibrāhīm (pas de ʾ)
- أنس → Anas (pas de ʾ)
- mais عائشة → ʿĀʾisha (hamza médiane conservée)
Règle : ʾ n'est représenté que lorsqu'il est médian ou final et
phonétiquement audible dans la forme savante attendue ; jamais en position
initiale d'un ism/kunya courant.
3. Article al-#
Convention unique, sans assimilation solaire dans la forme canonique :
- الزهري → al-Zuhrī (jamais az-Zuhrī)
- الشيباني → al-Shaybānī (jamais ash-Shaybānī)
L'assimilation solaire peut exister comme alias de recherche
(transliteration_aliases), jamais comme forme publiée.
4. Ibn / Bint#
- بن / ابن → ibn
- بنت → bint
- عبد الله بن عمر → ʿAbd Allāh ibn ʿUmar
- En tête de nom (appellation indépendante) : ابن شهاب → Ibn Shihāb (capitale, casse éditoriale — voir § Composition).
5. Kunya#
- أبو → Abū
- أم → Umm
- Les formes grammaticales أبو/أبي/أبا sont normalisées vers Abū en
forme canonique française, mais la forme arabe SOURCE (
arabic_form) est toujours conservée telle qu'attestée — jamais réécrite.
6. Noms composés — longest match first#
Le lexique comprend des expressions, pas seulement des mots isolés :
- عبد الله → ʿAbd Allāh (pas عبد + الله séparément)
- عبد الرحمن → ʿAbd al-Raḥmān
- أبو هريرة → Abū Hurayra
La composition (composeCanonicalLatin) tente d'abord les fenêtres de mots les plus longues (jusqu'à 4 tokens) avant de retomber sur un mot seul. Exemple entièrement travaillé (golden test) :
عبد الله بن عمر بن الخطاب العدوي القرشي
-> [عبد الله] + [بن] + [عمر] + [بن] + [الخطاب] + [العدوي] + [القرشي]
-> ʿAbd Allāh ibn ʿUmar ibn al-Khaṭṭāb al-ʿAdawī al-Qurashī
Simplification assumée et documentée : la casse position-dépendante
(ibn/al- minuscule en milieu de nom, Ibn/Al- capitale en tête) n'est
modélisée que par une capitalisation de la première lettre du résultat
composé. Un futur raffinement pourrait modéliser la casse par position dans
transliteration_lexicon (dette documentée, non bloquante — aucun cas du
dry-run initial n'a produit de résultat structurellement invalide de ce
fait : proposed_structural_valid = false pour 0/8862 narrateurs).
7. Types du lexique (transliteration_lexicon.entry_type)#
PERSONAL_NAME, COMPOUND_NAME, KUNYA, NASAB_PARTICLE, NISBA,
LAQAB, TRIBAL_NAME, TOPONYM, PARTICLE, FULL_NAME_OVERRIDE,
OTHER. Abū, ibn, al-Baṣrī, Muḥammad ne sont jamais traités
comme des objets linguistiques équivalents — chacun a son propre type.
8. Statuts de revue (review_status)#
AUTO_PROPOSED → UNDER_REVIEW → APPROVED / OVERRIDDEN / REJECTED.
Seuls APPROVED et OVERRIDDEN font autorité canonique publiée
(vue public.public_transliteration_lexicon). Transition exclusivement via
set_transliteration_lexicon_review_status() (SECURITY DEFINER, admin
uniquement).
9. Normalisation nominale (parité SQL / TS / Node)#
Deux implémentations indépendantes doivent produire le même résultat pour un même texte d'entrée :
- SQL :
public.normalize_ar_query()(supabase/entity_resolution_v2.sql) - TypeScript/Node :
normalizeNominalArabic()(src/domain/narrator-identity/nominalTransliteration.ts), importée telle quelle par les scripts Node (scripts/lib/contextual-resolver-core.mjs,scripts/audit/contextual-resolver-readonly.mjs) — une seule source TS/Node, pas une troisième réimplémentation.
Parité vérifiée par 21 fixtures partagées
(scripts/lib/transliteration-golden-fixtures.mjs), testées des deux côtés :
npm run test:transliteration-golden (TS/Node) et
npm run test:transliteration-golden:sql (SQL). 21/21 identiques.
Dette découverte et documentée pendant cette parité (non corrigée dans
cette étape — porte sur normalize_ar_query() en production, hors périmètre
"harmoniser uniquement la normalisation nominale") :
- Le motif honorifique
رضي الله عنه(ما|م)?ne couvre pas le suffixe fémininعنها("elle") :normalize("عائشة رضي الله عنها")laisse un"ا"résiduel ("عايشه ا"au lieu de"عايشه"). - La virgule arabe
،(U+060C) n'est pas dans la classe de ponctuation remplacée par espace (seule la ponctuation latine + guillemets l'est).
Ces deux gaps existent identiquement côté SQL et TS (vérifié en direct sur
la base liée) — ce n'est donc pas une divergence de parité, mais une
limite connue de la règle elle-même, à traiter dans un futur correctif SQL
dédié (avec ses propres tests de non-régression, car normalize_ar_query
est utilisée en production par le retrieval narrateurs).
Portée volontairement limitée : cette étape harmonise la normalisation nominale (noms propres). La normalisation générale du texte de hadith (matn) reste un chantier séparé, futur (§ 12).
10. Validation structurelle#
validateNameStructure() (TS, src/domain/narrator-identity/nominalTransliteration.ts) et
public.validate_name_structure() (SQL, même migration) — parité vérifiée
sur 18 fixtures PASS/FAIL (18/18 identiques).
Codes d'erreur détectés : UNBALANCED_PARENTHESES/BRACKETS/BRACES,
UNBALANCED_QUOTES, EMPTY_PARENTHESES, CONTROL_CHARACTER,
UNICODE_REPLACEMENT_CHAR, HTML_FRAGMENT, MARKDOWN_FRAGMENT,
LITERAL_UNDEFINED, LITERAL_NULL, LITERAL_OBJECT_OBJECT,
REPEATED_PUNCTUATION, MULTIPLE_SPACES, TRAILING_DASH/SLASH/APOSTROPHE,
INCOMPLETE_TRAILING_COMPONENT, INCOMPLETE_LEADING_COMPONENT.
CRITICAL INVARIANT : aucun nom canonique généré ne peut atteindre le
statut FULLY_LEXICON_RESOLVED (candidat publiable) s'il échoue la
validation structurelle — il est automatiquement rétrogradé à
STRUCTURAL_ERROR (voir scripts/validation/transliteration-dry-run.mjs::auditNarrator).
Sur le dry-run initial (8862 narrateurs), 0 proposition générée n'a
échoué la validation structurelle.
11. Architecture retenue#
texte arabe (narrators.full_name_ar)
-> normalizeNominalArabic() [TS, src/domain/narrator-identity/nominalTransliteration.ts]
-> composeCanonicalLatin() [TS, longest-match-first sur le lexique]
-> validateNameStructure() [TS, sur la proposition ET sur la forme actuelle]
-> scripts/validation/transliteration-dry-run.mjs
-> start_transliteration_dry_run() + append_transliteration_dry_run_proposals()
[SQL, SECURITY DEFINER — seule porte d'écriture]
-> transliteration_dry_run_runs / transliteration_narrator_proposals
Choix assumé : la génération du dry-run (composition + validation) tourne
côté Node, réutilisant directement les mêmes fonctions TS que celles
testées par les golden tests — pas une réimplémentation PL/pgSQL parallèle.
Le lexique et son exposition publique restent en SQL (RLS, vue
public_transliteration_lexicon), cohérent avec le reste de l'architecture
RUWAT où le retrieval vit en SQL. validate_name_structure() existe aussi
en SQL (parité vérifiée) pour une future validation live côté UI sans
aller-retour Node — mais la génération de masse reste Node pour éviter une
troisième implémentation de composeCanonicalLatin à maintenir en PL/pgSQL.
Écriture par lots de 500 narrateurs par appel RPC (append_transliteration_dry_run_proposals)
: un appel unique avec les 8862 lignes dépasse la limite de taille de
requête du canal supabase db query --linked (HTTP 413, constaté en
conditions réelles) — voir 20260824181000_batch_transliteration_dry_run_write.sql.
12. Hors périmètre de cette étape#
- Construction du moteur de recherche (
search_hadiths(), tsvector surhadiths.text_ar/fr, embeddings, retrieval sémantique français→arabe générique). - Bascule globale des noms français publiés (
narrators.full_name_fr) vers les propositions du dry-run — reste un dry-run tant qu'une validation humaine explicite n'a pas eu lieu. - Fusion des trois modèles de décomposition d'identité qui se chevauchent
(
narratorscolonnes brutes /narrator_name_forms/narrator_identities- tables satellites) — dette documentée dans le rapport d'audit précédent,
non retouchée ici :
transliteration_lexiconest un concept différent (lexique linguistique réutilisable, pas une représentation de personne).
- tables satellites) — dette documentée dans le rapport d'audit précédent,
non retouchée ici :
13. Versioning#
Toute génération de canon connaît son standard d'origine
(transliteration_lexicon.standard_id → transliteration_standards.code).
Une future révision éditoriale majeure devient RUWAT-FR-TRANSLIT-V2, une
nouvelle ligne dans transliteration_standards, sans effacer l'historique
de V1 (aucune suppression, status passe à DEPRECATED).