Skip to content

Romanian Normalization - #34

Open
eek wants to merge 4 commits into
gladiaio:mainfrom
eek:feat/romanian-normalization
Open

eek wants to merge 4 commits into
gladiaio:mainfrom
eek:feat/romanian-normalization

Conversation

@eek

@eek eek commented Oct 15, 2026

Copy link
Copy Markdown

What does this PR do?

Adds Romanian (ro) normalization: spelled-out numbers to digits, Romanian fillers, symbols,
currencies, clock times and decimal conventions, so Romanian STT output and references compare fairly.

Type of change

  • New language

Checklist

New language

  • Created normalization/languages/romanian/ 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 expand_written_numbers and get_word_replacements are overridden
  • 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/gladia-3/ro.csv

Notes

  • text2num has no Romanian, so number_normalizer.py parses cardinals: units, teens, tens,
    "și" compounds (douăzeci și cinci = 25), hundreds, mie/milion/miliard with the "de" Romanian
    uses from 20 up (douăzeci de mii = 20000), colloquial contracted forms (cinșpe = 15,
    douăj = 20), and ordinals after "al"/"a" (al treilea = al 3-lea = al 3).
  • "un"/"o" count as 1 only before sută/mie/milion/miliard, so articles ("o zi", "un om") stay words.
  • Fillers are listed only where their diacritic-folded form cannot be a Romanian word: "ăă" is
    removed, but "ă" (→ "a") and "îî" (→ "ii", the pronoun "îi") are kept.
  • decimal_word is the folded "virgula", because the decimal-word steps run after
    remove_diacritics; this makes "3,5" and "trei virgulă cinci" agree.
  • Legacy cedilla letters (ş/ţ), still produced by some engines, are read as ș/ț.
  • Romanian links numbers of 20+ (or ending in 00) to their noun with "de" ("douăzeci de grade",
    "o sută de lei"); written forms ("20°", "20 grade") have none, so that "de" is dropped after
    such numbers. It is kept after smaller numbers and in "de la" ("from").
  • Informal teens of everyday speech ("cincișpe" = 15, "unșpe" = 11) and their most reduced
    forms ("cinșpe") are recognized, as are "și" compounds written as one word ("douăzecișicinci").
  • Currency symbols before or after the amount ("1.200 €", "1.200€", "€1.200") become "1200 euro",
    matching "o mie două sute de euro". The shared pipeline drops a symbol written after the
    amount with a space, so, as in the Danish normalizer, the Romanian number normalizer
    rewrites the config's currency symbols itself.
  • Spoken clock times after "la"/"ora"/"e" ("la opt și jumătate", "la opt jumate", "opt și-un sfert",
    "opt fără un sfert", "șapte patruzeci și cinci", and "8.30" written with a dot) become H:MM.
    They are converted in normalize_numeric_time_formats because it runs before the time-colon
    protection; a dedicated hook for spoken times may be preferable in a future preset.
  • A half after an amount is its decimal ("trei kile jumate" = "trei kilograme și jumătate" =
    "3,5 kg"); kilogram/kilometre spellings map to kg/km.
  • Currency codes stay words (expand_all_caps_letter_by_letter=False): "RON" is not "lei".
  • We have used this module to score Romanian STT on FLEURS and on conversational speech.

How was this tested?

uv run pytest tests/        # 1203 passed
uv run ruff check . && uv run ruff format --check . && uv run ty check

eek added 4 commits October 8, 2026 09:48
Add Romanian (`ro`) as a self-contained language:

- RomanianNumberNormalizer: spelled-out cardinals to digits (units, teens, tens, "și"
  compounds, hundreds, mie/milion/miliard with the "de" Romanian uses from 20 up), the
  colloquial contracted forms (cinșpe = 15, douăj = 20), ordinals after "al"/"a" and digit
  ordinals (al 3-lea, a 3-a). "un"/"o" count as 1 only before sută/mie/milion/miliard, so
  articles stay words. Words match with or without diacritics, and the legacy cedilla
  letters ş/ţ are read as ș/ț.
- LanguageConfig: decimal comma with "virgula" (folded, since the decimal-word steps run
  after remove_diacritics), thousands dot, symbols and currencies, digit and number words.
- Filler words only where their diacritic-folded form cannot be a Romanian word: "ăă"
  becomes "aa" and is removed, but "ă" (→ "a") and "îî" (→ "ii", the pronoun "îi") are
  kept.
- Replacements: okay/oke/okei/okey → ok.
- Unit tests for the number normalizer and operators, a gladia-3 e2e CSV, README row.
- Drop the "de" that links a number of at least 20 (or ending in 00) to its noun, so
  "20°", "20 de grade" and "douăzeci de grade" all normalize to "20 grade" (and
  "300 de lei" / "trei sute de lei" to "300 lei"). "de" after smaller numbers and in
  "de la" ("from") is kept.
- Recognize the informal teens of everyday speech ("cincișpe"/"cincispe" = 15) next to
  the standard and most reduced forms.
- Read "și" compounds written as one word ("douăzecișicinci" = 25).
- E2e cases rewritten as natural sentences (battery level, discounts, prices, temperature).
"1.200 €", "1.200€" and "€1.200" become "1200 euro", matching the spoken
"o mie două sute de euro" (the shared pipeline drops a symbol written after the amount
with a space). As in the Danish normalizer, the Romanian number normalizer rewrites the
config's currency symbols itself.
- Spoken clock times after "la"/"ora"/"e" become H:MM before the time-colon protection,
  so they match written times: "la opt și jumătate" / "la opt jumate" / "la ora 8.30" →
  8:30, "opt și(-un) sfert" → 8:15, "opt fără un sfert" and "șapte patruzeci și cinci" →
  7:45, "opt fără zece" → 7:50 (time_normalizer.py, via normalize_numeric_time_formats).
- A half after an amount is its decimal: "trei kile jumate", "trei kilograme și jumătate"
  and "3,5 kg" all become "3 virgula 5 kg"; "jumătate de ..." is left alone.
- Units: kil/kile/kilogram(e) → kg, kilometru/kilometri → km.
- Currency codes and acronyms stay words (expand_all_caps_letter_by_letter=False): "RON"
  is "ron", not "r o n", and stays distinct from "lei".
- Informal numbers: douășpe/doușpe (12, feminine), fused tens-and-five (douășcinci,
  treișcinci, patrușcinci).

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