API reference
Top-level API
indic_normalizer — a lightweight TTS pre-normalizer for the 22 scheduled Indian languages (+ English).
Public API:
from indic_normalizer import Normalizer, normalize
normalize("भारत 1947 में स्वतंत्र हुआ", lang="hi")
See NormalizerConfig for options.
- class indic_normalizer.Normalizer(lang: str = 'en', config: NormalizerConfig | None = None, **kwargs)[source]
Bases:
objectLightweight TTS pre-normalizer for Indic languages + English.
- normalize(text: str) str[source]
Normalize
textto spoken form.Stages: control/unicode cleanup -> LaTeX spans -> escape stripping -> tag protection -> parenthetical removal -> semiotic-class handlers -> tag restore -> whitespace/punctuation tidy (NFC output).
- indic_normalizer.normalize(text: str, lang: str = 'en', **kwargs) str[source]
One-shot convenience wrapper.
- class indic_normalizer.NormalizerConfig(lang: str = 'en', number_lang: str | None = None, force: bool = False, default_number_lang: str = 'en', strip_parentheses: bool = True, keep_square_brackets: bool = True, keep_angle_brackets: bool = True, strip_escapes: bool = True, latex: bool = True, latex_verbosity: str = 'natural', detect_years: bool = True, year_range: Tuple[int, int] = (1100, 2099), detect_positions: bool = True, detect_roman: bool = False, emit_variations: bool = False)[source]
Bases:
objectOptions controlling normalization.
- lang
The sentence’s (regional) language. Drives lexicon selection and the verbalization language for native-script digits.
- Type:
str
- number_lang
If set, force ALL numbers to be verbalized in this language, overriding script-based resolution.
force=Trueis a shorthand that sets this tolang.- Type:
str | None
- force
Shorthand: force all numbers into
lang.- Type:
bool
- default_number_lang
Language used for ASCII/Arabic (0-9) digits when not forced. Defaults to English, matching common Indian TTS usage.
- Type:
str
- strip_parentheses
Remove
(...)spans (content included).- Type:
bool
- keep_square_brackets / keep_angle_brackets
Preserve
[...]/<...>spans verbatim (SSML / prosody markers).
- strip_escapes
Decode & strip escape sequences and control chars.
- Type:
bool
- latex
Convert LaTeX math spans to spoken English (auto-detect).
- Type:
bool
- latex_verbosity
“natural” or “explicit”.
- Type:
str
- detect_years
Enable year-style reading heuristic.
- Type:
bool
- year_range
Inclusive [lo, hi] range treated as candidate years.
- Type:
Tuple[int, int]
- detect_positions
Read 3-4 digit numbers after position words (room/page/flight/gate/bus) in pairing style: “room 225” -> “room two twenty five” (English only).
- Type:
bool
- emit_variations
If True,
normalizemay annotate alt readings (currently used by the number layer for debugging/inspection).- Type:
bool
- resolve_number_lang(native: bool) str[source]
Pick the language to verbalize a number in.
forced
number_langwins;else native-script digits -> the sentence language;
else (ASCII digits) ->
default_number_lang(English).
- indic_normalizer.latex_to_speech(expr, verbosity='natural')[source]
Convert raw LaTeX (without delimiters) to spoken English.
Never raises: on failure, degrades to a stripped reading.
- indic_normalizer.normalize(text: str, lang: str = 'en', **kwargs) str[source]
One-shot convenience wrapper.
- class indic_normalizer.Normalizer(lang: str = 'en', config: NormalizerConfig | None = None, **kwargs)[source]
Bases:
objectLightweight TTS pre-normalizer for Indic languages + English.
- normalize(text: str) str[source]
Normalize
textto spoken form.Stages: control/unicode cleanup -> LaTeX spans -> escape stripping -> tag protection -> parenthetical removal -> semiotic-class handlers -> tag restore -> whitespace/punctuation tidy (NFC output).
Pipeline
The main normalization pipeline.
- class indic_normalizer.normalizer.Normalizer(lang: str = 'en', config: NormalizerConfig | None = None, **kwargs)[source]
Bases:
objectLightweight TTS pre-normalizer for Indic languages + English.
- indic_normalizer.normalizer.normalize(text: str, lang: str = 'en', **kwargs) str[source]
One-shot convenience wrapper.
Configuration for the normalizer pipeline.
- class indic_normalizer.config.NormalizerConfig(lang: str = 'en', number_lang: str | None = None, force: bool = False, default_number_lang: str = 'en', strip_parentheses: bool = True, keep_square_brackets: bool = True, keep_angle_brackets: bool = True, strip_escapes: bool = True, latex: bool = True, latex_verbosity: str = 'natural', detect_years: bool = True, year_range: Tuple[int, int] = (1100, 2099), detect_positions: bool = True, detect_roman: bool = False, emit_variations: bool = False)[source]
Bases:
objectOptions controlling normalization.
- lang
The sentence’s (regional) language. Drives lexicon selection and the verbalization language for native-script digits.
- Type:
str
- number_lang
If set, force ALL numbers to be verbalized in this language, overriding script-based resolution.
force=Trueis a shorthand that sets this tolang.- Type:
str | None
- force
Shorthand: force all numbers into
lang.- Type:
bool
- default_number_lang
Language used for ASCII/Arabic (0-9) digits when not forced. Defaults to English, matching common Indian TTS usage.
- Type:
str
- strip_parentheses
Remove
(...)spans (content included).- Type:
bool
- keep_square_brackets / keep_angle_brackets
Preserve
[...]/<...>spans verbatim (SSML / prosody markers).
- strip_escapes
Decode & strip escape sequences and control chars.
- Type:
bool
- latex
Convert LaTeX math spans to spoken English (auto-detect).
- Type:
bool
- latex_verbosity
“natural” or “explicit”.
- Type:
str
- detect_years
Enable year-style reading heuristic.
- Type:
bool
- year_range
Inclusive [lo, hi] range treated as candidate years.
- Type:
Tuple[int, int]
- detect_positions
Read 3-4 digit numbers after position words (room/page/flight/gate/bus) in pairing style: “room 225” -> “room two twenty five” (English only).
- Type:
bool
- emit_variations
If True,
normalizemay annotate alt readings (currently used by the number layer for debugging/inspection).- Type:
bool
- indic_normalizer.config.SUPPORTED_LANGS
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable’s items.
If the argument is a tuple, the return value is the same object.
Semiotic-class handlers
Ordered registry of semiotic-class handlers.
- class indic_normalizer.classes.Context(cfg: NormalizerConfig)[source]
Bases:
objectPer-run state shared by all handlers: the config plus number-language and lexicon resolution.
- indic_normalizer.classes.apply_all(text: str, ctx: Context, enable_roman: bool = False) str[source]
Run every enabled handler over
textin registry (priority) order.
Shared context and helpers for semiotic-class handlers.
Each handler is a callable (text, ctx) -> text that rewrites the spans it
recognises into spoken words. Handlers run in a fixed priority order (specific
-> general); because each handler replaces its matches with words, later
numeric handlers never re-match already-verbalized spans.
- class indic_normalizer.classes.base.Context(cfg: NormalizerConfig)[source]
Bases:
objectPer-run state shared by all handlers: the config plus number-language and lexicon resolution.
Semiotic-class handlers (regex tagger -> verbalizer), in priority order.
- indic_normalizer.classes.handlers.web_(text: str, ctx: Context) str[source]
Speak emails and URLs token-by-token (“at”, “dot”, “slash”, digits digit-wise); the
https://prefix is dropped.
- indic_normalizer.classes.handlers.abbrev_(text: str, ctx: Context) str[source]
Expand titles and abbreviations (“Dr.”, “Pvt. Ltd.”, “etc.”, “vs.”, “No. 5”, “St.” Saint/Street heuristic) and space out dotted acronyms (“U.S.A.” -> “U S A”).
- indic_normalizer.classes.handlers.alphanumeric_split(text: str, ctx: Context) str[source]
Split Latin-letter/digit boundaries (“COVID19” -> “COVID 19”) so the embedded number verbalizes while the Latin part stays verbatim.
- indic_normalizer.classes.handlers.time_(text: str, ctx: Context) str[source]
HH:MM(:SS) with optional am/pm: “10:30 am” -> “ten thirty am”.
- indic_normalizer.classes.handlers.ratio_(text: str, ctx: Context) str[source]
Colon pairs the time handler rejected: “3:2” -> “three to two”.
- indic_normalizer.classes.handlers.date_(text: str, ctx: Context) str[source]
Numeric dates (d/m/yyyy, d-m-yyyy, yyyy-mm-dd, d.m.yyyy). An invalid day/month pair first tries US month/day order; a hopeless triple reads as plain numbers instead of leaking to the fraction handler.
- indic_normalizer.classes.handlers.textdate_(text: str, ctx: Context) str[source]
English textual dates (“15 August 1947”, “Aug 15, 1947”, “5 June”): ordinal day, expanded month name, year-style year.
- indic_normalizer.classes.handlers.money_(text: str, ctx: Context) str[source]
Currency amounts: prefix symbol/code (”₹1,234.50”, “Rs 150”), scale words (”₹5 lakh”), signs, singular units (”₹1” -> “one rupee”), and suffix forms (“100₹”, “250 rs”).
- indic_normalizer.classes.handlers.percent_(text: str, ctx: Context) str[source]
“12.5%” -> “twelve point five percent” (optional leading sign).
- indic_normalizer.classes.handlers.ids_(text: str, ctx: Context) str[source]
Indian ID formats spelled letter-by-letter with digit-wise digits: PAN (ABCDE1234F), IFSC (SBIN0001234), vehicle plates (KA 01 AB 1234), and “PIN <6 digits>”.
- indic_normalizer.classes.handlers.bp_(text: str, ctx: Context) str[source]
Blood pressure (“120/80 mmHg”, or “BP 120/80”) -> “one hundred and twenty over eighty (millimeters of mercury)”.
- indic_normalizer.classes.handlers.measure_(text: str, ctx: Context) str[source]
Number + unit (“5kg”, “37.5°C”, “2.4 GHz”) -> spoken unit words. Unit matching is case-sensitive so “5G” is never five grams.
- indic_normalizer.classes.handlers.native_scale_(text: str, ctx: Context) str[source]
ASCII number + native-script scale word (“2 करोड़”) reads the number in the scale word’s language: “दो करोड़”.
- indic_normalizer.classes.handlers.range_(text: str, ctx: Context) str[source]
“N-M” ranges up to 4 digits per side (“1939-1945”, “10-15%”, “5-10 kg”) -> “N to M” (year-style sides where plausible); longer sides fall through to the phone handler.
- indic_normalizer.classes.handlers.phone_(text: str, ctx: Context) str[source]
Phone-shaped digit runs -> digit-by-digit (“+91 98765 43210”); shape-gated (see
_looks_like_phone()) so year lists and large cardinals pass through to the number handler.
- indic_normalizer.classes.handlers.dotted_(text: str, ctx: Context) str[source]
Digit runs with >= 2 dots: IPv4 reads digit-wise with “dot”; versions (“3.11.4”) read as cardinals joined by the decimal-point word.
- indic_normalizer.classes.handlers.cricket_(text: str, ctx: Context) str[source]
“287/5” near a cricket word -> “two hundred and eighty seven for five”; without the context word the fraction handler keeps it.
- indic_normalizer.classes.handlers.fraction_(text: str, ctx: Context) str[source]
“a/b” -> a named fraction (“3/4” -> “three quarters”) or “a over b” (English); other languages join with the “and” connector.
- indic_normalizer.classes.handlers.decimal_(text: str, ctx: Context) str[source]
“3.14” -> “three point one four” (fraction digits read one by one).
- indic_normalizer.classes.handlers.scientific_(text: str, ctx: Context) str[source]
“1.5e10” -> “one point five times ten to the power ten” (English-only wording; other resolved languages pass through).
- indic_normalizer.classes.handlers.code_(text: str, ctx: Context) str[source]
Alphanumeric codes with >= 2 letter/digit transitions (“AB123CD”, “6E204”) spell out: letters one by one, digits digit-wise. Tokens with a single transition (“32A”, “B12”) keep the natural reading.
- indic_normalizer.classes.handlers.decade_(text: str, ctx: Context) str[source]
“1990s” / “1980’s” / “90s” -> “nineteen nineties” / “nineties” (English idiom; non-decades like “1994s” are left alone).
- indic_normalizer.classes.handlers.ordinal_(text: str, ctx: Context) str[source]
English “21st” -> “twenty first”; native suffix ordinals (“5वाँ”) speak the sentence language regardless of digit script.
- indic_normalizer.classes.handlers.position_(text: str, ctx: Context) str[source]
3-4 digit numbers after a position word (room/page/flight/gate/bus) read in pairing style: “room 225” -> “room two twenty five”, “flight 6204” -> “flight sixty two oh four”. Gated by
detect_positions.
- indic_normalizer.classes.handlers.number_(text: str, ctx: Context) str[source]
Bare integers: year-style for plausible bare 4-digit years, digit-wise for leading zeros (“007”), else cardinal; leading +/- spoken.
- indic_normalizer.classes.handlers.symbol_(text: str, ctx: Context) str[source]
Standalone symbols between spaces (& @ + = < >) -> words.
Number verbalization
Number verbalization built on the vendored engine.
The vendored _numengine handles cardinals for all 22 scheduled languages +
English. This module wraps it with graceful fallbacks and adds the readings the
engine does not provide: decimals, digit-strings, ordinals, and year-style.
- indic_normalizer.numbers.core.cardinal(value, lang: str = 'en') str[source]
Return the primary cardinal reading of a non-negative integer.
Never raises; on engine failure falls back to a digit-by-digit reading.
- indic_normalizer.numbers.core.cardinal_variations(value, lang: str = 'en') List[str][source]
All cardinal readings the engine offers for
value(at least one).
- indic_normalizer.numbers.core.split_digits(digit_str: str, lang: str = 'en') str[source]
Read each digit separately: ‘420’ -> ‘four two zero’.
- indic_normalizer.numbers.core.decimal(number_str: str, lang: str, lex) str[source]
Read a decimal: integer part as cardinal, fractional part digit-by-digit.
- indic_normalizer.numbers.core.ordinal(value: int, lang: str, lex) str[source]
Ordinal reading: English forms for
en, else the lexicon’s rule (irregular map, then cardinal + suffix).
- indic_normalizer.numbers.core.year(value: int, lang: str, lex) str[source]
Year-style reading.
English-style pairing (
nineteen forty-seven) is used when the lexicon opts in (year_pairing); otherwise the full cardinal is used, which is the natural reading for most Indic languages.
- indic_normalizer.numbers.core.english_ordinal(value: int) str[source]
English ordinal words: 21 -> “twenty first”, 12 -> “twelfth”.
Unicode numeral handling for Indic scripts.
We rely on unicodedata for decoding any Unicode decimal digit to its
value, and keep an explicit script -> (digit range, language hint) table so we
can (a) recognise native-script digits, and (b) name the script of a run of
digits. Language resolution for verbalization only needs: “is this digit
ASCII, or native-script?” (see indic_normalizer.normalizer).
- indic_normalizer.numerals.is_native_digit(ch: str) bool[source]
True if
chis a non-ASCII Unicode decimal digit (Indic/Perso-Arabic).
- indic_normalizer.numerals.digit_value(ch: str) int | None[source]
Numeric value of a single digit char (ASCII or native), else None.
- indic_normalizer.numerals.to_ascii_digits(s: str) str[source]
Convert every native digit in
sto its ASCII equivalent.Non-digit characters are passed through unchanged.
Lexicon
Lexicon registry: build and cache one Lexicon per language.
- class indic_normalizer.lexicon.Lexicon(lang: str, decimal_point: str = 'point', negative: str = 'minus', connector_and: str = 'and', percent: str = 'percent', range_to: str = 'to', year_pairing: bool = False, year_hundred_word: str = 'hundred', year_oh_word: str = 'oh', ordinal_suffix: str = '', ordinal_irregular: Dict[int, str]=<factory>, symbols: Dict[str, str]=<factory>, currency: Dict[str, ~typing.Tuple[str, str]]=<factory>, units: Dict[str, str]=<factory>, months: Dict[int, str]=<factory>, year_trigger_words: Tuple[str, ...]=(), date_trigger_words: Tuple[str, ...]=(), review: Tuple[str, ...]=())[source]
Bases:
objectPer-language glue words the number engine cannot provide: decimal point, percent, currency units, months, ordinal rules, connectors. Fields left at their defaults fall back to English wording;
reviewnames entries still awaiting native-speaker verification.
- indic_normalizer.lexicon.get_lexicon(lang: str) Lexicon[source]
Return the cached
Lexiconforlang(built on first use).
- indic_normalizer.lexicon.list_review_flags() List[str][source]
Return
"<lang>: <field>"for every low-confidence lexicon entry.
Per-language lexicon: the glue words the number engine cannot provide.
Each language ships a Lexicon instance in lexicon/data/<lang>.py.
Entries that could not be verified by a native speaker are marked in the
review set (their keys) so they can be surfaced for follow-up.
- class indic_normalizer.lexicon.base.Lexicon(lang: str, decimal_point: str = 'point', negative: str = 'minus', connector_and: str = 'and', percent: str = 'percent', range_to: str = 'to', year_pairing: bool = False, year_hundred_word: str = 'hundred', year_oh_word: str = 'oh', ordinal_suffix: str = '', ordinal_irregular: Dict[int, str]=<factory>, symbols: Dict[str, str]=<factory>, currency: Dict[str, ~typing.Tuple[str, str]]=<factory>, units: Dict[str, str]=<factory>, months: Dict[int, str]=<factory>, year_trigger_words: Tuple[str, ...]=(), date_trigger_words: Tuple[str, ...]=(), review: Tuple[str, ...]=())[source]
Bases:
objectPer-language glue words the number engine cannot provide: decimal point, percent, currency units, months, ordinal rules, connectors. Fields left at their defaults fall back to English wording;
reviewnames entries still awaiting native-speaker verification.
Preprocessing
Artifact stripping: control chars, escape sequences, whitespace, unicode.
Split into two phases so LaTeX (which is full of backslashes) can be extracted in between:
clean_controls()— safe to run first; NFC-normalizes, drops control & zero-width characters, and collapses whitespace. Leaves backslashes intact.strip_escapes()— run AFTER LaTeX extraction; decodes literal\uXXXX/\xHHsequences and removes leftover escape artifacts (\n,\t, stray backslashes).
- indic_normalizer.preprocess.artifacts.clean_controls(text: str, collapse_newlines: bool = True) str[source]
NFC-normalize, drop control/zero-width chars, collapse whitespace.
- indic_normalizer.preprocess.artifacts.strip_escapes(text: str) str[source]
Decode/strip escape-sequence artifacts. Run after LaTeX extraction.
Bracket handling.
(...)— removed entirely (content included), honouring nesting.[...]and<...>— preserved verbatim; they are SSML / prosody markers or tags. They are masked with private-use sentinels before the rest of the pipeline runs, then restored unchanged at the very end.
- indic_normalizer.preprocess.brackets.protect_tags(text: str, square: bool = True, angle: bool = True) Tuple[str, List[str]][source]
Replace
[...]/<...>spans with sentinels; return (masked, store).
- indic_normalizer.preprocess.brackets.restore_tags(text: str, store: List[str]) str[source]
Re-insert the spans masked by
protect_tags()(its inverse).
- indic_normalizer.preprocess.brackets.strip_parentheses(text: str) str[source]
Remove balanced
(...)spans including nested content.Only balanced pairs are removed (innermost-first); an unmatched “(” or “)” is kept as a literal so it cannot swallow the rest of the utterance.
Lightweight Unicode script detection (used for diagnostics / future hooks).
LaTeX-to-speech
Pure-Python, English-only LaTeX-to-speech engine.
Public API
latex_to_speech(expr, verbosity=”natural”) -> str convert_spans(text, verbosity=”natural”) -> str LATEX_SPAN_PATTERN (compiled re.Pattern)
- indic_normalizer.latex.latex_to_speech(expr, verbosity='natural')[source]
Convert raw LaTeX (without delimiters) to spoken English.
Never raises: on failure, degrades to a stripped reading.
- indic_normalizer.latex.convert_spans(text, verbosity='natural')[source]
Replace every LaTeX span in
textwith its spoken reading.Each span becomes
" <spoken> "(space-padded). Non-LaTeX text is untouched. Never raises.
Chemistry: mhchem ce{…} rendering plus a bare-formula heuristic.
Element symbols are read letter-by-letter (uppercase letters spoken as the letter), digit counts as English cardinals, per the approved examples:
\ce{H2O} -> "H two O"
\ce{2H2 + O2 -> 2H2O} -> "two H two plus O two yields two H two O"
\ce{SO4^2-} -> "S O four two minus"