Why USD123.45 becomes “US dollars” but SGD123.45 gets spelled out

Published on

A screen reader turns a screen into speech. On Windows you meet NVDA, Narrator and JAWS; on macOS and iOS, VoiceOver; on Android, TalkBack. It does not make the speech itself. It collects text and hands it to a speech synthesizer, the component that also reads e-books and GPS directions, then plays the audio it gets back.

The sound of your page depends on that hand-off, and on which voice the reader speaks through. Put two lines in a page:

<p>USD123.45</p>
<p>SGD123.45</p>

On Windows 10 and 11, NVDA speaks through Windows OneCore by default 1. OneCore reads the first as “one hundred and twenty three U S dollars and forty five cents” and the second as a spelled-out code followed by a decimal number: “S G D one twenty three point forty five”. No “Singapore dollars”, no cents. Narrator, Microsoft’s own screen reader, uses OneCore too and produces the same two readings 2 3.

NVDA also bundles eSpeak NG, the fallback used where OneCore is unavailable 4 1. eSpeak NG does not expand currency 2, so on it both lines come out as letters and a decimal. The money reading above is OneCore’s doing, not a property of NVDA itself.

The digits come through in both cases, so the number itself is read correctly. What SGD loses is the currency: the listener hears a code they have to decode, and never hears the currency’s name or its minor unit. On a page that mixes Singapore and US amounts, they have to work out which currency they just heard, and can get it wrong.

That observation is from NVDA on Windows 10 using its default OneCore voice, and the two lines are plain text nodes in the same browser, and nothing but the code itself differs. The broader per-reader results later in the post come from a published comparison, and I did not re-run them.

The difference has to appear downstream of your markup. The string passes from the <p> into the DOM, into the browser’s accessibility tree, through the screen reader, and into the operating system’s speech engine. There, the engine’s text normalizer matches it against a currency table, and one code is tagged as money while the other is not.

TL;DR

The browser passes the string through. Chrome’s accessibility tree hands the operating system USD123.45 as plain text 5, and Blink, Chrome’s rendering engine, has no currency logic in the code that builds that tree 6. The screen reader passes it along too: NVDA holds no currency table, only a per-language symbol dictionary for punctuation 7.

The synthesizer decides. Its text normalizer tags a token as money and rewrites it into words. NVDA’s tracker names the engines that do this (Microsoft OneCore, SAPI5, IBM’s IBMTTS) and the one that does not (eSpeak NG) 2. Windows, Android and Apple’s synthesizers all normalize currency in the same place, and the readers forward the text 2 8 9 10.

What differs is the currency list, which is short and rarely published 3. $ and USD sit in the en-US table and SGD does not 11; NVDA and TalkBack ship no amount table of their own, only a symbol dictionary 7 8, and the open normalizers from Google and NVIDIA ship small ones 12 13. HTML has no currency element 14, so write the currency name out or supply the spoken form yourself.

Clips from two Google TTS builds on Android, Microsoft OneCore on Windows, and an eSpeak NG column let you hear what the voice actually does; the Google and OneCore columns are transcribed against the audio, and eSpeak appears as phonemes rather than words. The OneCore run is Windows 10 22H2 build 19045.2965 with the OneCore engine 10.3.21207.0 15.

What Narrator and NVDA share

When two readers say the same thing, the cause lies in what they share: the browser and the speech engine. Both were reading Chrome, so if Blink expanded currency codes on the way into the accessibility tree, both readers would inherit the expansion. Narrator speaks through Microsoft’s voices 16; NVDA speaks through the synthesizer it is set to, OneCore by default on Windows 10 and 11 2 1. An NVDA that matches Narrator is running a Microsoft engine.

The browser is the only component upstream of the accessibility tree, so start there. If Chrome passes the text through unchanged, the rewrite has to happen after the tree, in an engine both readers can use.

What Chrome hands over

Chrome builds an accessibility tree, a machine-readable map of the page, and exposes it to the operating system: to UIA on Windows, to AT-SPI on Linux 6. Screen readers read that tree, so an expansion added by the browser would show up in it.

A page with one text node per format:

<p id="a">USD123.45</p>
<p id="b">SGD123.45</p>
<p id="c">$123.45</p>
<p id="d">US$123.45</p>
<p id="e">EUR123.45</p>

Here is the tree Chrome exposes for it, which you can read in DevTools under Elements → Accessibility 5:

RootWebArea "currency announcement test cases"
StaticText "USD123.45"
StaticText "SGD123.45"
StaticText "$123.45"
StaticText "US$123.45"
StaticText "EUR123.45"

Every node carries the literal string. Chrome adds no currency role and no spoken form. Blink’s accessibility code, where such a rewrite would happen, holds no currency table and makes no Intl.NumberFormat call 6. It does build strings for other cases, such as alt text, but not for money.

The screen reader forwards it

NVDA is open source, so “does NVDA expand currency?” is a question you can answer by reading the code. It does not. NVDA’s speech code contains no currency table, no money rule and no amount parser. What it carries is a symbol dictionary per language that gives a spoken word for punctuation the synthesizer would otherwise skip 7. The dictionary has an entry for the $ character. It does not read USD123.45 as an amount.

The maintainers chose that division on purpose. In 2010 someone filed a request to have NVDA speak $10.00 as an amount and $0.50 as “50 cents”. They closed it as work for the synthesizer 7:

This sort of special processing is very controversial. Personally, I prefer to hear the text as close as possible to its original form, as there is less room for error. […] This is also probably something that should be done in the synthesiser, not in NVDA.

As this is a synthesizer specific issue, can it therefore be closed? Using synths such as Eloquence, Vocalizer Expressive and Windows OneCore Voices, I do not experience this. It’s something the eSpeak NG team needs to work on, in my view.

NVDA forwards the text and reports back what the synthesizer gives it. A Narrator/NVDA comparison therefore lines up only when both run on the same engine.

A 2022 bug fix shows the hand-off directly. An NVDA contributor tracked down a misreport and wrote up the cause 2:

Read the following line with OneCore (e.g. Zira) and symbol level all: What is USD???? … I can hear: “What is four US dollars question” […] The issue is due to the fact that the synth interprets USD 4 as four dollars. […] The impacted synthesizers are: OneCore, SAPI5, IBMTTS. On the contrary, eSpeak is not impacted. Probably because it does not handle internally expressions such as USD 4.

The synthesizer rewrites USD plus a number into a spoken amount, and the list names which synthesizers do it. eSpeak NG, the free engine bundled with NVDA, does not. So some NVDA users hear “US dollars” and others hear letters, depending on the synthesizer they selected.

Text normalization

A text-to-speech engine never starts from characters. It runs input through text normalization first, which turns written forms into spoken words: 2016 into twenty sixteen, Dr. into doctor, 12:45 into twelve forty-five, and $34.90 into thirty-four dollars and ninety cents 17 18.

Microsoft’s speech API treats this as a named category. A CONTEXT attribute called Currency tells the engine to normalize a block of text as money, and the documentation gives its own example 17:

$34.90 will be normalized to “thirty four dollars and ninety cents”

Microsoft leaves the implementation to each engine, so each engine keeps its own currency list 17.

Modern engines split the work into two steps 18 19:

flowchart LR
    A["input text
USD123.45"] --> B["classify
(tokenize + tag)"] B --> C["semiotic class: MONEY
integer: 123 · fraction: 45 · currency: USD"] C --> D["verbalize
(rewrite to words)"] D --> E["one hundred and twenty three
U S dollars and forty five cents"] E --> F["grapheme-to-phoneme
+ acoustic model"] F --> G["audio"]

The tagging step is what splits the two outcomes. A normalizer that tags the token MONEY writes a spoken amount: the number, the major unit, and the fraction expressed as a minor unit. A normalizer that finds nothing special reads the letters and then the number as a plain decimal 18. Those are the two results in the opening example.

The fallback reuses the same TranslateNumber machinery every engine needs for cardinals and decimals 20. eSpeak NG documents number fragments, decimal-point words and ordinals, and documents no currency table at all 4 20. To eSpeak, SGD123.45 is an ordinary string of letters and a number.

The same machinery handles dates and phone numbers

Currency is one semiotic class among several, alongside dates, times, measures, phone numbers, ordinals and fractions. An engine carries a table for each class, in each language, and the same distinction applies to each: a token is either in the table or not. A year written 2026 and a serial number 2026 take different paths through the same engine, and so do a known abbreviation and one that gets spelled out 19. When assistive technology reads a string wrong, the useful question is which layer recognized it as a class and which did not.

Why USD is in the table and SGD is not

A normalizer can only recognize what its table lists, and the published tables are short and tied to the voice’s locale. Other vendors publish theirs, and the shape repeats:

Engine (per its docs)What it treats as currency
Genesys Interaction TTS, en-US$ or USD only 11
Genesys Interaction TTS, en-AU$ or AUD only 11
Genesys Interaction TTS, ja-JP¥ or JPY only 11
LumenVox TTS1”a wide list … many of the ISO 4217 currency codes”, not all of them 21

The engines that publish their lists put $ and USD in the en-US table and little else in that locale’s short list 11. Microsoft does not publish its table, so its exact contents have to be inferred from behaviour. Treat a code’s recognition as a property of the engine, not a guarantee 22.

Cross-reader testing shows the same split. In a 2024 comparison of five screen readers, AUD was recognized by NVDA, Narrator and VoiceOver, while THB (Thai baht) and VND (Vietnamese dong) were not recognized by any of them. THB58.96 came out as letters plus a number, “t h b fifty-eight point nine six”, which matches the shape of the SGD result above 3.

The Singapore dollar’s subunit is the cent, the same word an engine already carries for USD, so the missing minor unit is not because SGD has no cent. The engine has no SGD entry in its en-US table, and the token never reaches the money branch. Unicode’s CLDR carries a display name, a symbol and plural forms for almost every ISO 4217 code, including Singapore Dollar, yet it carries no subunit names and no spoken-form grammar 23 24. An engine that needs to say “and forty five cents” has to carry that word itself, which is one reason each vendor keeps a hand-maintained list 25 24.

The engine only speaks a minor unit once it has recognized a major one. That is why USD123.45 gets “cents” and SGD123.45 does not.

The concatenated form is the weak case

The opening example glues a code to a number, USD123.45, and that exact form is where the evidence is weakest. Every open normalizer measured here spells it, as the appendices show, and the published example of the mechanism uses a space (USD 4) 2. Microsoft’s table is unpublished too, so the concatenated case rests on the opening observation plus that engine’s documented behaviour with a space. A code stuck to a number is not the input these engines were tested on, and one engine here managed a money reading for it.

The probes settle whether the space matters. ja-JP returns JPY123 and JPY 123 as byte-identical clips, and ta-IN does the same for INR123 and INR 123, so spacing carries no weight. The token does. ja-JP renders ¥123 and 円123 as different clips, while ta-IN renders ₹123 and ரூபாய்123 as the same one. Recognition follows the symbol or the word the table holds, not a code glued to a number.

What each screen reader does

The table below condenses a 2024 comparison of five screen readers 3.

Screen reader (tested versions)Reads as moneyDoes not
NVDA 2023.3 (Windows)$123, US$123, AUD123A$123, THB123, VND123
Narrator (Windows)same as NVDAsame
VoiceOver (iOS 15.8)$123, A$123, AUD123, ¥123, £123, €123US$123, THB123, VND123
TalkBack (Android 10)$123A$123, AUD123, €123, THB123
JAWS 2024 (Windows)£123$123, ¥123, €123, AUD123, THB123

The source did not record which synthesizer each reader used, so read a row as the behaviour of one reader paired with one engine, not as a property of the product. $58.96 came out the way a person would say it in NVDA, Narrator, TalkBack and VoiceOver, and as “dollar fifty-eight point nine six” in JAWS, from the same page and the same bytes 3.

Unrecognized currency fails the same way: letters plus a plain decimal, as in “t h b fifty-eight point nine six”, not a misread number 3.

Android: TalkBack and the engine under it

TalkBack is open source 8. Its source carries a currency symbol dictionary, mapping $, €, £, ¥ and others to spoken names, but no currency-amount table 8. Like NVDA, it takes text from the accessibility tree and hands it to the platform’s text-to-speech engine 26.

On Android that engine is a separate, replaceable app, and the default is Google’s Speech Recognition & Synthesis (com.google.android.tts), which is closed source 9. Swap in an OEM engine, the open-source eSpeak NG 4, or AOSP’s open-source Pico TTS 27, and the announcement changes with the engine.

TalkBack recognized the least of the five readers. On the test device it recognized only $ on its own. A$58.96 became “a dollar fifty-eight point nine six”, €58.96 dropped the symbol and read “fifty-eight point nine six”, and AUD59.96 and THB58.96 were spelled out 3. Whether that limit belongs to TalkBack or to its engine is unresolved: TalkBack is open source, the engine is not 8 9.

Apple: VoiceOver and the synthesizer it drives

VoiceOver is closed source, and so is the speech engine behind it, so there is no table to read. Apple’s engineers have described the layer in public. In a 2021 ICU4X discussion, one of them listed what a spoken-currency system has to do and why Apple did not reuse ICU’s currency formatting for Siri 24:

The major unit and minor units need to be independently pronounced. […] USD with US region: dollar/dollars. CAD with CA region: dollar/dollars. USD with CA region: US dollar/US dollars. CAD with US region: Canadian dollar/Canadian dollars. […] Fractions of currency subunits can be needed, like 2.4 cents. […] Subunit translation is needed, like the notion of cent.

A reply on the same thread names the data gap that keeps these tables short 24:

ICU and CLDR have no support for subunit names. CLDR also doesn’t have always have data for colloquial names.

Apple put this logic in the synthesizer, not in VoiceOver. AVSpeechSynthesizer, the API developers call directly, reads $100.99 as “one hundred dollars and ninety nine cents” 10. A filed bug shows it reading $13.3 million as “13 dollars and 30 cents million”: the money classifier fired on a token the synthesizer should have read as a number 28. VoiceOver was the most permissive reader in the comparison, understanding $, A$, AUD, ¥, £ and €, and it still garbled others: US$58.96 became “u s u s”, and €58.96 left the cents as “dot ninety-six” 3.

Writing money for a screen reader

No markup makes the browser fix this, and no markup marks a value as money. HTML has an open, unimplemented request for a <currency> element, which is not a tool you have today 14. Text normalization runs on a flat string, after the markup is gone, so no attribute can change it. Setting the page lang does not help either, since the table comes from the voice’s locale, not the document’s 3.

Three options, in order of reliability:

  • Write the currency name as words where the amount is the message: “123.45 Singapore dollars”. This reads correctly on every engine because it needs no currency table 3. Writing the bare code (SGD123.45) is not enough: every open engine measured below spells it out.

  • Supply the spoken form. Keep the compact form for sighted users, and give assistive tech a version it can read 29:

    <p>
    <span aria-hidden="true">USD123.45</span>
    <span class="sr-only">123 dollars and 45 cents</span>
    </p>

    An aria-label works as well, on an element whose role supports a name: <a href="…" aria-label="Pay 123 dollars and 45 cents">USD123.45</a>. A bare <span> is not a reliable host for one, because the span has no role for the name to attach to 30.

  • Keep the amount in one text node. $ + 123 + . + 45 in four spans destroys the token before the normalizer sees it, and each piece gets read on its own 29. That is the usual reason a page that would have been read correctly stops being read correctly.

A checklist for a price or a balance:

  • Ambiguous currency? Write the code or the name as text and do not rely on the amount being understood 3.
  • Data someone has to act on, such as a total or a transfer? Give it an explicit accessible name 29.
  • Decorative or repeated? Keep it simple and let the engine do its job 29.
  • Before assuming the announcement holds, test with two synthesizers, such as NVDA with eSpeak NG and NVDA with OneCore 2.

What the open engines publish

For some engines you can predict the read-out from the table; for others you have to listen.

Open engines that carry no currency table:

  • eSpeak NG ships its number handling in source. numbers.c holds no currency logic 20, and the English dictionary’s only currency-code entries are usd $abbrev $allcaps and eur jU@ $only: USD is an abbreviation to spell and EUR a word to say, and neither adds amount handling 31. eSpeak’s behaviour is fully knowable, and it is “S G D one hundred and twenty three point four five”.
  • NVDA 7 and TalkBack 8 are open source and likewise carry no currency-amount table, only symbol maps. Their output is whatever the selected engine produces.

Open engines with small currency tables you can read:

  • Google’s Sparrowhawk is the text-normalization framework behind Kestrel, which Google’s own normalizer derives from. It ships a money grammar. Its classification table maps $, £ and €; its verbalization table supplies dollars/cents, pounds/pence, euros/cents. Three currencies 12.
  • NVIDIA’s NeMo text-processing ships a larger English table. currency_major.tsv holds about forty entries: symbols and lowercase tokens such as cad, nzd, chf, dkk, plus ¥ (yen) and ฿ (Thai baht). currency_minor_plural.tsv supplies cents and pence. sgd is absent 13.

Closed engines, which you probe by ear:

  • Microsoft’s OneCore and SAPI5 engines 17, Google’s Speech Recognition & Synthesis 9, Apple’s synthesizers 24, and the commercial voices from Nuance, IBM and Eloquent 7. These vendors publish neither source nor table.

Not even CLDR holds a canonical list. It carries a display name for almost every ISO 4217 code, but no subunit names and no spoken-form grammar 23 24. The currency read-out is a handful of vendor tables that agree on $ and USD and diverge everywhere else.

For the open engines the logic is in the source:

EngineWhere the currency logic lives
Flite (CMU)lang/usenglish/us_text.c, inside us_tokentowords_one(), the “US money” branch 32
eSpeak NGdictsource/en_list and dictsource/en_rules; symbol to word only 33
NeMo text-processing (NVIDIA)text_normalization/en/taggers/money.py and verbalizers/money.py, with tables in data/money/*.tsv 13
Sparrowhawk (Google)documentation/grammars/en_toy/{classify,verbalize}/money.grm plus money.tsv 12
num2wordsnum2words/base.py (CURRENCY_FORMS) and num2words/lang_EU.py 34

Flite gives the clearest example from a real synthesizer. Its tokenizer matches a dollar amount with one regex, named usmoney, and rewrites it:

/* lang/usenglish/us_text.c, abridged. usmoney (from make_us_regexes): \$[0-9,]+(\.[0-9]+)? */
else if (cst_regex_match(usmoney, name)) {
/* US money */
p = strchr(name, '.');
...
else if (cst_strlen(p) == 1 || cst_strlen(p) > 3) {
/* simply read as mumble point mumble */
r = val_append(en_exp_real(&name[1]), cons_val(string_val("dollars"), NULL));
} else {
...
r = val_append(en_exp_number(p + 1), cons_val(string_val("cents"), NULL));
...
}
}

Source at flite commit 6c9f20d: the branch above is in lang/usenglish/us_text.c#L611-L665, and the usmoney regex in lang/usenglish/make_us_regexes#L45. mumble is flite’s placeholder for a number, so the comment means “say the digits literally”: $12.345 becomes “twelve point three four five dollars”, not a cent amount.

The branch handles $-prefixed amounts, writes dollar/dollars and cent/cents, and reads the number as a plain decimal when there are no digits after the dot, or three or more. A code like SGD never reaches it.

Built and run, it does what the code says 35:

InputFlite read-out
$123.45”one hundred twenty three dollars forty five cents”
$1.5”one dollar five cents”
$0.50”zero dollars fifty cents”
$12.345”twelve point three four five dollars”
$1,234.56”one thousand two hundred thirty four dollars fifty six cents”
SGD123.45”s g d one hundred twenty three point four five”

Two details appear only at runtime. $0.50 speaks “zero dollars fifty cents”, so the major unit is spoken even when zero. $1.5 speaks “one dollar five cents”, treating a single digit after the dot as a whole number of cents. Non-$ symbols are not currency tokens to Flite at all: £123.45 is left as-is, and no ISO code reaches the branch.

eSpeak NG’s equivalent is smaller: a symbol-to-phoneme entry, with no amount logic.

# dictsource/en_list:126 (spoken name of the "$" character)
_$ d0l3
# dictsource/en_rules:7118 ("$" inside a word)
$ d0l3

d0l3 is eSpeak’s phoneme string for “dollar”. USD appears elsewhere in the same file as an ordinary abbreviation, usd $abbrev $allcaps, so it is spelled out 33.

Sparrowhawk keeps its three currencies in two small TSV files:

# classify/money.tsv # verbalize/money.tsv
$ usd usd_maj dollars
£ gbp usd_min cents
€ eur gbp_maj pounds
gbp_min pence
eur_maj euros
eur_min cents

Its grammar inserts " and " between major and minor and singularizes after “one”, so $3.50 becomes “three dollars and fifty cents” 12.

Appendix: the top-20 currencies, engine by engine

These are the 20 currencies by 2022 foreign-exchange turnover 36. money means the engine names the currency for an amount, including its minor unit where the engine has one. symbol means it names the symbol but reads the amount as a plain decimal. text means a table entry exists and the normalizer still reads the input as a spelled code plus a decimal. none means no entry. eSpeak NG’s and Flite’s columns are measured (espeak-ng -q -x and flite -pw; the dumps and scripts are committed under artifacts/ 37 35). The NeMo column is measured too, over the same artifact set 38. Sparrowhawk and num2words are read from their tables.

CurrencyFliteNeMo (en)Sparrowhawknum2wordseSpeak NG
USDmoney ($)money ($, US$)money ($)moneysymbol ($)
EURnonemoney (€)money (€)moneysymbol (€)
JPYnonemoney (¥)nonemoneysymbol (¥)
GBPnonemoney (£)money (£)moneysymbol (£)
CNYnonenonenonenonenone
AUDnonenonenonemoneynone
CADnonetext (cad)nonemoneynone
CHFnonetext (chf)nonenonenone
HKDnonemoney (hk$)nonenonenone
SGDnonenonenonenonenone
KRWnonemoney (₩)nonemoneysymbol (₩)
INRnonetext (rs)nonemoneysymbol (₹)
NZDnonetext (nzd)nonenonenone
SEKnonetext (sek)nonemoneynone
NOKnonetext (nok)nonemoneynone
MXNnonenonenonemoneynone
TWDnonenonenonenonenone
ZARnonenonenonenonenone
BRLnonetext (r)nonenonesymbol (R$)
THBnonemoney (฿)nonenonenone (symbol dropped)

NeMo’s English model is the widest of the three open tables and still covers 7 of these 20 as money. The text rows matter: cad, chf, nzd, sek, nok, rs and r all sit in the table and none of them fires when concatenated to an amount, which is the same distinction behind the pt and vi failures below. In English, none of these engines names SGD, CNY, TWD or ZAR. Language shifts the result more than the engine brand does: NeMo’s Chinese model names all four.

NeMo’s entries are literal tokens (cad, hk$, rs), so an uppercase CAD amount is a different input. num2words only names a currency when the caller passes the code, since it has no detector. Neither caveat applies to a symbol an engine matches on its own.

Closed engines offer no table to enumerate, only measurements. The five-reader comparison 3 gives, for $58.96, “fifty-eight dollars and ninety-six cents” in NVDA, Narrator, TalkBack and VoiceOver, and “dollar fifty-eight point nine six” in JAWS. Add US$ and the readings split: NVDA and Narrator keep the money reading, VoiceOver says “U S U S”, JAWS stays symbol-first. £58.96 is money in NVDA, Narrator, VoiceOver and JAWS; TalkBack names the pound but reads the amount as a plain decimal, “fifty-eight pounds point nine six” 3. THB58.96 comes out as letters and a decimal in all five. The source labels the Australian amount AUD59.96 once and AUD58.96 elsewhere; I use 58, which its own read-outs agree on.

How the same amount reads in other languages

Locale questions need an engine with per-language currency data, and the open-source one that has it is NVIDIA’s NeMo text-processing. Fifteen languages carry a money grammar and twelve ship currency tables. Running its normalizer over every token in each language’s table produces a cross-locale dataset 38. Here are the same four symbols, in every language the tables carry, including the ones that fail:

Language$123.45€123.45£123.45¥123.45
enone hundred and twenty three dollars forty five cents… euros forty five cents… pounds forty five pence… yen. forty five
hiएक सौ तेईस डॉलर पैंतालीस सेंटएक सौ तेईस यूरो पैंतालीस सेंटएक सौ तेईस पाउंड पैंतालीस पेंसएक सौ तेईस येन पैंतालीस सेन
arمئة وثلاثة وعشرون دولار وخمسة وأربعون سنتمئة وثلاثة وعشرون يورو وخمسة وأربعون سنتمئة وثلاثة وعشرون جنيه استرليني وخمسة وأربعون بنسمئة وثلاثة وعشرون ين ياباني وخمسة وأربعون
ko백이십삼 달러사십오백이십삼 유로사십오£ 백이십삼점사오백이십삼 엔사십오
zh十二三点四五美元十二三点四五欧元十二三点四五英镑十二三点四五圆
essigno de dólar uno dos tres punto cuatro cincociento veintitrés euros . cuarenta y cincociento veintitrés libras . cuarenta y cincociento veintitrés yenes . cuarenta y cinco
itdollaro uno due tre punto quattro cinqueun euro due tre punto quattro cinqueuna sterlina due tre punto quattro cinque¥123.45
dedollar zeichen eins zwei drei punkt vier fünfein euro zwei drei punkt vier fünfein pfund zwei drei punkt vier fünfein yen zwei drei punkt vier fünf
ptdólar um dois três ponto quatro cincoERRERRERR
svhundratjugotre dollar . fyrtiofemhundratjugotre euro . fyrtiofemhundratjugotre pund . fyrtiofemhundratjugotre yen . fyrtiofem
hudollárjel egy kettő három pont négy öt€123.45százhuszonhárom font . negyvenöt¥123.45
viERRERRERRERR

Three languages produce a full major-plus-minor reading: English (dollars/cents, pounds/pence), Hindi (डॉलर/सेंट, पाउंड/पेंस) and Arabic (دولار/سنت, جنيه/بنس). Korean names the unit and appends the fraction with no “cents” word, as in 백이십삼 달러사십오, “one hundred twenty-three dollar forty-five”. The rest name the major unit and read the number as a decimal or digit by digit. German turns $ into dollar zeichen (dollar sign) and then reads every digit. Spanish keeps a literal period, ciento veintitrés euros . cuarenta y cinco. Swedish reads the digits and drops the minor unit. Chinese names the currency and reads the amount as 点四五, never splitting it into 元/角/分.

A table entry is not a working read-out. Vietnamese carries nineteen entries: fourteen name the symbol and read the amount digit by digit, and five raise FstOpError ($, €, ¥, £, ₩). Portuguese carries four, reads two as symbols, and fails on € and £. Italian and Hungarian leave ¥ untouched. An entry can exist and the grammar can still fail to render it.

Chinese classifies bare ISO codes. NeMo’s zh model keys on ISO codes and currency symbols, so unlike English it reads the concatenated code form 38:

InputNeMo zhNeMo en
USD123.45十二三点四五美元usd one two three dot four five
SGD123.45十二三点四五新加坡元sgd one two three dot four five
CNY123.45十二三点四五人民币cny one two three dot four five
TWD123.45十二三点四五新台币twd one two three dot four five
ZAR123.45十二三点四五南非兰特zar one two three dot four five

The English column shows the same fallback: USD123.45 is not classified as money in English NeMo either, because the English tokens are $ and US$. Whether an input counts as an amount depends on the language, and is settled before anything is spoken.

Coverage is uneven, and the raw counts hide how uneven. The sweep runs over 105 ISO codes 38. This counts every table entry that maps to one of those codes and what happened when that token was concatenated with an amount:

Languagemoneynamed the symbol, digits readtext onlyerrorunclassified
zh810000
sv230002
ar160000
ko90000
en701700
hi60000
de051300
hu05000
vi014050
it03401
pt02020
es1010400

Chinese works for all 81 of the swept codes its table carries. English works for 7 of the 24 codes its table carries, 29% of its own entries. The same trap that catches English catches Spanish harder: 105 codes map to words and only one works when concatenated with an amount. Vietnamese lists 19: 14 merely name the symbol while 5 fail. Claiming that a language supports a currency needs the working count, not the table size.

Two caveats. This measures NeMo’s text normalization, the spoken string the engine would pass to a synthesizer, not audio or a screen reader. Every test also uses the prefix form (USD123.45); postfix forms such as 123,45 € are untested here. Several outputs carry visible defects: Spanish and zh number grouping, the stray period in es and sv, and the FstOpErrors. They are shown as the engines produced them; the post documents behaviour, not fixed output.

Closed engines: audio only

The engines most people hear (Google’s on Android, Microsoft’s and Apple’s) publish no table, so reaching them needs another route. On Android the TTS engine is a replaceable app, and TextToSpeech.synthesizeToFile renders any string to a WAV. A small harness captures real audio for the locales you install 39. Google’s engine exposes 218 voices across 48 locales 39, and the committed set covers 15 of them: en-US, en-GB, en-IN, id-ID, zh-CN, zh-TW, yue-HK (Cantonese), th-TH, ja-JP, ta-IN (Tamil) and five European locales. Two builds are captured, 103.12.8 on a OnePlus 6T and 20260817.01 on a Pixel 9, and the gallery shows both, since a new build can change a read-out.

Amounts use each locale’s own decimal separator, verified against CLDR: a period for en-US, en-GB, en-IN, zh-CN, zh-TW, yue-HK, th-TH, ja-JP, ta-IN, and a comma for id-ID, de-DE, fr-FR, es-ES, it-IT, nl-NL. The Indonesian set is IDR123,45, IDR123, and IDR123.45 as a wrong-separator control. Each locale runs USD123<sep>45, SGD123<sep>45, $123<sep>45, the cents-only USD0<sep>10, then its own currency. id-ID, en-IN, ja-JP and ta-IN add an integer form of that currency (IDR123, INR123, JPY123), and ja-JP and ta-IN add three probes that swap the code for a symbol, a word, or a spaced code.

Android’s API returns audio and never the normalized string, so the words have to come from listening. The gallery below carries a clip and a transcript for each amount. Three columns are word read-outs transcribed against the audio: two Google builds (103.12.8 and 20260817) and Microsoft OneCore on Windows 10 22H2 (engine 10.3.21207.0), the voice NVDA and Narrator use by default. The fourth, eSpeak NG, is an open engine shown as phonemes, behind a toggle. Currency, the number, the point word and the minor unit are each coloured, with a second, non-colour cue so the split survives without colour; non-Latin read-outs carry a romanization underneath.

Google’s pipeline does show up in the data it ships. The APK’s bundled en-US voice carries en_verbalize_spec.pb, a spec that names a money class with currency, amount.integer_part and amount.fractional_part fields. That is the same classify-then-verbalize split the other engines use 39.

For closed engines you can hear the difference, and the data they ship shows the shape of their pipeline, but their currency table stays out of reach.

The clips and their transcripts

90 forms A = 103.12.8 (2023) · B = 20260817 (2026) · D = OneCore (Win 10 22H2)

Engines
Input
Read-out
en-US
USD123.45
A · 103.12.8 one hundred twenty three United States dollars and forty five cents
B · 20260817 one hundred twenty three United States dollars and forty five cents
C · eSpeak NG j,u:,Esd'i: w'Vnh'VndrI2d tw'Ent2i Tr'i: pOInt f'o@ f'aIv
D · OneCore one hundred and twenty three U S dollars and forty five cents
SGD123.45
A · 103.12.8 one hundred twenty three Singapore dollars and forty five cents
B · 20260817 one hundred twenty three Singapore dollars and forty five cents
C · eSpeak NG ,EsdZ,i:d'i: w'Vnh'VndrI2d tw'Ent2i Tr'i: pOInt f'o@ f'aIv
D · OneCore S G D one twenty three point forty five
$123.45
A · 103.12.8 one hundred twenty three dollars and forty five cents
B · 20260817 one hundred twenty three dollars and forty five cents
C · eSpeak NG d'0l3 w'Vnh'VndrI2d tw'Ent2i Tr'i: pOInt f'o@ f'aIv
D · OneCore one hundred and twenty three dollars and forty five cents
USD0.10
A · 103.12.8 ten United States cents
B · 20260817 ten United States cents
C · eSpeak NG j,u:,Esd'i: z'i@roU pOInt w'Vn z'i@roU
D · OneCore ten cents
en-GB
USD123.45
A · 103.12.8 one hundred and twenty three United States dollars and forty five cents
B · 20260817 one hundred twenty three United States dollars and forty five cents
C · eSpeak NG j,u:,Esd'i: w'0nh'VndrI2d@n tw'Enti Tr'i: pOInt f'o@ f'aIv
D · OneCore one hundred and twenty three U S dollars and forty five cents
SGD123.45
A · 103.12.8 one hundred and twenty three Singapore dollars and forty five cents
B · 20260817 one hundred twenty three Singapore dollars and forty five cents
C · eSpeak NG ,EsdZ,i:d'i: w'0nh'VndrI2d@n tw'Enti Tr'i: pOInt f'o@ f'aIv
D · OneCore S G D one two three dot forty five
$123.45
A · 103.12.8 one hundred and twenty three dollars and forty five cents
B · 20260817 one hundred and twenty three dollars and forty five cents
C · eSpeak NG d'0l3 w'0nh'VndrI2d@n tw'Enti Tr'i: pOInt f'o@ f'aIv
D · OneCore one hundred and twenty three dollars and forty five cents
USD0.10
A · 103.12.8 ten United States cents
B · 20260817 ten United States cents
C · eSpeak NG j,u:,Esd'i: z'i@roU pOInt w'0n z'i@roU
D · OneCore ten cents
GBP123.45
A · 103.12.8 one hundred and twenty three British pounds and forty five pence
B · 20260817 one hundred and twenty three British pounds and forty five pence
C · eSpeak NG dZ,i:b,i:p'i: w'0nh'VndrI2d@n tw'Enti Tr'i: pOInt f'o@ f'aIv
D · OneCore one hundred and twenty three pounds sterling and forty five pence
en-IN
USD123.45
A · 103.12.8 one hundred and twenty three United States dollars and forty five cents
B · 20260817 one hundred twenty three United States dollars and forty five cents
C · eSpeak NG no voice
D · OneCore one hundred and twenty three U S dollars forty five cents
SGD123.45
A · 103.12.8 one hundred and twenty three Singapore dollars and forty five cents
B · 20260817 one hundred and twenty three Singapore dollars and forty five cents
C · eSpeak NG no voice
D · OneCore S G D one two three dot forty five
$123.45
A · 103.12.8 one hundred and twenty three dollars and forty five cents
B · 20260817 one hundred and twenty three dollars and forty five cents
C · eSpeak NG no voice
D · OneCore one hundred and twenty three dollars forty five cents
USD0.10
A · 103.12.8 ten United States cents
B · 20260817 ten United States cents
C · eSpeak NG no voice
D · OneCore ten cents
INR123.45
A · 103.12.8 one hundred and twenty three Indian rupee and forty five paise
B · 20260817 one hundred and twenty three Indian rupee and forty five paise
C · eSpeak NG no voice
D · OneCore one hundred and twenty three rupees and forty five paise
INR123
A · 103.12.8 one hundred and twenty three Indian rupees
B · 20260817 one hundred and twenty three Indian rupees
C · eSpeak NG no voice
D · OneCore one hundred and twenty three rupees
id-ID
USD123,45
A · 103.12.8 seratus dua puluh tiga dolar Amerika Serikat empat puluh lima sen
B · 20260817 seratus dua puluh tiga dolar Amerika Serikat empat puluh lima sen
C · eSpeak NG 'usd s@R2'atus_| d'uap,uluht'iga _k,oma_ '@mpat l'ima
D · OneCore seratus dua puluh tiga koma empat lima dolar Amerika
SGD123,45
A · 103.12.8 seratus dua puluh tiga dolar Singapura empat puluh lima sen
B · 20260817 seratus dua puluh tiga dolar Singapura empat puluh lima sen
C · eSpeak NG ,Esg,ed'e s@R2'atus_| d'uap,uluht'iga _k,oma_ '@mpat l'ima
D · OneCore seratus dua puluh tiga koma empat lima dolar Singapura
$123,45
A · 103.12.8 seratus dua puluh tiga dolar empat puluh lima sen
B · 20260817 seratus dua puluh tiga dolar empat puluh lima sen
C · eSpeak NG d'OlaR s@R2'atus_| d'uap,uluht'iga _k,oma_ '@mpat l'ima
D · OneCore seratus dua puluh tiga koma empat lima dolar
USD0,10
A · 103.12.8 nol dolar Amerika Serikat sepuluh sen
B · 20260817 nol koma satu nol dolar Amerika Serikat
C · eSpeak NG 'usd n'ol _k,oma_ s'atu n'ol
D · OneCore nol koma satu nol dolar Amerika
IDR123,45
A · 103.12.8 seratus dua puluh tiga rupiah empat puluh lima sen
B · 20260817 seratus dua puluh tiga koma empat lima rupiah
C · eSpeak NG 'idR s@R2'atus_| d'uap,uluht'iga _k,oma_ '@mpat l'ima
D · OneCore I D R seratus dua tiga empat puluh lima
IDR123
A · 103.12.8 seratus dua puluh tiga rupiah
B · 20260817 seratus dua puluh tiga rupiah
C · eSpeak NG 'idR s@R2'atus_| d'uap,uluht'iga
D · OneCore I D R seratus dua tiga
IDR123.45
A · 103.12.8 I D R seratus dua puluh tiga titik empat lima
B · 20260817 ider seratus dua puluh tiga titik empat lima
C · eSpeak NG 'idR s@R2'atus_| d'uap,uluht'iga t'iti? 'Empatp,uluhl'ima
D · OneCore I D R seratus dua tiga empat puluh lima
yue-HK
USD123.45
A · 103.12.8 一百二十三點四五美元 jat1 baak3 ji6 sap6 saam1 dim2 sei3 ng5 mei5 jyun4
B · 20260817 一百二十三點四五美元 jat1 baak3 ji6 sap6 saam1 dim2 sei3 ng5 mei5 jyun4
C · eSpeak NG (en)j,u:1,E1sd'i:1(yue)_| j,a1t_| baa3k_| j,i6sa6p_| s,aa7m_| sei3_| 'ng5_|
D · OneCore 一百二十三點四五美元 jat1 baak3 ji6 sap6 saam1 dim2 sei3 ng5 mei5 jyun4
SGD123.45
A · 103.12.8 一百二十三點四五新加坡元 jat1 baak3 ji6 sap6 saam1 dim2 sei3 ng5 san1 gaa1 bo1 jyun4
B · 20260817 一百二十三點四五新加坡元 jat1 baak3 ji6 sap6 saam1 dim2 sei3 ng5 san1 gaa1 bo1 jyun4
C · eSpeak NG (en),E1sdZ,i:1d'i:1(yue)_| j,a1t_| baa3k_| j,i6sa6p_| s,aa7m_| sei3_| 'ng5_|
D · OneCore S G D 一二三點四十五 S G D jat1 ji6 saam1 dim2 sei3 sap6 ng5
$123.45
A · 103.12.8 一百二十三點四五港元 jat1 baak3 ji6 sap6 saam1 dim2 sei3 ng5 gong2 jyun4
B · 20260817 一百二十三點四五港元 jat1 baak3 ji6 sap6 saam1 dim2 sei3 ng5 gong2 jyun4
C · eSpeak NG j,a1t_| baa3k_| j,i6sa6p_| s,aa7m_| sei3_| 'ng5_|
D · OneCore 一百二十三點四五元 jat1 baak3 ji6 sap6 saam1 dim2 sei3 ng5 jyun4
USD0.10
A · 103.12.8 零點一美元 ling4 dim2 jat1 mei5 jyun4
B · 20260817 零點一美元 ling4 dim2 jat1 mei5 jyun4
C · eSpeak NG (en)V1sdz'i@1roU1(yue)_| ja1t_| l'ing4_| _!_|
D · OneCore 零點一零美元 ling4 dim2 jat1 ling4 mei5 jyun4
HKD123.45
A · 103.12.8 一百二十三點四五港元 jat1 baak3 ji6 sap6 saam1 dim2 sei3 ng5 gong2 jyun4
B · 20260817 港幣一百二十三點四五元 gong2 bai6 jat1 baak3 ji6 sap6 saam1 dim2 sei3 ng5 jyun4
C · eSpeak NG (en),eI1tSk,eI1d'i:1(yue)_| j,a1t_| baa3k_| j,i6sa6p_| s,aa7m_| sei3_| 'ng5_|
D · OneCore 一百二十三點四五港元 jat1 baak3 ji6 sap6 saam1 dim2 sei3 ng5 gong2 jyun4
zh-CN
USD123.45
A · 103.12.8 一百二十三点四五美元 yī bǎi èr shí sān diǎn sì wǔ měi yuán
B · 20260817 一百二十三点四五美元 yī bǎi èr shí sān diǎn sì wǔ měi yuán
C · eSpeak NG (en)j,u:55,E55sd'i:55(cmn)_| j,i55pai21_|_| ,@r51s.i.35_|sa55n_| t'iE35n_|_| si[51_| w,u214_|
D · OneCore U S D 一二三点四十五 U S D yāo èr sān diǎn sì shí wǔ
SGD123.45
A · 103.12.8 一百二十三点四五新加坡元 yī bǎi èr shí sān diǎn sì wǔ xīn jiā pō yuán
B · 20260817 一百二十三点四五新加坡元 yī bǎi èr shí sān diǎn sì wǔ xīn jiā pō yuán
C · eSpeak NG (en),E55sdZ,i:55d'i:55(cmn)_| j,i55pai21_|_| ,@r51s.i.35_|sa55n_| t'iE35n_|_| si[51_| w,u214_|
D · OneCore S G D 一二三点四十五 S G D yāo èr sān diǎn sì shí wǔ
$123.45
A · 103.12.8 一百二十三点四五美元 yī bǎi èr shí sān diǎn sì wǔ měi yuán
B · 20260817 一百二十三点四五美元 yī bǎi èr shí sān diǎn sì wǔ měi yuán
C · eSpeak NG j,i55pai21_|_| ,@r51s.i.35_|sa55n_| t'iE35n_|_| si[51_| w,u214_|
D · OneCore 一百二十三点四五美元 yī bǎi èr shí sān diǎn sì wǔ měi yuán
USD0.10
A · 103.12.8 零点一美元 líng diǎn yī měi yuán
B · 20260817 零点一美元 líng diǎn yī měi yuán
C · eSpeak NG (en)V55sdz'i@55roU55(cmn)_| ji55l'i35N_!_|
D · OneCore U S D 零点十 U S D líng diǎn shí
CNY123.45
A · 103.12.8 一百二十三点四五元 yī bǎi èr shí sān diǎn sì wǔ yuán
B · 20260817 人民币一百二十三点四五元 rén mín bì yī bǎi èr shí sān diǎn sì wǔ yuán
C · eSpeak NG (en)s,i:55;,E55nw'aI55(cmn)_| j,i55pai21_|_| ,@r51s.i.35_|sa55n_| t'iE35n_|_| si[51_| w,u214_|
D · OneCore C N Y 一二三点四十五 C N Y yāo èr sān diǎn sì shí wǔ
zh-TW
USD123.45
A · 103.12.8 一百二十三點四五美元 yī bǎi èr shí sān diǎn sì wǔ měi yuán
B · 20260817 一百二十三點四五美元 yī bǎi èr shí sān diǎn sì wǔ měi yuán
C · eSpeak NG (en)j,u:55,E55sd'i:55(cmn)_| j,i55pai21_|_| ,@r51s.i.35_|sa55n_| t'iE35n_|_| si[51_| w,u214_|
D · OneCore 一百二十三點四五美元 yī bǎi èr shí sān diǎn sì wǔ měi yuán
SGD123.45
A · 103.12.8 一百二十三點四五新加坡元 yī bǎi èr shí sān diǎn sì wǔ xīn jiā pō yuán
B · 20260817 新币一百二十三點四五元 xīn bì yī bǎi èr shí sān diǎn sì wǔ yuán
C · eSpeak NG (en),E55sdZ,i:55d'i:55(cmn)_| j,i55pai21_|_| ,@r51s.i.35_|sa55n_| t'iE35n_|_| si[51_| w,u214_|
D · OneCore S G D 一二三點四十五 S G D yī èr sān diǎn sì shí wǔ
$123.45
A · 103.12.8 一百二十三點四五美元 yī bǎi èr shí sān diǎn sì wǔ měi yuán
B · 20260817 一百二十三點四五美元 yī bǎi èr shí sān diǎn sì wǔ měi yuán
C · eSpeak NG j,i55pai21_|_| ,@r51s.i.35_|sa55n_| t'iE35n_|_| si[51_| w,u214_|
D · OneCore 一百二十三點四五美元 yī bǎi èr shí sān diǎn sì wǔ měi yuán
USD0.10
A · 103.12.8 零點一美元 líng diǎn yī měi yuán
B · 20260817 零點一美元 líng diǎn yī měi yuán
C · eSpeak NG (en)V55sdz'i@55roU55(cmn)_| ji55l'i35N_!_|
D · OneCore 零點一零美元 líng diǎn yī líng měi yuán
TWD123.45
A · 103.12.8 一百二十三點四五新台幣 yī bǎi èr shí sān diǎn sì wǔ xīn tái bì
B · 20260817 新台幣一百二十三點四五元 xīn tái bì yī bǎi èr shí sān diǎn sì wǔ yuán
C · eSpeak NG (en)t,i:55d,V55b@L55j,u:55d'i:55(cmn)_| j,i55pai21_|_| ,@r51s.i.35_|sa55n_| t'iE35n_|_| si[51_| w,u214_|
D · OneCore T W D 一二三點四十五 T W D yī èr sān diǎn sì shí wǔ
th-TH
USD123.45
A · 103.12.8 หนึ่งร้อยยี่สิบสามดอลลาร์สี่สิบห้าเซ็นต์ nueng roi yi sip sam dollar si sip ha cent
B · 20260817 หนึ่งร้อยยี่สิบสามดอลลาร์สหรัฐสี่สิบห้าเซ็นต์ nueng roi yi sip sam dollar saharat si sip ha cent
C · eSpeak NG (en)j,u:,Esd'i:(th) n,u5e5ngs,o3ng_!sa5ms'i3ha_!
D · OneCore หนึ่งร้อยยี่สิบสามจุดสี่ห้าดอลลาร์สหรัฐ nueng roi yi sip sam chut si ha dollar saharat
SGD123.45
A · 103.12.8 หนึ่งร้อยยี่สิบสามดอลลาร์สิงคโปร์สี่สิบห้าเซ็นต์ nueng roi yi sip sam dollar singapore si sip ha cent
B · 20260817 หนึ่งร้อยยี่สิบสามดอลลาร์สิงคโปร์สี่สิบห้าเซ็นต์ nueng roi yi sip sam dollar singapore si sip ha cent
C · eSpeak NG (en),EsdZ,i:d'i:(th) n,u5e5ngs,o3ng_!sa5ms'i3ha_!
D · OneCore S G D หนึ่งสองสาสี่สิบห้า S G D nueng song sa si sip ha
$123.45
A · 103.12.8 หนึ่งร้อยยี่สิบสามดอลลาร์สี่สิบห้าเซ็นต์ nueng roi yi sip sam dollar si sip ha cent
B · 20260817 หนึ่งร้อยยี่สิบสามดอลลาร์สี่สิบห้าเซ็นต์ nueng roi yi sip sam dollar si sip ha cent
C · eSpeak NG n,u5e5ngs,o3ng_!sa5ms'i3ha_!
D · OneCore หนึ่งร้อยยี่สิบสามจุดสี่ห้าดอลลาร์ nueng roi yi sip sam chut si ha dollar
USD0.10
A · 103.12.8 สิบเซ็นต์ sip cent
B · 20260817 สิบเซ็นต์สหรัฐ sip cent saharat
C · eSpeak NG (en)j,u:,Esd'i:(th) s,u5nnu'e3ngsun_!
D · OneCore ศูนย์จุดหนึ่งศูนย์ดอลลาร์สหรัฐ sun chut nueng sun dollar saharat
THB123.45
A · 103.12.8 หนึ่งร้อยยี่สิบสามบาทสี่สิบห้าสตางค์ nueng roi yi sip sam baht si sip ha satang
B · 20260817 หนึ่งร้อยยี่สิบสามบาทสี่สิบห้าสตางค์ nueng roi yi sip sam baht si sip ha satang
C · eSpeak NG (en)t,i:;,eItSb'i:(th) n,u5e5ngs,o3ng_!sa5ms'i3ha_!
D · OneCore หนึ่งร้อยยี่สิบสามจุดสี่ห้าบาท nueng roi yi sip sam chut si ha baht
de-DE
USD123,45
A · 103.12.8 einhundertdreiundzwanzig US-Dollar und fünfundvierzig Cent
B · 20260817 einhundertdreiundzwanzig US-Dollar und fünfundvierzig Cent
C · eSpeak NG _!'Ust _'aInh'Und3t dr'aI _|Unttsv'antsIC _k,OmA: f'i:r f'ynf
D · OneCore einhundertdreiundzwanzig US-Dollar und fünfundvierzig Cent
SGD123,45
A · 103.12.8 einhundertdreiundzwanzig Singapur-Dollar und fünfundvierzig Cent
B · 20260817 einhundertdreiundzwanzig Singapur-Dollar und fünfundvierzig Cent
C · eSpeak NG _!,Esg,e:d'e: _'aInh'Und3t dr'aI _|Unttsv'antsIC _k,OmA: f'i:r f'ynf
D · OneCore S G D einhundertdreiundzwanzig fünfundvierzig
$123,45
A · 103.12.8 einhundertdreiundzwanzig Dollar und fünfundvierzig Cent
B · 20260817 einhundertdreiundzwanzig Dollar und fünfundvierzig Cent
C · eSpeak NG dOl'A: _'aInh'Und3t dr'aI _|Unttsv'antsIC _k,OmA: f'i:r f'ynf
D · OneCore einhundertdreiundzwanzig Dollar und fünfundvierzig Cent
USD0,10
A · 103.12.8 null US-Dollar und zehn Cent
B · 20260817 zehn US-Cent
C · eSpeak NG _!'Ust n'Ul _kOmA: _'aIns n'Ul
D · OneCore zehn amerikanische Cent
EUR123,45
A · 103.12.8 einhundertdreiundzwanzig Euro und fünfundvierzig Cent
B · 20260817 einhundertdreiundzwanzig Euro und fünfundvierzig Cent
C · eSpeak NG _!'OYr _'aInh'Und3t dr'aI _|Unttsv'antsIC _k,OmA: f'i:r f'ynf
D · OneCore einhundertdreiundzwanzig Euro und fünfundvierzig Cent
EUR123.45
A · 103.12.8 einhundertdreiundzwanzig Euro und fünfundvierzig Cent
B · 20260817 einhundertdreiundzwanzig Euro und fünfundvierzig Cent
C · eSpeak NG _!'OYr _'aInh'Und3t dr'aI _|Unttsv'antsIC p,UNkt f'ynf _|Untf'IrtsIC
D · OneCore einhundertdreiundzwanzig Euro und fünfundvierzig Cent
fr-FR
USD123,45
A · 103.12.8 cent vingt-trois dollars américains et quarante-cinq cents
B · 20260817 cent vingt-trois dollars américains et quarante-cinq cents
C · eSpeak NG 'yzd sA~ vE~ttrwa virgyl karA~ts'E~k
D · OneCore cent vingt-trois virgule quarante-cinq dollars américains
SGD123,45
A · 103.12.8 cent vingt-trois dollars de Singapour et quarante-cinq cents
B · 20260817 cent vingt-trois dollars de Singapour et quarante-cinq cents
C · eSpeak NG ,EsZ,ed'e sA~ vE~ttrwa virgyl karA~ts'E~k
D · OneCore S G D cent vingt-trois virgule quarante-cinq
$123,45
A · 103.12.8 cent vingt-trois dollars et quarante-cinq cents
B · 20260817 cent vingt-trois dollars et quarante-cinq cents
C · eSpeak NG dOl'ar sA~ vE~ttrwa virgyl karA~ts'E~k
D · OneCore cent vingt-trois dollars quarante-cinq
USD0,10
A · 103.12.8 zéro dollar américain dix cents
B · 20260817 zéro dollar américain dix cents
C · eSpeak NG 'yzd zero virgyl d'is
D · OneCore dix cents américains
EUR123,45
A · 103.12.8 cent vingt-trois euros et quarante-cinq centimes
B · 20260817 cent vingt-trois euros et quarante-cinq centimes
C · eSpeak NG 'Wr sA~ vE~ttrwa virgyl karA~ts'E~k
D · OneCore cent vingt-trois Euro quarante-cinq
EUR123.45
A · 103.12.8 cent vingt-trois euros et quarante-cinq centimes
B · 20260817 cent vingt-trois euros et quarante-cinq centimes
C · eSpeak NG 'Wr sA~ vE~ttrwa _|pwE~ karA~ts'E~k
D · OneCore Euro cent vingt-trois points quarante-cinq
es-ES
USD123,45
A · 103.12.8 ciento veintitrés dólares estadounidenses con cuarenta y cinco centavos
B · 20260817 ciento veintitrés dólares estadounidenses con cuarenta y cinco centavos
C · eSpeak NG 'usd Tj'ento B,eIntit**'es kom,a kwa**,EntaiT'inko
D · OneCore ciento veintitrés coma cuarenta y cinco dólares americanos
SGD123,45
A · 103.12.8 ciento veintitrés dólares singapurenses con cuarenta y cinco centavos
B · 20260817 ciento veintitrés dólares singapurenses con cuarenta y cinco centavos
C · eSpeak NG ,esex,eD'e Tj'ento B,eIntit**'es kom,a kwa**,EntaiT'inko
D · OneCore S G D ciento veintitrés coma cuarenta y cinco
$123,45
A · 103.12.8 ciento veintitrés dólares con cuarenta y cinco centavos
B · 20260817 ciento veintitrés dólares con cuarenta y cinco centavos
C · eSpeak NG d'olar Tj'ento B,eIntit**'es kom,a kwa**,EntaiT'inko
D · OneCore ciento veintitrés coma cuarenta y cinco dólares
USD0,10
A · 103.12.8 diez centavos
B · 20260817 diez centavos
C · eSpeak NG 'usd T'e**o k,oma Dj'ET
D · OneCore cero coma un dólares americanos
EUR123,45
A · 103.12.8 ciento veintitrés euro con cuarenta y cinco céntimos
B · 20260817 ciento veintitrés euro con cuarenta y cinco céntimos
C · eSpeak NG 'eUr Tj'ento B,eIntit**'es kom,a kwa**,EntaiT'inko
D · OneCore ciento veintitrés coma cuarenta y cinco euros
EUR123.45
A · 103.12.8 ciento veintitrés euro con cuarenta y cinco céntimos
B · 20260817 ciento veintitrés euro con cuarenta y cinco céntimos
C · eSpeak NG 'eUr Tj'ento B,eIntit**'es punto kwa**,EntaiT'inko
D · OneCore eur ciento veintitrés punto cuarenta y cinco
it-IT
USD123,45
A · 103.12.8 centoventitré dollari americani e quarantacinque cents
B · 20260817 centoventitré dollari americani e quarantacinque centesimi
C · eSpeak NG 'uzd tS'Ento ventit@-*'e v'i;@-*gola kUa*'a:ntatS'inkwe
D · OneCore centoventitré virgola quarantacinque dollari usa
SGD123,45
A · 103.12.8 centoventitré dollari di Singapore e quarantacinque cents
B · 20260817 centoventitré dollari di Singapore e quarantacinque cents
C · eSpeak NG ,EssedZ,id['i: tS'Ento ventit@-*'e v'i;@-*gola kUa*'a:ntatS'inkwe
D · OneCore S G D centoventitré virgola quarantacinque
$123,45
A · 103.12.8 centoventitré dollari e quarantacinque cents
B · 20260817 centoventitré dollari e quarantacinque centesimi
C · eSpeak NG d'OllaRI tS'Ento ventit@-*'e v'i;@-*gola kUa*'a:ntatS'inkwe
D · OneCore centoventitré virgola quarantacinque dollari
USD0,10
A · 103.12.8 dieci cents
B · 20260817 dieci centesimi
C · eSpeak NG 'uzd dz'E*o v'i;@-*gola dj'EtSI
D · OneCore zero virgola dieci dollari usa
EUR123,45
A · 103.12.8 centoventitré euro e quarantacinque centesimi
B · 20260817 centoventitré euro e quarantacinque centesimi
C · eSpeak NG 'EU@-* tS'Ento ventit@-*'e v'i;@-*gola kUa*'a:ntatS'inkwe
D · OneCore centoventitré virgola quarantacinque euro
EUR123.45
A · 103.12.8 centoventitré euro e quarantacinque centesimi
B · 20260817 centoventitré euro e quarantacinque centesimi
C · eSpeak NG 'EU@-* tS'Ento ventit@-*'e pUnto kUa*'a:ntatS'inkwe
D · OneCore centoventitré virgola quarantacinque euro
nl-NL
USD123,45
A · 103.12.8 honderddrieëntwintig Amerikaanse dollar en vijfenveertig cent
B · 20260817 honderddrieëntwintig Amerikaanse dollar en vijfenveertig cent
C · eSpeak NG _!'8st h'Ond@r! tr,iEntv#,Int@x _k,Oma: v,ir v,EIf
D · OneCore honderd drieëntwintig komma vijfenveertig Amerikaanse dollar
SGD123,45
A · 103.12.8 honderddrieëntwintig Singaporese dollar en vijfenveertig cent
B · 20260817 honderddrieëntwintig Singaporese dollar en vijfenveertig cent
C · eSpeak NG _!,EsQ,e:d'e: h'Ond@r! tr,iEntv#,Int@x _k,Oma: v,ir v,EIf
D · OneCore S G D één twee drie vier vijf
$123,45
A · 103.12.8 honderddrieëntwintig dollar en vijfenveertig cent
B · 20260817 honderddrieëntwintig dollar en vijfenveertig cent
C · eSpeak NG d'OlAr h'Ond@r! tr,iEntv#,Int@x _k,Oma: v,ir v,EIf
D · OneCore honderd drieëntwintig komma vijfenveertig dollar
USD0,10
A · 103.12.8 tien cent
B · 20260817 tien cent
C · eSpeak NG _!'8st n'8l _k,Oma: ,e:n n,8l
D · OneCore nul komma één Amerikaanse dollar
EUR123,45
A · 103.12.8 honderddrieëntwintig euro en vijfenveertig cent
B · 20260817 honderddrieëntwintig euro en vijfenveertig cent
C · eSpeak NG _!'Y:r h'Ond@r! tr,iEntv#,Int@x _k,Oma: v,ir v,EIf
D · OneCore honderd drieëntwintig komma vijfenveertig euro
EUR123.45
A · 103.12.8 honderddrieëntwintig euro en vijfenveertig cent
B · 20260817 honderddrieëntwintig euro en vijfenveertig cent
C · eSpeak NG _!'Y:r h'Ond@r! tr,iEntv#,Int@x v'EIfEnf,I:rt@x
D · OneCore honderd drieëntwintig komma vijfenveertig euro
ja-JP
USD123.45
A · 103.12.8 百二十三点四五ドル hyaku nijū san ten yon go doru
B · 20260817 百二十三アメリカドル四五セント hyaku nijū san amerika doru yon go sento
C · eSpeak NG _:(en)j'u: _:'Es _:d'i:(ja) ,it_si_!C,aku n,i d_zu_!san_! s'i_! go_!
D · OneCore 百二十三点四五U Sドル hyaku nijū san ten yon go US doru
SGD123.45
A · 103.12.8 百二十三点四五シンガポールドル hyaku nijū san ten yon go shingapōru doru
B · 20260817 百二十三シンガポールドル四五セント hyaku nijū san shingapōru doru yon go sento
C · eSpeak NG _:(en)'Es _:dZ'i: _:d'i:(ja) ,it_si_!C,aku n,i d_zu_!san_! s'i_! go_!
D · OneCore 百二十三点四五シンガポールドル hyaku nijū san ten yon go shingapōru doru
$123.45
A · 103.12.8 百二十三点四五ドル hyaku nijū san ten yon go doru
B · 20260817 百二十三ドル四五セント hyaku nijū san doru yon go sento
C · eSpeak NG ,it_si_!C,aku n,i d_zu_!san_! s'i_! go_!
D · OneCore 百二十三点四五ドル hyaku nijū san ten yon go doru
USD0.10
A · 103.12.8 零点一零ドル rei ten ichi rei doru
B · 20260817 十セント jū sento
C · eSpeak NG _:(en)j'u: _:'Es _:d'i:(ja) r,ei_! ,it_si_! r'ei_!
D · OneCore 零点一零U Sドル rei ten ichi rei US doru
JPY123.45
A · 103.12.8 JP百二十三点四五 jei pī hyaku nijū san ten yon go
B · 20260817 百二十三日本円四五セン hyaku nijū san nihon en yon go sen
C · eSpeak NG _:(en)dZ'eI _:p'i: _:w'aI(ja) ,it_si_!C,aku n,i d_zu_!san_! s'i_! go_!
D · OneCore 百二十三点四五円 hyaku nijū san ten yon go en
JPY123
A · 103.12.8 JP百二十三 jei pī hyaku nijū san
B · 20260817 百二十三日本円 hyaku nijū san nihon en
C · eSpeak NG _:(en)dZ'eI _:p'i: _:w'aI(ja) ,it_si_!C,aku ni d_z'u_!san_!
D · OneCore 百二十三円 hyaku nijū san en
¥123
A · 103.12.8 百二十三円 hyaku nijū san en
B · 20260817 not captured
C · eSpeak NG ,it_si_!C,aku ni d_z'u_!san_!
D · OneCore 百二十三円 hyaku nijū san en
円123
A · 103.12.8 円百二十三 en hyaku nijū san
B · 20260817 not captured
C · eSpeak NG _:(en)tS'aIni:z(ja)l'et@ ,it_si_!C,aku ni d_z'u_!san_!
D · OneCore 円百二十三 en hyaku nijū san
JPY 123
A · 103.12.8 JP百二十三 jei pī hyaku nijū san
B · 20260817 not captured
C · eSpeak NG _:(en)dZ'eI _:p'i: _:w'aI(ja) ,it_si_!C,aku ni d_z'u_!san_!
D · OneCore 百二十三円 hyaku nijū san en
ta-IN
USD123.45
A · 103.12.8 U S D நூற்று இருபத்து மூன்று புள்ளி நான்கு ஐந்து yu es dee noorthu irubathu moonru pulli naangu ainthu
B · 20260817 U S D நூற்று இருபத்து மூன்று புள்ளி நான்கு ஐந்து yu es dee noorthu irubathu moonru pulli naangu ainthu
C · eSpeak NG j'ues d'olVz n'u:t.t.Ri ,irUb,VttUm,u:nRU _p,ul.l.i n,a:ngU ,aIndU
D · OneCore நூற்று இருபத்து மூன்று அமெரிக்க டாலர் நாற்பத்தைந்து சென்ட் noorthu irubathu moonru amerikka dollar naarpathaindhu sent
SGD123.45
A · 103.12.8 S G D நூற்று இருபத்து மூன்று புள்ளி நான்கு ஐந்து es jee dee noorthu irubathu moonru pulli naangu ainthu
B · 20260817 S G D நூற்று இருபத்து மூன்று புள்ளி நான்கு ஐந்து es jee dee noorthu irubathu moonru pulli naangu ainthu
C · eSpeak NG (en),EsdZ,i:d'i:(ta) n'u:t.t.Ri ,irUb,VttUm,u:nRU _p,ul.l.i n,a:ngU ,aIndU
D · OneCore S G D ஒன்று இரண்டு மூன்று நாற்பத்தைந்து es jee dee ondru irandu moonru naarpathaindhu
$123.45
A · 103.12.8 நூற்று இருபத்து மூன்று டாலர் நாற்பத்தைந்து சென்ட் noorthu irubathu moonru dollar naarpathaindhu sent
B · 20260817 நூற்று இருபத்து மூன்று டாலர் நாற்பத்தைந்து சென்ட் noorthu irubathu moonru dollar naarpathaindhu sent
C · eSpeak NG d'olV n'u:t.t.Ri ,irUb,VttUm,u:nRU _p,ul.l.i n,a:ngU ,aIndU
D · OneCore நூற்று இருபத்து மூன்று டாலர் நாற்பத்தைந்து சென்ட் noorthu irubathu moonru dollar naarpathaindhu sent
USD0.10
A · 103.12.8 U S D பூஜ்யம் புள்ளி ஒன்று பூஜ்யம் yu es dee poojyam pulli ondru poojyam
B · 20260817 U S D பூஜ்யம் புள்ளி ஒன்று பூஜ்யம் yu es dee poojyam pulli ondru poojyam
C · eSpeak NG j'ues d'olVz s'uz.ij,@m _pul.l.,i onR,U suz.,ij@m
D · OneCore பத்து சென்று pathu sendru
INR123.45
A · 103.12.8 I N R நூற்று இருபத்து மூன்று புள்ளி நான்கு ஐந்து ai en aar noorthu irubathu moonru pulli naangu ainthu
B · 20260817 I N R நூற்று இருபத்து மூன்று புள்ளி நான்கு ஐந்து ai en aar noorthu irubathu moonru pulli naangu ainthu
C · eSpeak NG 'IndI;Vn r'u:pi:z n'u:t.t.Ri ,irUb,VttUm,u:nRU _p,ul.l.i n,a:ngU ,aIndU
D · OneCore நூற்று இருபத்தி மூன்று ரூபாய் நாற்பத்தைந்து பைசா. noorthu irubathi moonru roobai naarpathaindhu paisa
INR123
A · 103.12.8 I N R நூற்று இருபத்து மூன்று ai en aar noorthu irubathu moonru
B · 20260817 I N R நூற்று இருபத்து மூன்று ai en aar noorthu irubathu moonru
C · eSpeak NG 'IndI;Vn r'u:pi:z n'u:t.t.Ri ,irUb,VttUm,u:nRU
D · OneCore நூற்று இருபத்து மூன்று ரூபாய் noorthu irubathu moonru roobai
₹123
A · 103.12.8 நூற்று இருபத்து மூன்று ரூபாய் noorthu irubathu moonru roobai
B · 20260817 not captured
C · eSpeak NG r'u:ba:j n'u:t.t.Ri ,irUb,VttUm,u:nRU
D · OneCore நூற்று இருபத்து மூன்று ரூபாய் noorthu irubathu moonru roobai
ரூபாய்123
A · 103.12.8 நூற்று இருபத்து மூன்று ரூபாய் noorthu irubathu moonru roobai
B · 20260817 not captured
C · eSpeak NG r'u:ba:j n'u:t.t.Ri ,irUb,VttUm,u:nRU
D · OneCore ரூபாய் பன்னிரண்டு மூன்று. roobai pannirendu moonru
INR 123
A · 103.12.8 I N R நூற்று இருபத்து மூன்று ai en aar noorthu irubathu moonru
B · 20260817 not captured
C · eSpeak NG 'IndI;Vn r'u:pi:z n'u:t.t.Ri ,irUb,VttUm,u:nRU
D · OneCore நூற்று இருபத்து மூன்று ரூபாய் noorthu irubathu moonru roobai

To check any of these readings yourself, the clips and their transcripts live in the repository under public/posts/screen-reader-currency-announcements/artifacts/, with one transcript file per engine content/posts/screen-reader-currency-announcements/. Play a clip, follow its read-out, and the per-form manifest next to the clips tells you which file belongs to which amount.

Try it yourself

The demo page behind this post is currency-announcements.html, a table of plain text nodes with no markup tricks. Open it in a screen reader and arrow through the samples.

For the browser side, inspect a page with a currency string in Chrome DevTools under Elements → Accessibility, or open the Accessibility pane and enable the full AX tree 5. The raw text node is there, for recognized and unrecognized currency alike.

To find which layer is speaking on your machine, look at the synthesizer, not the screen reader. In NVDA, Speech → Synthesizer names it; on Windows 10 and 11 that is OneCore by default 1. Microsoft OneCore and Microsoft Speech API version 5 carry the currency normalizer; eSpeak NG does not 2. Narrator uses the same Microsoft voice family 16. That setting is the difference between “U S dollars and forty five cents” and “point forty five”.

Footnotes

  1. NV Access, “Synthesizer options” (“NVDA uses Windows OneCore by default”), and NVDA’s source/synthDriverHandler.py (defaultSynthPriorityList = ["oneCore", "espeak", "silence"]) with source/config/configSpec.py (the synthesizer defaults to auto); eSpeak NG is the bundled fallback. ↩ ↩2 ↩3 ↩4

  2. NVDA PR #14266, “Fixed an issue where symbol repetition was not correctly reported by the synthesizer”, the OneCore/SAPI5/IBMTTS vs eSpeak distinction and the USD 4 → “four dollars” example. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9

  3. Ricky Onsman, “Money Talks! Formatting Currency in Web Content” (also at OZeWAI), the five-screen-reader comparison ($, US$, A$, AU$, AUD, THB, VND, £, €, ¥) and its per-reader recognition summary. The article does not record the synthesizers used. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13

  4. eSpeak NG, the open-source synthesizer that ships with NVDA; number/decimal/ordinal handling documented in docs/dictionary.md. ↩ ↩2 ↩3

  5. Accessibility-tree capture for this post, from Chrome on Linux, via the DevTools accessibility tree for the fixture public/posts/screen-reader-currency-announcements/currency-announcements.html. Reproduction notes are in this post’s AGENTS.md. Not re-verified on Windows; the Blink tree is the same source tree the Windows UIA bridge projects from. ↩ ↩2 ↩3

  6. Chromium accessibility source, third_party/blink/renderer/modules/accessibility/ax_node_object.cc, the code that computes accessible names/text on the way into the tree; no currency handling. Platform exposure (UIA/AT-SPI) is described in Chrome’s accessibility documentation. ↩ ↩2 ↩3

  7. NVDA issue #953, “NVDA pronouncing currency symbol before amount”, opened 2010-10-01; closed as synthesizer-specific. Source of the two maintainer quotes and of NVDA’s symbol-dictionary approach. (The reporter’s own wording for the $10.00 case reads “100 dollars”; the intended spoken form is “ten dollars”.) ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  8. Google TalkBack, the open-source Android screen reader; no currency-amount table in its source. The TALKBACK_PUNCTUATION_AND_SYMBOL map in SpeechCleanupUtils.java maps $, €, £, ¥ and more to spoken names. It is a symbol dictionary, not an amount normalizer. ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  9. Speech Recognition & Synthesis (Wikipedia) and its Play Store listing, the closed-source Google engine that TalkBack and other apps speak through by default. ↩ ↩2 ↩3 ↩4

  10. AVSpeechSynthesizer currency handling, Apple’s synthesizer reads $100.99 as “one hundred dollars and ninety nine cents” and 10cm as “ten centimeters”. ↩ ↩2

  11. Genesys, Supported say-as text normalization, per-locale currency lists ($/USD for en-US, $/AUD for en-AU, ¥/JPY for ja-JP). ↩ ↩2 ↩3 ↩4 ↩5

  12. Google Sparrowhawk, open-source Kestrel text normalization; classify/money.tsv ($→usd, £→gbp, €→eur) and verbalize/money.tsv (dollars/cents, pounds/pence, euros/cents). This is the framework’s bundled en_toy grammar, not a production table. ↩ ↩2 ↩3 ↩4

  13. NVIDIA NeMo text-processing, currency_major.tsv (~40 entries, no sgd) and currency_minor_plural.tsv (cents, pence). ↩ ↩2 ↩3

  14. WHATWG HTML issue #12856, proposal: a declarative <currency> element, the unimplemented markup that would let the accessibility tree carry the value as money. ↩ ↩2

  15. OneCore measurement: scripts/windows-tts-dump.ps1 renders the same locale × form matrix through the WinRT Windows.Media.SpeechSynthesis API — the one NVDA and Narrator use on Windows — and uploads the WAVs to scripts/win-tts-receiver.mjs; scripts/gtts-windows-import.sh converts them to mp3 and writes artifacts/RESULTS-gtts-windows-onecore.tsv, with the clips in artifacts/onecore/ and the hand-filled read-outs in transcripts.onecore.json. Host: Windows 10 Home 22H2, build 19045.2965; OneCore TTS engine 10.3.21207.0 (System32\Speech_OneCore\Engines\TTS). Cantonese (yue-HK) is spoken through Windows’ zh-HK voice, Microsoft Danny / Tracy, as Windows ships no yue-HK voice token. ↩

  16. Microsoft, “Chapter 7: Customizing Narrator”, which lists the Microsoft voices Narrator speaks through (David, Zira, Mark and the natural voices) and notes SAPI5 support. ↩ ↩2

  17. Microsoft, English Context tag definitions (SAPI 5.3), the Currency normalization category, $34.90 → “thirty four dollars and ninety cents”, and the statement that the implementation is left to the engine. ↩ ↩2 ↩3 ↩4

  18. NVIDIA NeMo text-processing, en/verbalizers/money.py, the verbalize half of the money pipeline, paired with taggers/money.py and the major/minor unit tables beside them. ↩ ↩2 ↩3

  19. Ritchie et al., “Unified Verbalization for Speech Recognition & Synthesis Across Languages” (Interspeech 2019), semiotic classes, currency as a class, and why currency expressions need element reordering. ↩ ↩2

  20. eSpeak NG, src/libespeak-ng/numbers.c, number translation with no currency or money handling. ↩ ↩2 ↩3

  21. LumenVox, TTS1 English text normalization, “a wide list … many of the ISO 4217 currency codes”. ↩

  22. OCP, TTS Verbalization Guide and Specification for en-US, a currency-code verbalization table (USD/EUR/GBP and their subunits). ↩

  23. Unicode CLDR, Currency Names, display names, symbols and plural forms for every ISO 4217 code; no subunit (“cent”/“pence”) data. ↩ ↩2

  24. ICU4X discussion #495, “Measurement and Currency feedback from Siri’s experience”, Apple’s spoken-currency requirements (independent major/minor units, region-sensitive names, subunit translation) and the note that ICU/CLDR have no subunit names. ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  25. Microsoft Recognizers-Text, Patterns/English/English-NumbersWithUnit.yaml, a currency model with the ISO 4217 list, including SGD (“Singapore dollar”) and the “Cent” fraction unit. ↩

  26. Android Accessibility Help, Text-to-speech output, TTS is a user-selectable engine; the default varies by device. ↩

  27. AOSP platform/external/svox (Pico TTS), an open-source Android TTS engine. ↩

  28. openradar rdar://46031623, AVSpeechSynthesizer reads $13.3 million as “13 dollars and 30 cents million”. ↩

  29. Stack Overflow, “Screen reader is not reading the price ($47.49) properly”, the aria-label and sr-only patterns for amounts, and the advice not to fight the default $-prefixed form. ↩ ↩2 ↩3 ↩4

  30. WAI-ARIA, aria-label, naming is only defined for roles that support it; aria-label on a role-less <span> is not reliably exposed, which is why the visually-hidden sentence above is the portable pattern. ↩

  31. eSpeak NG, dictsource/en_list, the only two currency-code entries: usd $abbrev $allcaps (line 683) and eur jU@ $only (line 574). USD is an abbreviation to be spelled and EUR a word to say; there is no dollar/cent amount handling. ↩

  32. Flite, lang/usenglish/us_text.c, us_tokentowords_one(), the “US money” branch emitting dollars/dollar and cents/cent. The usmoney regex it matches is defined in lang/usenglish/make_us_regexes as \$[0-9,]+(\.[0-9]+)?. ↩

  33. eSpeak NG, dictsource/en_list (line 126, _$ d0l3), dictsource/en_rules (line 7118, $ d0l3), and the usd $abbrev $allcaps abbreviation entry. There is no amount handling in src/libespeak-ng/numbers.c. ↩ ↩2

  34. num2words, num2words/base.py (CURRENCY_FORMS, to_currency) and num2words/lang_EU.py (the English/EU currency table). ↩

  35. Flite measurement: scripts/currency-flite-dump.sh runs each form through flite_cmu_us_slt -o none -pw (Flite flite-2.2-current, built-in US English voice); the committed output is artifacts/RESULTS-flite.txt. Requires Flite on PATH; set FLITE_BIN to point at a specific voice binary. ↩ ↩2

  36. BIS Triennial Central Bank Survey 2022, FX turnover, the ranking behind the top-20 list. ↩

  37. Reproducible measurement: scripts/currency-espeak-dump.sh prints the phoneme dump, and the committed output is artifacts/RESULTS-espeak.txt with the currency list at artifacts/currencies.txt. Requires espeak-ng on PATH. ↩

  38. NeMo measurement: scripts/nemo-currency-dump.sh builds a Podman image from scripts/nemo/Dockerfile (Python + pynini + nemo_text_processing) and runs scripts/nemo/langs.py, scripts/nemo/currency_tokens.py and scripts/nemo/coverage.py inside it, nothing is installed on the host. Committed output: artifacts/RESULTS-nemo-langs.txt, artifacts/RESULTS-nemo-currencies.txt and artifacts/RESULTS-nemo-coverage.tsv, with the readable matrix in RESULTS-nemo-coverage.md. ru is unavailable through the Normalizer wrapper (it requires non-deterministic mode), and fr has a money verbalizer but no data directory in the released package. ↩ ↩2 ↩3 ↩4

  39. Android measurement: scripts/android-tts-harness/ (a TtsActivity for the android-simple-webview app) and scripts/gtts-locale-dump.sh; the manifest is artifacts/RESULTS-gtts-locales.tsv, the clips are in artifacts/gtts/, and the hand-filled transcripts are content/posts/screen-reader-currency-announcements/transcripts.json. Engine: Google Speech Recognition & Synthesis (com.google.android.tts) 103.12.8, re-signed with a v2 signature to install on Android 15; device OnePlus 6T. A second build, googletts.google-speech-apk_20260817.01_p0.966249458 on a Pixel 9, is in artifacts/RESULTS-gtts-locales-20260817.tsv. The money schema is read from assets/voices/en-us-x-sfg/hmm/en_verbalize_spec.pb inside that APK. ↩ ↩2 ↩3

back