On this page
- Why digits are hard for Hindi text to speech
- Rupees, lakh and crore: amounts the way Hindi speakers say them
- Dates, times and ordinals in spoken Hindi
- OTPs, account numbers and phone numbers: one digit at a time
- Where the conversion should happen: the prompt, the engine or a text step
- Marathi and other languages written in Devanagari
- Testing Hindi number reading before callers hear it
- Sources
Why digits are hard for Hindi text to speech
A string of digits does not say how it should be spoken. "1499" is a price in one sentence, an order ID in the next and a PIN in a third. A person reading aloud picks the right form from context without thinking. A TTS system has to make the same choice, and the step that makes it is called text normalisation: turning digits, symbols and abbreviations into the words to be spoken, before the voice model sees the text.
When that step is missing, open models show the problem plainly. An IndicF5 user reported in May 2026 that "Your OTP is 4582" came out with garbled noise around the number, and the replies pointed to the same fix: a normaliser in front of the model, or numbers converted to words by hand (IndicF5 issue 31). On Indic Parler-TTS, a user could barely hear "thousand" in "These headphones cost 1000 rupees". The maintainer's answer was that the model needs a text normaliser because it "was not extensively trained for numbers" (Hugging Face discussion 9).
The same pattern turns up in other languages and models. A user fine-tuning IndicF5 for Marathi found it skipping the digits in "११,००० ते १४,००० रुपये" in the middle of a sentence (discussion 44). Kokoro users reported "$3.12" coming out as "312$"; a reply in October 2026 says the case now tokenises correctly with pinned dependencies, but the project has not confirmed a fix (kokoro issue 129).
Commercial systems tend to fail differently: they say the number, but in the wrong form. A team that built a Hindi-English voice agent for a fintech wrote that its first agent read an amount due as "one thousand four hundred ninety nine rupees", a full English chunk in the middle of Hindi, and read a reference number as one giant number instead of digit by digit. They said this alone sank their first pilot, because customers found it confusing and untrustworthy (r/AI_Agents, August 2026).
Both kinds of failure come from the same gap: nobody decided, in a place that can be tested, what kind of number each one is and how a Hindi speaker says it. The decision has two halves. Quantities, meaning money, counts, percentages, dates and times, are read as numbers in Hindi words. Identifiers, meaning anything a caller may need to write down or type, such as an OTP, PIN, account, reference or phone number, are read one digit at a time. The table gives the readings the rest of this article explains.
| Written | Say it as | Rule |
|---|---|---|
| ₹1,499 | चौदह सौ निन्यानवे रुपये | Prices under ₹10,000 are often said in hundreds |
| ₹1,49,900 or ₹149,900 | एक लाख उनचास हज़ार नौ सौ रुपये | Lakh and crore come from the value, not the commas |
| ₹2,50,000 | ढाई लाख रुपये (or दो लाख पचास हज़ार) | Fraction words: डेढ़, ढाई, सवा, साढ़े, पौने |
| ₹1.2 crore | एक करोड़ बीस लाख रुपये | A decimal crore becomes crore plus lakh |
| ₹1,499.50 | चौदह सौ निन्यानवे रुपये पचास पैसे | Paise, never a decimal point |
| 12.5% | साढ़े बारह प्रतिशत (or परसेंट) | One word for % per agent |
| 15 तारीख; 15/10 | पंद्रह तारीख; पंद्रह अक्टूबर | Day as a plain number, month by name |
| 1 तारीख | पहली तारीख (or एक तारीख) | The first of the month often takes the ordinal |
| 5:30 PM; 1:30; 2:30 | शाम साढ़े पाँच बजे; डेढ़ बजे; ढाई बजे | साढ़े, सवा, पौने, plus the time of day |
| 3rd किस्त | तीसरी किस्त | Ordinals agree with the noun's gender |
| 2026 | दो हज़ार छब्बीस | A year as a full number |
| OTP 4582 | चार, पाँच, आठ, दो | Codes digit by digit, with pauses; repeat once |
| Ref 20481937 | दो ज़ीरो चार आठ, एक नौ तीन सात | Long codes in groups of three or four |
| 98765 43210 | नौ आठ सात छह पाँच, चार तीन दो एक ज़ीरो | Mobile numbers in two groups of five |
Rupees, lakh and crore: amounts the way Hindi speakers say them
Amounts are where listeners notice a wrong reading first, because the money is usually what the call is about. Three conventions decide whether an amount sounds natural in Hindi: the Indian numbering system, the habit of counting prices in hundreds and fractions, and a consistent choice where two forms are both correct.
The samples in this article are read by Priya, one of the voices on our Hindi text to speech page. Each before-and-after pair uses the same voice and the same sentence, so the only thing that changes is how the number is written.
The Indian numbering system and its commas
Hindi counts large amounts in lakh and crore. One lakh is a hundred thousand, written 1,00,000, and one crore is ten million, written 1,00,00,000. Indian grouping puts a comma after the last three digits and then after every two, so ₹1,49,900 is एक लाख उनचास हज़ार नौ सौ रुपये, never "one hundred forty nine thousand".
A normaliser should work out lakh and crore from the value, not from where the commas sit. Text from forms, CRMs and payment systems arrives with Indian commas, international commas (₹149,900) or none at all, and a Hindi listener expects the same words for all three. The symbol matters less than it looks: ₹, Rs. and INR all mean rupees, and in speech the word रुपये comes after the number even though ₹ is written before it. One rupee is एक रुपया.
Hundreds, fractions and paise
Everyday Hindi often counts prices below ten thousand in hundreds. ₹1,499 is चौदह सौ निन्यानवे, and ₹4,500 is पैंतालीस सौ. The full form, एक हज़ार चार सौ निन्यानवे, is correct but sounds like a cheque being read out, so it suits formal statements better than a sales or support call.
Fractions have their own words, and they are among the clearest signs that a voice speaks Hindi rather than translated English. डेढ़ means one and a half and ढाई means two and a half, so ₹1.5 lakh is डेढ़ लाख and ₹2.5 lakh is ढाई लाख. From three upwards, साढ़े adds a half (साढ़े तीन लाख), सवा adds a quarter (सवा करोड़ for 1.25 crore) and पौने takes a quarter away (पौने दो लाख for 1.75 lakh). Hindi has no "साढ़े एक" or "साढ़े दो": a rule that only knows साढ़े gets the two most common amounts wrong.
A decimal that has no fraction word becomes crore plus lakh, or lakh plus thousand: ₹1.2 crore is एक करोड़ बीस लाख रुपये, not एक दशमलव दो करोड़. Paise are paise, so ₹1,499.50 is चौदह सौ निन्यानवे रुपये पचास पैसे and ₹99.90 is निन्यानवे रुपये नब्बे पैसे. Percentages follow the same idea: 12.5% is साढ़े बारह प्रतिशत, while an exact rate such as 8.65% needs आठ दशमलव छह पाँच प्रतिशत, or "point", which many speakers use.
One house style per agent
Several numbers have two correct readings. ₹2,50,000 can be ढाई लाख or दो लाख पचास हज़ार, % can be प्रतिशत or परसेंट, and 0 can be शून्य or ज़ीरो. None of these is a mistake on its own. What sounds broken is switching between them within one call, so pick one form for each agent and write it down where everyone who edits prompts and scripts can see it.
The biggest choice is whether amounts are said in Hindi at all. Many urban callers say prices in English, and an all-English amount can be the right style for an agent that speaks mostly Hinglish. That is a different decision from the one this article covers; Hinglish text to speech: numbers and accent drift deals with it. Whichever you choose, numbers written as words take more characters than digits, which also changes how long a script runs, as the guide to Hindi voiceover length per character explains.
आपका इस महीने का बिल ₹1,499 है।
आपका इस महीने का बिल चौदह सौ निन्यानवे रुपये है।
आपका लोन ₹2,50,000 का है, और अगली EMI 15 तारीख को कटेगी।
आपका लोन ढाई लाख रुपये का है, और अगली EMI पंद्रह तारीख को कटेगी।
Dates, times and ordinals in spoken Hindi
Dates are simpler in Hindi than in English. The day of the month is a plain number: पंद्रह तारीख, or पंद्रह अक्टूबर with the month named, where English says "the fifteenth". The first of the month is the common exception, often पहली तारीख, as in "सैलरी पहली तारीख को आती है". A slash date such as 15/10 has to be read day first, as Indian forms write it, and the year is usually left out of speech unless it matters. When it is said, 2026 is दो हज़ार छब्बीस.
Clock times use the same fraction words as money. 5:30 is साढ़े पाँच बजे, 4:15 is सवा चार बजे and 6:45 is पौने सात बजे, while 1:30 and 2:30 are डेढ़ बजे and ढाई बजे. Other minutes are said with बजकर, as in चार बजकर दस मिनट for 4:10. Instead of AM and PM, Hindi names the time of day: सुबह, दोपहर, शाम or रात. A 24-hour time such as 17:30 should therefore become शाम साढ़े पाँच बजे; few callers would say सत्रह बजकर तीस मिनट.
Ordinals are the hardest case for a normaliser, because the word depends on the noun. The first few are irregular (पहला, दूसरा, तीसरा, चौथा, छठा) and the rest add वाँ, as in पाँचवाँ or दसवाँ. Each also agrees with the gender of the noun it describes: तीसरा महीना (third month) but तीसरी किस्त (third instalment) and पाँचवीं बार (fifth time). A rule that turns "3rd" into one fixed word will be wrong for half the nouns it meets, so ordinals are best written out by whoever knows the sentence.
आपकी appointment 20 अक्टूबर को शाम 4:15 बजे है।
आपकी appointment बीस अक्टूबर को शाम सवा चार बजे है।
OTPs, account numbers and phone numbers: one digit at a time
Identifiers work best in short groups with a pause between them. The caller is usually writing it down or typing it into another screen, and short groups give them time to do that. Repeating an OTP once costs two seconds and can save a call back.
Length alone is not a safe signal. A six-digit run is almost always a code, but a four-digit OTP looks exactly like a year or a price. "4582" next to the word OTP must become चार, पाँच, आठ, दो, while "4582 रुपये" is an amount. The word in front of the number, such as OTP, PIN, order ID or reference, is the most reliable clue a normaliser has.
Phone numbers need grouping as well as digits. Indian mobile numbers are usually said in two groups of five, so 98765 43210 becomes नौ आठ सात छह पाँच, a pause, then चार तीन दो एक ज़ीरो. Long reference numbers read best in groups of three or four, and a landline sounds most natural with its STD code (the area code, such as 022 for Mumbai) as a group of its own.
One more trap comes from engines that guess. A commenter in the fintech thread who works on a TTS API noted that the choice between one big number and digit by digit can flip between two renders of the same text (r/AI_Agents). Hearing a demo read 48291 correctly once proves little, so render each test sentence several times.
आपका OTP है 4582।
आपका OTP है चार, पाँच, आठ, दो। फिर से, चार, पाँच, आठ, दो।
हमारा नंबर है नौ आठ सात छह पाँच, चार तीन दो एक ज़ीरो।
Where the conversion should happen: the prompt, the engine or a text step
There are three places to turn numbers into words, and voice-agent builders use all three. The first is the LLM prompt. Vapi's prompting guide, for example, tells the model to use the spoken form for dates, money and phone numbers, with "five five five" rather than 555 (Vapi prompting guide).
The second is a setting in the TTS engine. Some providers now let the number language differ from the voice: Smallest.ai's Lightning v3.1 models take a number_pronunciation_language parameter that re-speaks only the numbers, in Hindi words or in English (Smallest.ai changelog). Cartesia's normalization option accepts a locale such as en-IN, and for mixed text its documentation suggests splitting the transcript into separate requests with the same voice (Cartesia text normalisation). The third place is your own text step, a small function that runs just before the text goes to the voice.
Prompt-only fixes work reasonably well in English and break down in code-mixed Hindi, for three reasons. The LLM decides afresh on every reply whether to write a number in Hindi words, English words or digits, so one call can mix all three. Numbers that come from tools, such as a balance from a CRM or a slot from a booking system, often reach the voice as raw digits the LLM never rewrote. And a prompt cannot be unit-tested, while a function that turns ₹1,499 into the same words every time can.
The LLM still knows things a pattern does not, such as whether 4582 is an OTP or a flat number. A workable split is to have the prompt keep the label next to each number ("OTP 4582", "₹1,499") and let a deterministic text step apply the house style. An engine setting can do the same job if it covers your cases, but check lakh and crore, the hundreds form, fraction words and gender in ordinals before you rely on it.
What Sonex's text step does with digits
Pāṇini, Sonex's text-to-speech engine, runs its own text step before every request, through the API and on live calls. It reads digits as English number words in every language. That is a deliberate choice: agents switch languages in the middle of a sentence, and English numerals are widely understood across the markets Sonex serves.
In a Hindi sentence, then, ₹1,499 is spoken as "one thousand four hundred ninety nine rupees" and 5:30 as "five thirty". The step does keep the Indian system: ₹2,50,000 and ₹250,000 both become "two lakh fifty thousand rupees", and Devanagari digits such as ११,००० are read the same as 11,000. Runs of six or more digits without a currency, and phone numbers of ten or more digits, are read digit by digit in English without a pause between groups. A four-digit OTP is read as one number.
There is no setting today to switch this to Hindi number words, but the Sonex text-to-speech API speaks Hindi words exactly as you send them. To hear चौदह सौ निन्यानवे रुपये, send those words, for example from a function like the sketch below. It is a starting point to adapt, not a complete normaliser.
# Minimal Hindi/Marathi number normaliser to run before TTS. pip install indic-numtowords
import re
from indic_numtowords import num2words
DIGITS = {
"hi": "शून्य एक दो तीन चार पाँच छह सात आठ नौ".split(), # or ज़ीरो: pick one
"mr": "शून्य एक दोन तीन चार पाच सहा सात आठ नऊ".split(),
}
CODE = re.compile(r"(OTP|PIN|ref(?:erence)?(?: number| no\.?)?|order(?: id)?)(\s*(?:है|:)?\s*)(\d[\d ]*\d)", re.I)
MOBILE = re.compile(r"(?:\+91[\s-]?)?\b(\d{5})[\s-]?(\d{5})\b")
RUPEES = re.compile(r"(?:₹|Rs\.?)\s?(\d[\d,]*)")
NUMBER = re.compile(r"\d[\d,]*")
def words(n: int, lang: str) -> str:
# Hindi prices: 1,499 is said "चौदह सौ निन्यानवे", not "एक हज़ार चार सौ निन्यानवे".
if lang == "hi" and 1100 <= n < 10000 and (n // 100) % 10:
rest = n % 100
return num2words(n // 100, lang="hi") + " सौ" + (" " + num2words(rest, lang="hi") if rest else "")
out = num2words(n, lang=lang)
return out.replace("हज़ार", "हजार") if lang == "mr" else out
def one_by_one(s: str, lang: str) -> str:
return ", ".join(DIGITS[lang][int(ch)] for ch in s if ch.isdigit())
def normalise(text: str, lang: str = "hi") -> str:
# Order matters: codes and phone numbers first, so they are never read as amounts.
text = CODE.sub(lambda m: m.group(1) + m.group(2) + one_by_one(m.group(3), lang), text)
text = MOBILE.sub(lambda m: one_by_one(m.group(1), lang) + "; " + one_by_one(m.group(2), lang), text)
text = RUPEES.sub(lambda m: words(int(m.group(1).replace(",", "")), lang) + " रुपये", text)
return NUMBER.sub(lambda m: words(int(m.group(0).replace(",", "")), lang), text)
print(normalise("आपका due amount ₹1,499 है, 15 तारीख तक।"))
# आपका due amount चौदह सौ निन्यानवे रुपये है, पंद्रह तारीख तक।
print(normalise("आपका OTP 4582 है।"))
# आपका OTP चार, पाँच, आठ, दो है।
print(normalise("प्रीमियम साधारण ११,००० ते १४,००० रुपये येतो.", lang="mr"))
# प्रीमियम साधारण अकरा हजार ते चौदा हजार रुपये येतो.Marathi and other languages written in Devanagari
Every Indian language needs its own number words, even where the script is shared. Marathi writes in Devanagari like Hindi, but two is दोन, five is पाच, eleven is अकरा and fourteen is चौदा, and thousand is usually spelt हजार. A Hindi table applied to Marathi text produces a sentence no Marathi speaker would say.
The Marathi sentence from the IndicF5 report makes a good test because it packs an age, a cover amount in lakh and a price range into one line. Written out the way a Marathi speaker would say it, nothing is left for the model to skip. The same approach holds for Tamil, Telugu, Bengali and the rest: a separate set of words and a separate native reviewer for each, as on our Marathi text to speech page. Arabic has the same problem with a different script, covered in how Arabic TTS should read numbers, prices and dates.
बत्तीस वर्षांच्या व्यक्तीसाठी पाच लाखांच्या कव्हरचा प्रीमियम साधारण अकरा हजार ते चौदा हजार रुपये येतो.
Testing Hindi number reading before callers hear it
A fixed list of sentences is the only way to know whether a change helped. Each line below holds one number case from the table near the top, and the table gives the reading to listen for. Two lines test the label rule: 4582 रुपये should be an amount (पैंतालीस सौ बयासी, or चार हज़ार पाँच सौ बयासी in the full form), while Order ID 7731 should be read digit by digit.
Render each line at least three times, because some engines choose differently between runs. Listen through a real phone line or an 8 kHz copy of the audio, since that is what callers hear; the guide to 8 kHz phone audio and 16 kHz models explains what the narrow band removes. Ask a native speaker to mark each line right or wrong against the expected reading, not to give an overall impression.
Add lines from your own scripts until the list covers every kind of number your agent or narration uses. For voiceovers, real scripts such as our Hindi voice-over script samples are a quick source of the dates and prices that narrators meet.
आपका बिल ₹1,499 का है।
Loan ₹1,49,900 का है।
Loan ₹149,900 का है।
₹2,50,000 का loan approve हो गया है।
फ्लैट की कीमत ₹1.2 crore है।
Refund ₹1,499.50 का है।
ब्याज 12.5% सालाना है।
EMI 15 तारीख को कटेगी।
सैलरी 1 तारीख को आती है।
Appointment 15/10 को है।
शाम 5:30 बजे आइए।
दवा दोपहर 1:30 बजे लेनी है।
यह आपकी 3rd किस्त है।
Policy 2026 में renew होगी।
आपका OTP 4582 है।
4582 रुपये बाकी हैं।
Order ID 7731 है।
Reference number 20481937 है।
मेरा नंबर 98765 43210 है।
प्रीमियम ११,००० रुपये है।
[mr] प्रीमियम ११,००० ते १४,००० रुपये येतो.
[mr] माझा नंबर 98765 43210 आहे.Sources
- IndicF5 issue 31: number pronunciation causing gibberish noise ("Your OTP is 4582"), May 2026Official
- Indic Parler-TTS discussion 9: TTS doesn't spell numbers correctly, Jan 2025Official
- IndicF5 discussion 44: model unable to read digits in Marathi, Sep 2026Official
- kokoro issue 129: Error with Hindi texts ($3.12 read as 312$), Mar 2025Official
- r/AI_Agents: Shipped a Hindi-English voice agent for a fintech, Aug 2026Secondary
- Vapi: Prompting guide (spoken forms for dates, money and phone numbers)Official
- Smallest.ai: Lightning v3.1 changelog, number_pronunciation_languageOfficial
- Cartesia: Text normalization capability guideOfficial
- indic-numtowords on PyPI (MIT licence)Official
Change log: 24 Oct 2026, Rewritten as one continuous guide before publication: added Indian grouping, fraction words (डेढ़, ढाई, सवा, साढ़े, पौने), ordinals and gender, 24-hour times, engine-level number settings, and a description of how Sonex's text step reads digits; steps, checklist and FAQs folded into the body; before-and-after audio pairs in one Hindi voice




