OVOS Number Parser

July 31, 2026 · View on GitHub

Convert numbers between digits and spoken words, in 40 languages, with one small dependency-light library.

   "twenty twenty three"  ⇄  2023  ⇄  "two thousand, twenty three"

ovos-number-parser goes both ways:

  • words to digits: turn spoken-form text into numbers (extract_number, numbers_to_digits). Use it for ASR post-processing / inverse text normalization and numeric entity extraction.
  • digits to words: turn numbers into natural spoken text (pronounce_number, pronounce_ordinal, pronounce_fraction). Use it for TTS normalization before synthesis.

It ships as part of the OpenVoiceOS voice stack. It is plain Python with no assistant dependencies. Every example below runs after a single pip install, with no voice assistant involved.

Install

pip install ovos-number-parser
# or, with uv:
uv pip install ovos-number-parser

30-second quickstart

from ovos_number_parser import (pronounce_number, pronounce_ordinal,
                                 extract_number, numbers_to_digits,
                                 is_fractional, is_ordinal)

# digits -> words
pronounce_number(3.5, "en")               # 'three point five'
pronounce_number(2023, "en")              # 'two thousand, twenty three'
pronounce_ordinal(21, "en")               # 'twenty-first'

# words -> digits
extract_number("three point five liters", "en")          # 3.5
extract_number("dos mil veintitrés", "es")               # 2023
numbers_to_digits("set a timer for five minutes", "en")  # 'set a timer for 5 minutes'

# classification helpers
is_fractional("half", "en")               # 0.5
is_ordinal("third", "en")                 # 3

# no number in the text -> False
extract_number("hello world", "en")       # False

Pass a different language code and the same calls work. See the matrix for the 40 supported languages.

What it's good for (beyond voice assistants)

The library is a standalone text/number utility. Four common uses, each with a runnable script in examples/:

1. ASR post-processing / inverse text normalization

Speech-to-text emits numbers as words. Most downstream systems want digits.

from ovos_number_parser import numbers_to_digits, extract_number

numbers_to_digits("i need twenty three of them", "en")   # 'i need 23 of them'
extract_number("three point five liters", "en")          # 3.5

examples/asr_itn.py

2. TTS normalization

Expand numbers into words before synthesis so the voice reads them naturally ("one thousand..." instead of "one-two-three-four dot five six").

from ovos_number_parser import pronounce_number

pronounce_number(1234.56, "en", places=2)
# 'one thousand, two hundred and thirty four point five six'
pronounce_number(-3.5, "en")               # 'minus three point five'

examples/tts_normalization.py

3. Numeric entity extraction (NER)

Pull cardinal, ordinal and fractional mentions out of free text without an ML model.

from ovos_number_parser import extract_number, is_fractional, is_ordinal

extract_number("shipped in three boxes", "en")   # 3
is_ordinal("second", "en")                        # 2
is_fractional("quarter", "en")                    # 0.25

examples/ner.py

4. Multilingual round-trips

The same two functions cover 40 languages, with dialect prefixes (pt-BR, pt-PTpt).

examples/multilingual.py

In an OVOS skill vs. standalone

The API is the same in both cases. Only where you get the language code differs.

# Standalone Python: you pass the language yourself
from ovos_number_parser import extract_number
n = extract_number(text, "en")

# Inside an OVOS skill: the framework already knows the session language
class MySkill(OVOSSkill):
    def handle_intent(self, message):
        n = extract_number(message.data["utterance"], self.lang)

Supported languages

40 languages. Every language supports every public function: where a language has no hand-written implementation for a given function, a documented generic fallback fills in (unicode-rbnf for pronunciation, reverse-lookup for the rest: see Full function parity), so the call always returns a sensible result rather than raising.

  • Y: dedicated hand-written implementation
  • ·: supported via the documented generic fallback
CodeLanguagepronounce_numberpronounce_ordinalextract_numbernumbers_to_digitsis_fractionalis_ordinal
arArabicYYY·YY
anAragoneseYYYYYY
astAsturianYYYYYY
azAzerbaijaniY·YYY·
euBasqueYYY·YY
bgBulgarianY·YYY·
caCatalanY·YYY·
hrCroatianYYYYY·
csCzechYYYYY·
daDanishYYYYYY
nlDutchYYYYY·
enEnglishY·YYYY
etEstonianYYY·YY
fiFinnishYYY·YY
frFrenchY·Y·Y·
glGalicianYYYYYY
deGermanYYYYYY
elGreekYYY·YY
heHebrewYYY·YY
huHungarianYYY·YY
idIndonesianYYYYY·
itItalianY·Y·Y·
kabKabyleYYY·YY
msMalayYYYYY·
mwlMirandeseYYYYYY
nbNorwegian BokmålYYYYYY
nnNorwegian NynorskYYYYYY
ocOccitanYYYYYY
faPersianYYY·Y·
plPolishYYYYY·
ptPortugueseYYYYYY
roRomanianYYYYYY
ruRussianY·YYY·
skSlovakYYYYY·
slSlovenianY·Y·YY
esSpanishY·YYY·
svSwedishYYY·Y·
trTurkishYYYYY·
ukUkrainianYYYYY·
fyWest FrisianYYYYY·

Language-specific behaviour (compound-word splitting, vigesimal counting, grammatical gender, declensions, script handling) is documented in docs/languages.md.

Grammatical gender

Languages whose numerals inflect accept a gender argument:

from ovos_number_parser import pronounce_number
from ovos_number_parser.util import GrammaticalGender

pronounce_number(2, "pt", gender=GrammaticalGender.FEMININE)  # 'duas'

Documentation

License

Apache License 2.0.