Skip to content

feat(ar): add Arabic normalization - #36

Draft
karamouche wants to merge 10 commits into
mainfrom
feat/arabic-normalization
Draft

karamouche wants to merge 10 commits into
mainfrom
feat/arabic-normalization

Conversation

@karamouche

@karamouche karamouche commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds Arabic (ar) normalization: it strips vowel marks (harakat) and tatweel, unifies alef / alef maqsura / ta marbuta variants, and converts Arabic-Indic digits and separators. This is driven by a new generic apply_character_replacements step. Previously Arabic fell back to default, which split vocalized words into single letters (مَرْحَبًا → م ر ح ب ا) and dropped the Arabic decimal separator (١٢٫٥ → ١٢٥).

Not covered yet: spelled-out Arabic numbers (ثلاثة → 3), and the word replacement list is still empty.

Type of change

  • New language
  • Edit existing language (fix a replacement, tweak config, …)
  • New normalization step
  • Edit existing step (bug fix, behaviour change)
  • New preset version
  • Bug fix (other)
  • Refactor / docs / CI

Checklist

New language

  • Created normalization/languages/{lang}/ with operators.py, replacements.py, __init__.py
  • Word substitutions are in replacements.py (not hardcoded in operators.py)
  • LanguageConfig is filled in with the language's data (separators, currency words, digit words, …)
  • Subclassed LanguageOperators — only override methods where the logic changes, not just the data
  • Class is decorated with @register_language and imported in normalization/languages/__init__.py
  • Unit tests added in tests/unit/languages/
  • E2e CSV added in tests/e2e/files/{preset}/{lang}.csv (e.g. tests/e2e/files/gladia-3/fr.csv)

New step

  • Unique name class attribute set (this is the key used in YAML presets)
  • Decorated with @register_step and imported in steps/text/__init__.py or steps/word/__init__.py
  • No hardcoded language values — read data from operators.config.* instead
  • If placeholder-based: protect + restore are both in steps/text/placeholders.py and pipeline/base.py's validate() is updated (N/A)
  • Unit tests added in tests/unit/steps/
  • Step name added to the relevant preset YAML — or a new preset file created if existing presets are affected
  • If the docstring changed: ran uv run scripts/generate_step_docs.py

How was this tested?

uv run pytest tests/

1093 passed. New tests: tests/unit/steps/text/apply_character_replacements_test.py, tests/unit/languages/arabic_operators_test.py, tests/e2e/files/gladia-3/ar.csv.

Adds an optional character_replacements field to LanguageConfig and a
step that applies it first in gladia-3 text_pre. The step is a no-op
for every language that leaves the field as None, so existing outputs
are unchanged.
Registers the 'ar' language with symbol and currency words, Arabic
filler words and an empty word replacement table.
Vowel marks were turned into spaces by remove_symbols, splitting
vocalized words into single letters (مَرْحَبًا -> م ر ح ب ا). They are
now deleted up front, together with the typographic tatweel.
Folds hamzated/madda/wasla alef to bare alef, alef maqsura to yeh and
ta marbuta to heh, plus Persian yeh/keheh lookalikes, so spelling
variants of the same word no longer count as WER errors.
Maps Arabic-Indic and Eastern Arabic-Indic digits to 0-9 and the
Arabic decimal separator, thousands separator and percent sign to their
ASCII counterparts. Previously ١٢٫٥ collapsed to ١٢٥.
Once alef is folded, the plural "أمم" (nations) normalizes to "امم" and was
deleted as a hesitation. "اه" is also the dialectal "yes" in Egyptian and
Levantine Arabic. Only the elongated forms (اهه, اممم, ممم) are fillers now.
…ized form

remove_diacritics already folded ؤ to و and ئ to ي through NFKD, but only as
a side effect, so symbol words such as "زائد" and "بالمئه" did not match what
the pipeline outputs and replacement keys with hamza seats could never match.
The folding is now part of character_replacements, the config words are
written in their final form, and a test guards that invariant.
Adds ArabicNumberNormalizer behind expand_written_numbers, so references and
hypotheses that write the same number as words or digits normalize alike
(خمسة وعشرون and ٢٥ both give 25).

Covers MSA cardinals up to billions with gender and case variants, two-word
teens, units-before-tens compounds chained with و, glued and separate
hundreds, ألف/آلاف/مليون/ملايين groups, digits followed by a scale word, and
common Egyptian and Levantine spellings. و is only consumed where it builds a
compound, so ثلاثة وأربعة stays "3 و4" like ٣ و٤. Standalone واحد/أحد stay
words, as English keeps "one". digit_words enables digit-by-digit readings.

Ordinals, fractions (نص, ربع) and dual nouns are out of scope.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant