n2words 4.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +14 -12
  3. package/dist/am-ET.js +2 -2
  4. package/dist/am-ET.umd.js +2 -2
  5. package/dist/am-Latn-ET.js +2 -2
  6. package/dist/am-Latn-ET.umd.js +2 -2
  7. package/dist/ar-SA.js +2 -2
  8. package/dist/ar-SA.umd.js +2 -2
  9. package/dist/az-AZ.js +2 -2
  10. package/dist/az-AZ.umd.js +2 -2
  11. package/dist/bn-BD.js +2 -2
  12. package/dist/bn-BD.umd.js +2 -2
  13. package/dist/cs-CZ.js +2 -2
  14. package/dist/cs-CZ.umd.js +2 -2
  15. package/dist/da-DK.js +2 -2
  16. package/dist/da-DK.umd.js +2 -2
  17. package/dist/de-DE.js +2 -2
  18. package/dist/de-DE.umd.js +2 -2
  19. package/dist/el-GR.js +2 -2
  20. package/dist/el-GR.umd.js +2 -2
  21. package/dist/en-AU.js +2 -2
  22. package/dist/en-AU.umd.js +2 -2
  23. package/dist/en-BD.js +2 -2
  24. package/dist/en-BD.umd.js +2 -2
  25. package/dist/en-CA.js +2 -2
  26. package/dist/en-CA.umd.js +2 -2
  27. package/dist/en-GB.js +2 -2
  28. package/dist/en-GB.umd.js +2 -2
  29. package/dist/en-GH.js +2 -2
  30. package/dist/en-GH.umd.js +2 -2
  31. package/dist/en-IE.js +2 -2
  32. package/dist/en-IE.umd.js +2 -2
  33. package/dist/en-IN.js +2 -2
  34. package/dist/en-IN.umd.js +2 -2
  35. package/dist/en-KE.js +2 -2
  36. package/dist/en-KE.umd.js +2 -2
  37. package/dist/en-MY.js +2 -2
  38. package/dist/en-MY.umd.js +2 -2
  39. package/dist/en-NG.js +2 -2
  40. package/dist/en-NG.umd.js +2 -2
  41. package/dist/en-NZ.js +2 -2
  42. package/dist/en-NZ.umd.js +2 -2
  43. package/dist/en-PH.js +2 -2
  44. package/dist/en-PH.umd.js +2 -2
  45. package/dist/en-PK.js +2 -2
  46. package/dist/en-PK.umd.js +2 -2
  47. package/dist/en-SG.js +2 -2
  48. package/dist/en-SG.umd.js +2 -2
  49. package/dist/en-US.js +2 -2
  50. package/dist/en-US.umd.js +2 -2
  51. package/dist/en-ZA.js +2 -2
  52. package/dist/en-ZA.umd.js +2 -2
  53. package/dist/es-ES.js +2 -2
  54. package/dist/es-ES.umd.js +2 -2
  55. package/dist/es-MX.js +2 -2
  56. package/dist/es-MX.umd.js +2 -2
  57. package/dist/es-US.js +2 -2
  58. package/dist/es-US.umd.js +2 -2
  59. package/dist/fa-IR.js +2 -2
  60. package/dist/fa-IR.umd.js +2 -2
  61. package/dist/fi-FI.js +2 -2
  62. package/dist/fi-FI.umd.js +2 -2
  63. package/dist/fil-PH.js +2 -2
  64. package/dist/fil-PH.umd.js +2 -2
  65. package/dist/fr-BE.js +2 -2
  66. package/dist/fr-BE.umd.js +2 -2
  67. package/dist/fr-FR.js +2 -2
  68. package/dist/fr-FR.umd.js +2 -2
  69. package/dist/gu-IN.js +2 -2
  70. package/dist/gu-IN.umd.js +2 -2
  71. package/dist/ha-NG.js +2 -2
  72. package/dist/ha-NG.umd.js +2 -2
  73. package/dist/hbo-IL.js +2 -2
  74. package/dist/hbo-IL.umd.js +2 -2
  75. package/dist/he-IL.js +2 -2
  76. package/dist/he-IL.umd.js +2 -2
  77. package/dist/hi-IN.js +2 -2
  78. package/dist/hi-IN.umd.js +2 -2
  79. package/dist/hr-HR.js +2 -2
  80. package/dist/hr-HR.umd.js +2 -2
  81. package/dist/hu-HU.js +2 -2
  82. package/dist/hu-HU.umd.js +2 -2
  83. package/dist/id-ID.js +2 -2
  84. package/dist/id-ID.umd.js +2 -2
  85. package/dist/it-IT.js +2 -2
  86. package/dist/it-IT.umd.js +2 -2
  87. package/dist/ja-JP.js +2 -2
  88. package/dist/ja-JP.umd.js +2 -2
  89. package/dist/ka-GE.js +2 -2
  90. package/dist/ka-GE.umd.js +2 -2
  91. package/dist/kn-IN.js +2 -2
  92. package/dist/kn-IN.umd.js +2 -2
  93. package/dist/ko-KR.js +2 -2
  94. package/dist/ko-KR.umd.js +2 -2
  95. package/dist/lt-LT.js +2 -2
  96. package/dist/lt-LT.umd.js +2 -2
  97. package/dist/lv-LV.js +2 -2
  98. package/dist/lv-LV.umd.js +2 -2
  99. package/dist/mr-IN.js +2 -2
  100. package/dist/mr-IN.umd.js +2 -2
  101. package/dist/ms-MY.js +2 -2
  102. package/dist/ms-MY.umd.js +2 -2
  103. package/dist/nb-NO.js +2 -2
  104. package/dist/nb-NO.umd.js +2 -2
  105. package/dist/nl-NL.js +2 -2
  106. package/dist/nl-NL.umd.js +2 -2
  107. package/dist/pa-IN.js +2 -2
  108. package/dist/pa-IN.umd.js +2 -2
  109. package/dist/pl-PL.js +2 -2
  110. package/dist/pl-PL.umd.js +2 -2
  111. package/dist/pt-BR.js +2 -0
  112. package/dist/pt-BR.umd.js +2 -0
  113. package/dist/pt-PT.js +2 -2
  114. package/dist/pt-PT.umd.js +2 -2
  115. package/dist/ro-RO.js +2 -2
  116. package/dist/ro-RO.umd.js +2 -2
  117. package/dist/ru-RU.js +2 -2
  118. package/dist/ru-RU.umd.js +2 -2
  119. package/dist/sr-Cyrl-RS.js +2 -2
  120. package/dist/sr-Cyrl-RS.umd.js +2 -2
  121. package/dist/sr-Latn-RS.js +2 -2
  122. package/dist/sr-Latn-RS.umd.js +2 -2
  123. package/dist/sv-SE.js +2 -2
  124. package/dist/sv-SE.umd.js +2 -2
  125. package/dist/sw-KE.js +2 -2
  126. package/dist/sw-KE.umd.js +2 -2
  127. package/dist/ta-IN.js +2 -2
  128. package/dist/ta-IN.umd.js +2 -2
  129. package/dist/te-IN.js +2 -2
  130. package/dist/te-IN.umd.js +2 -2
  131. package/dist/th-TH.js +2 -2
  132. package/dist/th-TH.umd.js +2 -2
  133. package/dist/tr-TR.js +2 -2
  134. package/dist/tr-TR.umd.js +2 -2
  135. package/dist/uk-UA.js +2 -2
  136. package/dist/uk-UA.umd.js +2 -2
  137. package/dist/ur-PK.js +2 -2
  138. package/dist/ur-PK.umd.js +2 -2
  139. package/dist/vi-VN.js +2 -2
  140. package/dist/vi-VN.umd.js +2 -2
  141. package/dist/yo-NG.js +2 -2
  142. package/dist/yo-NG.umd.js +2 -2
  143. package/dist/zh-Hans-CN.js +2 -2
  144. package/dist/zh-Hans-CN.umd.js +2 -2
  145. package/dist/zh-Hant-TW.js +2 -2
  146. package/dist/zh-Hant-TW.umd.js +2 -2
  147. package/package.json +53 -36
  148. package/src/am-ET.d.ts +3 -5
  149. package/src/am-ET.js +41 -16
  150. package/src/am-Latn-ET.d.ts +3 -5
  151. package/src/am-Latn-ET.js +45 -16
  152. package/src/ar-SA.d.ts +44 -18
  153. package/src/ar-SA.js +93 -40
  154. package/src/az-AZ.d.ts +3 -5
  155. package/src/az-AZ.js +58 -20
  156. package/src/bn-BD.d.ts +3 -5
  157. package/src/bn-BD.js +32 -16
  158. package/src/cs-CZ.d.ts +3 -6
  159. package/src/cs-CZ.js +66 -42
  160. package/src/da-DK.d.ts +3 -6
  161. package/src/da-DK.js +53 -48
  162. package/src/de-DE.d.ts +17 -11
  163. package/src/de-DE.js +88 -57
  164. package/src/el-GR.d.ts +3 -6
  165. package/src/el-GR.js +45 -32
  166. package/src/en-AU.d.ts +17 -11
  167. package/src/en-AU.js +56 -41
  168. package/src/en-BD.d.ts +17 -11
  169. package/src/en-BD.js +60 -41
  170. package/src/en-CA.d.ts +36 -18
  171. package/src/en-CA.js +67 -46
  172. package/src/en-GB.d.ts +17 -11
  173. package/src/en-GB.js +56 -41
  174. package/src/en-GH.d.ts +32 -3
  175. package/src/en-GH.js +104 -26
  176. package/src/en-IE.d.ts +17 -11
  177. package/src/en-IE.js +56 -41
  178. package/src/en-IN.d.ts +17 -11
  179. package/src/en-IN.js +60 -41
  180. package/src/en-KE.d.ts +28 -3
  181. package/src/en-KE.js +93 -26
  182. package/src/en-MY.d.ts +26 -3
  183. package/src/en-MY.js +91 -26
  184. package/src/en-NG.d.ts +17 -11
  185. package/src/en-NG.js +56 -41
  186. package/src/en-NZ.d.ts +32 -3
  187. package/src/en-NZ.js +85 -31
  188. package/src/en-PH.d.ts +32 -3
  189. package/src/en-PH.js +97 -26
  190. package/src/en-PK.d.ts +17 -11
  191. package/src/en-PK.js +60 -41
  192. package/src/en-SG.d.ts +28 -3
  193. package/src/en-SG.js +93 -26
  194. package/src/en-US.d.ts +36 -18
  195. package/src/en-US.js +70 -47
  196. package/src/en-ZA.d.ts +17 -11
  197. package/src/en-ZA.js +56 -41
  198. package/src/es-ES.d.ts +53 -21
  199. package/src/es-ES.js +104 -56
  200. package/src/es-MX.d.ts +53 -21
  201. package/src/es-MX.js +104 -56
  202. package/src/es-US.d.ts +53 -21
  203. package/src/es-US.js +92 -51
  204. package/src/fa-IR.d.ts +3 -5
  205. package/src/fa-IR.js +28 -13
  206. package/src/fi-FI.d.ts +3 -6
  207. package/src/fi-FI.js +47 -29
  208. package/src/fil-PH.d.ts +3 -5
  209. package/src/fil-PH.js +61 -28
  210. package/src/fr-BE.d.ts +31 -15
  211. package/src/fr-BE.js +128 -57
  212. package/src/fr-FR.d.ts +31 -16
  213. package/src/fr-FR.js +97 -60
  214. package/src/gu-IN.d.ts +3 -5
  215. package/src/gu-IN.js +31 -16
  216. package/src/ha-NG.d.ts +3 -5
  217. package/src/ha-NG.js +55 -27
  218. package/src/hbo-IL.d.ts +26 -12
  219. package/src/hbo-IL.js +92 -51
  220. package/src/he-IL.d.ts +17 -10
  221. package/src/he-IL.js +92 -50
  222. package/src/hi-IN.d.ts +3 -5
  223. package/src/hi-IN.js +30 -17
  224. package/src/hr-HR.d.ts +21 -10
  225. package/src/hr-HR.js +89 -33
  226. package/src/hu-HU.d.ts +3 -5
  227. package/src/hu-HU.js +57 -23
  228. package/src/id-ID.d.ts +3 -5
  229. package/src/id-ID.js +56 -23
  230. package/src/it-IT.d.ts +17 -11
  231. package/src/it-IT.js +74 -43
  232. package/src/ja-JP.d.ts +3 -6
  233. package/src/ja-JP.js +39 -26
  234. package/src/ka-GE.d.ts +3 -6
  235. package/src/ka-GE.js +38 -26
  236. package/src/kn-IN.d.ts +3 -5
  237. package/src/kn-IN.js +31 -16
  238. package/src/ko-KR.d.ts +3 -6
  239. package/src/ko-KR.js +34 -26
  240. package/src/lt-LT.d.ts +21 -11
  241. package/src/lt-LT.js +64 -42
  242. package/src/lv-LV.d.ts +21 -11
  243. package/src/lv-LV.js +79 -51
  244. package/src/mr-IN.d.ts +3 -5
  245. package/src/mr-IN.js +31 -16
  246. package/src/ms-MY.d.ts +3 -5
  247. package/src/ms-MY.js +58 -24
  248. package/src/nb-NO.d.ts +3 -6
  249. package/src/nb-NO.js +54 -34
  250. package/src/nl-NL.d.ts +41 -20
  251. package/src/nl-NL.js +111 -69
  252. package/src/pa-IN.d.ts +3 -5
  253. package/src/pa-IN.js +32 -16
  254. package/src/pl-PL.d.ts +21 -11
  255. package/src/pl-PL.js +69 -45
  256. package/src/pt-BR.d.ts +42 -0
  257. package/src/pt-BR.js +574 -0
  258. package/src/pt-PT.d.ts +17 -11
  259. package/src/pt-PT.js +80 -48
  260. package/src/ro-RO.d.ts +21 -11
  261. package/src/ro-RO.js +77 -39
  262. package/src/ru-RU.d.ts +35 -15
  263. package/src/ru-RU.js +100 -38
  264. package/src/sr-Cyrl-RS.d.ts +35 -15
  265. package/src/sr-Cyrl-RS.js +100 -38
  266. package/src/sr-Latn-RS.d.ts +35 -15
  267. package/src/sr-Latn-RS.js +100 -38
  268. package/src/sv-SE.d.ts +3 -6
  269. package/src/sv-SE.js +53 -34
  270. package/src/sw-KE.d.ts +3 -5
  271. package/src/sw-KE.js +50 -20
  272. package/src/ta-IN.d.ts +3 -5
  273. package/src/ta-IN.js +29 -17
  274. package/src/te-IN.d.ts +3 -5
  275. package/src/te-IN.js +31 -16
  276. package/src/th-TH.d.ts +3 -5
  277. package/src/th-TH.js +42 -19
  278. package/src/tr-TR.d.ts +17 -11
  279. package/src/tr-TR.js +63 -37
  280. package/src/uk-UA.d.ts +21 -10
  281. package/src/uk-UA.js +89 -33
  282. package/src/ur-PK.d.ts +3 -5
  283. package/src/ur-PK.js +32 -16
  284. package/src/utils/check-max.d.ts +26 -0
  285. package/src/utils/check-max.js +33 -0
  286. package/src/utils/expand-scientific.d.ts +0 -4
  287. package/src/utils/expand-scientific.js +7 -9
  288. package/src/utils/is-plain-object.d.ts +3 -4
  289. package/src/utils/is-plain-object.js +3 -4
  290. package/src/utils/parse-cardinal.d.ts +1 -2
  291. package/src/utils/parse-cardinal.js +12 -9
  292. package/src/utils/parse-currency.d.ts +1 -2
  293. package/src/utils/parse-currency.js +9 -11
  294. package/src/utils/parse-ordinal.d.ts +0 -1
  295. package/src/utils/parse-ordinal.js +9 -10
  296. package/src/utils/resolve-options.d.ts +17 -0
  297. package/src/utils/resolve-options.js +56 -0
  298. package/src/utils/scale.d.ts +49 -0
  299. package/src/utils/scale.js +65 -0
  300. package/src/vi-VN.d.ts +3 -6
  301. package/src/vi-VN.js +41 -28
  302. package/src/yo-NG.d.ts +3 -5
  303. package/src/yo-NG.js +49 -33
  304. package/src/zh-Hans-CN.d.ts +45 -20
  305. package/src/zh-Hans-CN.js +84 -31
  306. package/src/zh-Hant-TW.d.ts +45 -20
  307. package/src/zh-Hant-TW.js +85 -34
  308. package/src/utils/validate-options.d.ts +0 -8
  309. package/src/utils/validate-options.js +0 -16
package/src/en-SG.js CHANGED
@@ -20,7 +20,9 @@
20
20
  import { parseCardinalValue } from './utils/parse-cardinal.js'
21
21
  import { parseCurrencyValue } from './utils/parse-currency.js'
22
22
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
23
- import { validateOptions } from './utils/validate-options.js'
23
+ import { checkMax } from './utils/check-max.js'
24
+ import { western } from './utils/scale.js'
25
+ import { resolveOptions } from './utils/resolve-options.js'
24
26
 
25
27
  // ============================================================================
26
28
  // Vocabulary (module-level constants)
@@ -35,9 +37,13 @@ const SCALES = [
35
37
  'quintillion', 'sextillion', 'septillion', 'octillion', 'nonillion',
36
38
  'decillion', 'undecillion', 'duodecillion', 'tredecillion', 'quattuordecillion',
37
39
  'quindecillion', 'sexdecillion', 'septendecillion', 'octodecillion', 'novemdecillion',
38
- 'vigintillion'
40
+ 'vigintillion',
39
41
  ]
40
42
 
43
+ export const cardinalMax = western(SCALES.length)
44
+ export const ordinalMax = western(SCALES.length)
45
+ export const currencyMax = western(SCALES.length)
46
+
41
47
  const HUNDRED = 'hundred'
42
48
  const ZERO = 'zero'
43
49
  const NEGATIVE = 'minus'
@@ -60,7 +66,11 @@ const CENTS = 'cents'
60
66
 
61
67
  const segmentResult = { word: '', hasHundred: false }
62
68
 
63
- function buildSegment (n) {
69
+ /**
70
+ * @param {number} n - Number 0-999
71
+ * @returns {{ word: string, hasHundred: boolean }} The segment words and whether it contains a hundreds part
72
+ */
73
+ function buildSegment(n) {
64
74
  if (n === 0) {
65
75
  segmentResult.word = ''
66
76
  segmentResult.hasHundred = false
@@ -74,20 +84,24 @@ function buildSegment (n) {
74
84
  let tensOnes = ''
75
85
  if (tens === 1) {
76
86
  tensOnes = TEENS[ones]
77
- } else if (tens >= 2) {
87
+ }
88
+ else if (tens >= 2) {
78
89
  tensOnes = ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
79
- } else if (ones > 0) {
90
+ }
91
+ else if (ones > 0) {
80
92
  tensOnes = ONES[ones]
81
93
  }
82
94
 
83
95
  if (hundreds > 0) {
84
96
  if (tensOnes) {
85
97
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED + ' and ' + tensOnes
86
- } else {
98
+ }
99
+ else {
87
100
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED
88
101
  }
89
102
  segmentResult.hasHundred = true
90
- } else {
103
+ }
104
+ else {
91
105
  segmentResult.word = tensOnes
92
106
  segmentResult.hasHundred = false
93
107
  }
@@ -99,7 +113,11 @@ function buildSegment (n) {
99
113
  // Conversion Functions
100
114
  // ============================================================================
101
115
 
102
- function integerToWords (n) {
116
+ /**
117
+ * @param {bigint} n - Non-negative integer to convert
118
+ * @returns {string} English words
119
+ */
120
+ function integerToWords(n) {
103
121
  if (n === 0n) return ZERO
104
122
 
105
123
  if (n < 1000n) {
@@ -124,7 +142,11 @@ function integerToWords (n) {
124
142
  return buildLargeNumberWords(n)
125
143
  }
126
144
 
127
- function buildLargeNumberWords (n) {
145
+ /**
146
+ * @param {bigint} n - Number >= 1,000,000
147
+ * @returns {string} English words
148
+ */
149
+ function buildLargeNumberWords(n) {
128
150
  const segments = []
129
151
  let temp = n
130
152
  while (temp > 0n) {
@@ -160,7 +182,8 @@ function buildLargeNumberWords (n) {
160
182
  if (i > 0) {
161
183
  result += ' ' + SCALES[i - 1]
162
184
  prevWasScale = true
163
- } else {
185
+ }
186
+ else {
164
187
  prevWasScale = false
165
188
  }
166
189
  }
@@ -168,7 +191,11 @@ function buildLargeNumberWords (n) {
168
191
  return result
169
192
  }
170
193
 
171
- function decimalPartToWords (decimalPart) {
194
+ /**
195
+ * @param {string} decimalPart - Decimal digits (without the point)
196
+ * @returns {string} English words for decimal part
197
+ */
198
+ function decimalPartToWords(decimalPart) {
172
199
  let result = ''
173
200
 
174
201
  let i = 0
@@ -189,14 +216,16 @@ function decimalPartToWords (decimalPart) {
189
216
 
190
217
  /**
191
218
  * Converts a numeric value to Singaporean English words.
192
- *
193
219
  * @param {number | string | bigint} value - The numeric value to convert
194
220
  * @returns {string} The number in English words
195
221
  * @throws {TypeError} If value is not a valid numeric type
196
222
  * @throws {Error} If value is not a valid number format
197
223
  */
198
- function toCardinal (value) {
224
+ function toCardinal(value) {
199
225
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
226
+ // Both the integer part and the decimal's significant digits are spelled via
227
+ // the scale builder, so both must clear the ceiling.
228
+ checkMax(integerPart, cardinalMax, decimalPart)
200
229
 
201
230
  let result = ''
202
231
 
@@ -217,7 +246,11 @@ function toCardinal (value) {
217
246
  // ORDINAL
218
247
  // ============================================================================
219
248
 
220
- function buildOrdinalSegment (n) {
249
+ /**
250
+ * @param {number} n - Number 0-999
251
+ * @returns {string} Ordinal words for this segment
252
+ */
253
+ function buildOrdinalSegment(n) {
221
254
  const ones = n % 10
222
255
  const tens = Math.trunc(n / 10) % 10
223
256
  const hundreds = Math.trunc(n / 100)
@@ -225,20 +258,24 @@ function buildOrdinalSegment (n) {
225
258
  let tensOnesOrdinal = ''
226
259
  if (tens === 1) {
227
260
  tensOnesOrdinal = ORDINAL_TEENS[ones]
228
- } else if (tens >= 2) {
261
+ }
262
+ else if (tens >= 2) {
229
263
  if (ones > 0) {
230
264
  tensOnesOrdinal = TENS[tens] + '-' + ORDINAL_ONES[ones]
231
- } else {
265
+ }
266
+ else {
232
267
  tensOnesOrdinal = ORDINAL_TENS[tens]
233
268
  }
234
- } else if (ones > 0) {
269
+ }
270
+ else if (ones > 0) {
235
271
  tensOnesOrdinal = ORDINAL_ONES[ones]
236
272
  }
237
273
 
238
274
  if (hundreds > 0) {
239
275
  if (tensOnesOrdinal) {
240
276
  return ONES[hundreds] + ' ' + HUNDRED + ' ' + tensOnesOrdinal
241
- } else {
277
+ }
278
+ else {
242
279
  return ONES[hundreds] + ' hundredth'
243
280
  }
244
281
  }
@@ -246,7 +283,11 @@ function buildOrdinalSegment (n) {
246
283
  return tensOnesOrdinal
247
284
  }
248
285
 
249
- function integerToOrdinal (n) {
286
+ /**
287
+ * @param {bigint} n - Positive integer to convert
288
+ * @returns {string} Ordinal English words
289
+ */
290
+ function integerToOrdinal(n) {
250
291
  if (n < 1000n) {
251
292
  return buildOrdinalSegment(Number(n))
252
293
  }
@@ -266,7 +307,11 @@ function integerToOrdinal (n) {
266
307
  return buildLargeOrdinal(n)
267
308
  }
268
309
 
269
- function buildLargeOrdinal (n) {
310
+ /**
311
+ * @param {bigint} n - Number >= 1,000,000
312
+ * @returns {string} Ordinal English words
313
+ */
314
+ function buildLargeOrdinal(n) {
270
315
  const segments = []
271
316
  let temp = n
272
317
  while (temp > 0n) {
@@ -295,10 +340,12 @@ function buildLargeOrdinal (n) {
295
340
  if (isLowestSegment) {
296
341
  if (i === 0) {
297
342
  result += buildOrdinalSegment(segment)
298
- } else {
343
+ }
344
+ else {
299
345
  result += buildSegment(segment).word + ' ' + SCALES[i - 1] + 'th'
300
346
  }
301
- } else {
347
+ }
348
+ else {
302
349
  result += buildSegment(segment).word
303
350
  if (i > 0) {
304
351
  result += ' ' + SCALES[i - 1]
@@ -309,8 +356,14 @@ function buildLargeOrdinal (n) {
309
356
  return result
310
357
  }
311
358
 
312
- function toOrdinal (value) {
359
+ /**
360
+ * Converts a numeric value to Singaporean English ordinal words.
361
+ * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
362
+ * @returns {string} The number as ordinal words (e.g., "first", "forty-second")
363
+ */
364
+ function toOrdinal(value) {
313
365
  const integerPart = parseOrdinalValue(value)
366
+ checkMax(integerPart, ordinalMax)
314
367
  return integerToOrdinal(integerPart)
315
368
  }
316
369
 
@@ -318,10 +371,24 @@ function toOrdinal (value) {
318
371
  // CURRENCY
319
372
  // ============================================================================
320
373
 
321
- function toCurrency (value, options) {
322
- options = validateOptions(options)
374
+ /**
375
+ * @typedef {object} CurrencyOptions
376
+ * @property {boolean} [and] - Use "and" between dollars and cents
377
+ */
378
+
379
+ /** @type {Required<CurrencyOptions>} */
380
+ export const currencyDefaults = { and: true }
381
+
382
+ /**
383
+ * Converts a numeric value to Singaporean English currency words.
384
+ * @param {number | string | bigint} value - The currency amount to convert
385
+ * @param {CurrencyOptions} [options] - Optional configuration
386
+ * @returns {string} The amount in Singaporean English currency words
387
+ */
388
+ function toCurrency(value, options) {
323
389
  const { isNegative, dollars, cents } = parseCurrencyValue(value)
324
- const { and: useAnd = true } = options
390
+ checkMax(dollars, currencyMax)
391
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
325
392
 
326
393
  let result = ''
327
394
  if (isNegative) result = NEGATIVE + ' '
package/src/en-US.d.ts CHANGED
@@ -1,17 +1,45 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CardinalOptions
6
+ * @property {boolean} [hundredPairing] - Use hundred-pairing for 1100-9999 (e.g., "fifteen hundred" instead of "one thousand five hundred")
7
+ * @property {boolean} [and] - Use "and" after hundreds and before final small numbers (e.g., "one hundred and one" instead of "one hundred one")
8
+ */
9
+ /** @type {Required<CardinalOptions>} */
10
+ export const cardinalDefaults: Required<CardinalOptions>;
11
+ /**
12
+ * @typedef {object} CurrencyOptions
13
+ * @property {boolean} [and] - Use "and" between dollars and cents (e.g., "one dollar and fifty cents")
14
+ */
15
+ /** @type {Required<CurrencyOptions>} */
16
+ export const currencyDefaults: Required<CurrencyOptions>;
17
+ export type CardinalOptions = {
18
+ /**
19
+ * - Use hundred-pairing for 1100-9999 (e.g., "fifteen hundred" instead of "one thousand five hundred")
20
+ */
21
+ hundredPairing?: boolean | undefined;
22
+ /**
23
+ * - Use "and" after hundreds and before final small numbers (e.g., "one hundred and one" instead of "one hundred one")
24
+ */
25
+ and?: boolean | undefined;
26
+ };
27
+ export type CurrencyOptions = {
28
+ /**
29
+ * - Use "and" between dollars and cents (e.g., "one dollar and fifty cents")
30
+ */
31
+ and?: boolean | undefined;
32
+ };
1
33
  /**
2
34
  * Converts a numeric value to American English words.
3
35
  *
4
36
  * This is the main public API. It accepts any valid numeric input
5
37
  * (number, string, or bigint) and handles parsing internally.
6
- *
7
38
  * @param {number | string | bigint} value - The numeric value to convert
8
- * @param {Object} [options] - Optional configuration
9
- * @param {boolean} [options.hundredPairing=false] - Use hundred-pairing for 1100-9999 (e.g., "fifteen hundred" instead of "one thousand five hundred")
10
- * @param {boolean} [options.and=false] - Use "and" after hundreds and before final small numbers (e.g., "one hundred and one" instead of "one hundred one")
39
+ * @param {CardinalOptions} [options] - Optional configuration
11
40
  * @returns {string} The number in American English words
12
41
  * @throws {TypeError} If value is not a valid numeric type
13
42
  * @throws {Error} If value is not a valid number format
14
- *
15
43
  * @example
16
44
  * toCardinal(42) // 'forty-two'
17
45
  * toCardinal(101) // 'one hundred one'
@@ -19,18 +47,13 @@
19
47
  * toCardinal(1500) // 'one thousand five hundred'
20
48
  * toCardinal(1500, { hundredPairing: true }) // 'fifteen hundred'
21
49
  */
22
- export function toCardinal(value: number | string | bigint, options?: {
23
- hundredPairing?: boolean | undefined;
24
- and?: boolean | undefined;
25
- }): string;
50
+ export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
26
51
  /**
27
52
  * Converts a numeric value to American English ordinal words.
28
- *
29
53
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
30
54
  * @returns {string} The number as ordinal words (e.g., "first", "forty-second")
31
55
  * @throws {TypeError} If value is not a valid numeric type
32
56
  * @throws {RangeError} If value is negative, zero, or has a decimal part
33
- *
34
57
  * @example
35
58
  * toOrdinal(1) // 'first'
36
59
  * toOrdinal(2) // 'second'
@@ -44,20 +67,15 @@ export function toCardinal(value: number | string | bigint, options?: {
44
67
  export function toOrdinal(value: number | string | bigint): string;
45
68
  /**
46
69
  * Converts a numeric value to American English currency words.
47
- *
48
70
  * @param {number | string | bigint} value - The currency amount to convert
49
- * @param {Object} [options] - Optional configuration
50
- * @param {boolean} [options.and=true] - Use "and" between dollars and cents (e.g., "one dollar and fifty cents")
71
+ * @param {CurrencyOptions} [options] - Optional configuration
51
72
  * @returns {string} The amount in American English currency words
52
73
  * @throws {TypeError} If value is not a valid numeric type
53
74
  * @throws {Error} If value is not a valid number format
54
- *
55
75
  * @example
56
76
  * toCurrency(42.50) // 'forty-two dollars and fifty cents'
57
77
  * toCurrency(1) // 'one dollar'
58
78
  * toCurrency(0.99) // 'ninety-nine cents'
59
79
  * toCurrency(42.50, { and: false }) // 'forty-two dollars fifty cents'
60
80
  */
61
- export function toCurrency(value: number | string | bigint, options?: {
62
- and?: boolean | undefined;
63
- }): string;
81
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;
package/src/en-US.js CHANGED
@@ -20,7 +20,9 @@
20
20
  import { parseCardinalValue } from './utils/parse-cardinal.js'
21
21
  import { parseCurrencyValue } from './utils/parse-currency.js'
22
22
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
23
- import { validateOptions } from './utils/validate-options.js'
23
+ import { checkMax } from './utils/check-max.js'
24
+ import { western } from './utils/scale.js'
25
+ import { resolveOptions } from './utils/resolve-options.js'
24
26
 
25
27
  // ============================================================================
26
28
  // VOCABULARY
@@ -35,8 +37,15 @@ const SCALES = [
35
37
  'quintillion', 'sextillion', 'septillion', 'octillion', 'nonillion',
36
38
  'decillion', 'undecillion', 'duodecillion', 'tredecillion', 'quattuordecillion',
37
39
  'quindecillion', 'sexdecillion', 'septendecillion', 'octodecillion', 'novemdecillion',
38
- 'vigintillion'
40
+ 'vigintillion',
39
41
  ]
42
+
43
+ // Each form's maximum supported value (a bigint), derived from the scale table
44
+ // so it can't drift. Exported per form so they split cleanly and the gate/docs
45
+ // read each as a fact. en-US shares one ceiling across all three forms.
46
+ export const cardinalMax = western(SCALES.length)
47
+ export const ordinalMax = western(SCALES.length)
48
+ export const currencyMax = western(SCALES.length)
40
49
  const HUNDRED = 'hundred'
41
50
  const ZERO = 'zero'
42
51
  const NEGATIVE = 'minus'
@@ -62,12 +71,11 @@ const segmentResult = { word: '', hasHundred: false }
62
71
 
63
72
  /**
64
73
  * Builds words for a 0-999 segment.
65
- *
66
74
  * @param {number} n - Number 0-999
67
75
  * @param {boolean} useAnd - Whether to use "and" after hundreds
68
- * @returns {{ word: string, hasHundred: boolean }}
76
+ * @returns {{ word: string, hasHundred: boolean }} The segment words and whether a hundreds place was used
69
77
  */
70
- function buildSegment (n, useAnd) {
78
+ function buildSegment(n, useAnd) {
71
79
  if (n === 0) {
72
80
  segmentResult.word = ''
73
81
  segmentResult.hasHundred = false
@@ -82,9 +90,11 @@ function buildSegment (n, useAnd) {
82
90
  let tensOnes = ''
83
91
  if (tens === 1) {
84
92
  tensOnes = TEENS[ones]
85
- } else if (tens >= 2) {
93
+ }
94
+ else if (tens >= 2) {
86
95
  tensOnes = ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
87
- } else if (ones > 0) {
96
+ }
97
+ else if (ones > 0) {
88
98
  tensOnes = ONES[ones]
89
99
  }
90
100
 
@@ -93,11 +103,13 @@ function buildSegment (n, useAnd) {
93
103
  if (tensOnes) {
94
104
  const connector = useAnd ? ' and ' : ' '
95
105
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED + connector + tensOnes
96
- } else {
106
+ }
107
+ else {
97
108
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED
98
109
  }
99
110
  segmentResult.hasHundred = true
100
- } else {
111
+ }
112
+ else {
101
113
  segmentResult.word = tensOnes
102
114
  segmentResult.hasHundred = false
103
115
  }
@@ -111,13 +123,12 @@ function buildSegment (n, useAnd) {
111
123
 
112
124
  /**
113
125
  * Converts a non-negative integer to English words.
114
- *
115
126
  * @param {bigint} n - Non-negative integer to convert
116
127
  * @param {boolean} hundredPairing - Use hundred-pairing for 1100-9999
117
128
  * @param {boolean} useAnd - Use "and" after hundreds and before final segment
118
129
  * @returns {string} English words
119
130
  */
120
- function integerToWords (n, hundredPairing, useAnd) {
131
+ function integerToWords(n, hundredPairing, useAnd) {
121
132
  if (n === 0n) return ZERO
122
133
 
123
134
  // Fast path: numbers < 1000
@@ -173,12 +184,11 @@ function integerToWords (n, hundredPairing, useAnd) {
173
184
  /**
174
185
  * Builds words for numbers >= 1,000,000.
175
186
  * Uses BigInt division for faster segment extraction.
176
- *
177
187
  * @param {bigint} n - Number >= 1,000,000
178
188
  * @param {boolean} useAnd - Use "and" after hundreds and before final segment
179
189
  * @returns {string} English words
180
190
  */
181
- function buildLargeNumberWords (n, useAnd) {
191
+ function buildLargeNumberWords(n, useAnd) {
182
192
  // Extract segments using BigInt division
183
193
  // Segments are stored least-significant first (index 0 = ones, 1 = thousands, etc.)
184
194
  const segments = []
@@ -223,7 +233,8 @@ function buildLargeNumberWords (n, useAnd) {
223
233
  if (i > 0) {
224
234
  result += ' ' + SCALES[i - 1]
225
235
  prevWasScale = true
226
- } else {
236
+ }
237
+ else {
227
238
  prevWasScale = false
228
239
  }
229
240
  }
@@ -233,12 +244,11 @@ function buildLargeNumberWords (n, useAnd) {
233
244
 
234
245
  /**
235
246
  * Converts decimal digits to English words.
236
- *
237
247
  * @param {string} decimalPart - Decimal digits (without the point)
238
248
  * @param {boolean} useAnd - Use "and" in number conversion
239
249
  * @returns {string} English words for decimal part
240
250
  */
241
- function decimalPartToWords (decimalPart, useAnd) {
251
+ function decimalPartToWords(decimalPart, useAnd) {
242
252
  let result = ''
243
253
 
244
254
  // Handle leading zeros
@@ -259,20 +269,25 @@ function decimalPartToWords (decimalPart, useAnd) {
259
269
  return result
260
270
  }
261
271
 
272
+ /**
273
+ * @typedef {object} CardinalOptions
274
+ * @property {boolean} [hundredPairing] - Use hundred-pairing for 1100-9999 (e.g., "fifteen hundred" instead of "one thousand five hundred")
275
+ * @property {boolean} [and] - Use "and" after hundreds and before final small numbers (e.g., "one hundred and one" instead of "one hundred one")
276
+ */
277
+
278
+ /** @type {Required<CardinalOptions>} */
279
+ export const cardinalDefaults = { hundredPairing: false, and: false }
280
+
262
281
  /**
263
282
  * Converts a numeric value to American English words.
264
283
  *
265
284
  * This is the main public API. It accepts any valid numeric input
266
285
  * (number, string, or bigint) and handles parsing internally.
267
- *
268
286
  * @param {number | string | bigint} value - The numeric value to convert
269
- * @param {Object} [options] - Optional configuration
270
- * @param {boolean} [options.hundredPairing=false] - Use hundred-pairing for 1100-9999 (e.g., "fifteen hundred" instead of "one thousand five hundred")
271
- * @param {boolean} [options.and=false] - Use "and" after hundreds and before final small numbers (e.g., "one hundred and one" instead of "one hundred one")
287
+ * @param {CardinalOptions} [options] - Optional configuration
272
288
  * @returns {string} The number in American English words
273
289
  * @throws {TypeError} If value is not a valid numeric type
274
290
  * @throws {Error} If value is not a valid number format
275
- *
276
291
  * @example
277
292
  * toCardinal(42) // 'forty-two'
278
293
  * toCardinal(101) // 'one hundred one'
@@ -280,12 +295,13 @@ function decimalPartToWords (decimalPart, useAnd) {
280
295
  * toCardinal(1500) // 'one thousand five hundred'
281
296
  * toCardinal(1500, { hundredPairing: true }) // 'fifteen hundred'
282
297
  */
283
- function toCardinal (value, options) {
284
- options = validateOptions(options)
298
+ function toCardinal(value, options) {
285
299
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
300
+ // Both the integer part and the decimal's significant digits are spelled via
301
+ // the scale builder, so both must clear the ceiling.
302
+ checkMax(integerPart, cardinalMax, decimalPart)
286
303
 
287
- // Extract options with defaults
288
- const { hundredPairing = false, and: useAnd = false } = options
304
+ const { hundredPairing, and: useAnd } = resolveOptions(options, cardinalDefaults)
289
305
 
290
306
  let result = ''
291
307
 
@@ -309,11 +325,10 @@ function toCardinal (value, options) {
309
325
  /**
310
326
  * Builds ordinal words for a 0-999 segment (final segment only).
311
327
  * Returns ordinal form: "first", "twenty-third", "one hundred forty-fifth"
312
- *
313
328
  * @param {number} n - Number 0-999
314
329
  * @returns {string} Ordinal words for this segment
315
330
  */
316
- function buildOrdinalSegment (n) {
331
+ function buildOrdinalSegment(n) {
317
332
  const ones = n % 10
318
333
  const tens = Math.trunc(n / 10) % 10
319
334
  const hundreds = Math.trunc(n / 100)
@@ -323,15 +338,18 @@ function buildOrdinalSegment (n) {
323
338
  if (tens === 1) {
324
339
  // Teens: 10-19 → "tenth" through "nineteenth"
325
340
  tensOnesOrdinal = ORDINAL_TEENS[ones]
326
- } else if (tens >= 2) {
341
+ }
342
+ else if (tens >= 2) {
327
343
  if (ones > 0) {
328
344
  // Compound: "twenty-first", "thirty-second", etc.
329
345
  tensOnesOrdinal = TENS[tens] + '-' + ORDINAL_ONES[ones]
330
- } else {
346
+ }
347
+ else {
331
348
  // Round tens: "twentieth", "thirtieth", etc.
332
349
  tensOnesOrdinal = ORDINAL_TENS[tens]
333
350
  }
334
- } else if (ones > 0) {
351
+ }
352
+ else if (ones > 0) {
335
353
  // Single digit: "first", "second", etc.
336
354
  tensOnesOrdinal = ORDINAL_ONES[ones]
337
355
  }
@@ -341,7 +359,8 @@ function buildOrdinalSegment (n) {
341
359
  if (tensOnesOrdinal) {
342
360
  // "one hundred twenty-first"
343
361
  return ONES[hundreds] + ' ' + HUNDRED + ' ' + tensOnesOrdinal
344
- } else {
362
+ }
363
+ else {
345
364
  // "one hundredth", "two hundredth", etc.
346
365
  return ONES[hundreds] + ' hundredth'
347
366
  }
@@ -353,11 +372,10 @@ function buildOrdinalSegment (n) {
353
372
  /**
354
373
  * Converts a positive integer to ordinal words.
355
374
  * Generates ordinals directly without string manipulation.
356
- *
357
375
  * @param {bigint} n - Positive integer to convert
358
376
  * @returns {string} Ordinal English words
359
377
  */
360
- function integerToOrdinal (n) {
378
+ function integerToOrdinal(n) {
361
379
  // Fast path: numbers < 1000
362
380
  if (n < 1000n) {
363
381
  return buildOrdinalSegment(Number(n))
@@ -385,11 +403,10 @@ function integerToOrdinal (n) {
385
403
  /**
386
404
  * Builds ordinal words for numbers >= 1,000,000.
387
405
  * All segments except the final one are cardinal; final segment is ordinal.
388
- *
389
406
  * @param {bigint} n - Number >= 1,000,000
390
407
  * @returns {string} Ordinal English words
391
408
  */
392
- function buildLargeOrdinal (n) {
409
+ function buildLargeOrdinal(n) {
393
410
  // Extract segments (least-significant first)
394
411
  const segments = []
395
412
  let temp = n
@@ -423,11 +440,13 @@ function buildLargeOrdinal (n) {
423
440
  if (i === 0) {
424
441
  // Units position: use ordinal segment
425
442
  result += buildOrdinalSegment(segment)
426
- } else {
443
+ }
444
+ else {
427
445
  // Scale position with no remainder below: "one millionth"
428
446
  result += buildSegment(segment, false).word + ' ' + SCALES[i - 1] + 'th'
429
447
  }
430
- } else {
448
+ }
449
+ else {
431
450
  // Non-final segments are cardinal
432
451
  result += buildSegment(segment, false).word
433
452
  if (i > 0) {
@@ -441,12 +460,10 @@ function buildLargeOrdinal (n) {
441
460
 
442
461
  /**
443
462
  * Converts a numeric value to American English ordinal words.
444
- *
445
463
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
446
464
  * @returns {string} The number as ordinal words (e.g., "first", "forty-second")
447
465
  * @throws {TypeError} If value is not a valid numeric type
448
466
  * @throws {RangeError} If value is negative, zero, or has a decimal part
449
- *
450
467
  * @example
451
468
  * toOrdinal(1) // 'first'
452
469
  * toOrdinal(2) // 'second'
@@ -457,8 +474,9 @@ function buildLargeOrdinal (n) {
457
474
  * toOrdinal(101) // 'one hundred first'
458
475
  * toOrdinal(1000) // 'one thousandth'
459
476
  */
460
- function toOrdinal (value) {
477
+ function toOrdinal(value) {
461
478
  const integerPart = parseOrdinalValue(value)
479
+ checkMax(integerPart, ordinalMax)
462
480
  return integerToOrdinal(integerPart)
463
481
  }
464
482
 
@@ -466,26 +484,31 @@ function toOrdinal (value) {
466
484
  // CURRENCY: toCurrency(value, options?)
467
485
  // ============================================================================
468
486
 
487
+ /**
488
+ * @typedef {object} CurrencyOptions
489
+ * @property {boolean} [and] - Use "and" between dollars and cents (e.g., "one dollar and fifty cents")
490
+ */
491
+
492
+ /** @type {Required<CurrencyOptions>} */
493
+ export const currencyDefaults = { and: true }
494
+
469
495
  /**
470
496
  * Converts a numeric value to American English currency words.
471
- *
472
497
  * @param {number | string | bigint} value - The currency amount to convert
473
- * @param {Object} [options] - Optional configuration
474
- * @param {boolean} [options.and=true] - Use "and" between dollars and cents (e.g., "one dollar and fifty cents")
498
+ * @param {CurrencyOptions} [options] - Optional configuration
475
499
  * @returns {string} The amount in American English currency words
476
500
  * @throws {TypeError} If value is not a valid numeric type
477
501
  * @throws {Error} If value is not a valid number format
478
- *
479
502
  * @example
480
503
  * toCurrency(42.50) // 'forty-two dollars and fifty cents'
481
504
  * toCurrency(1) // 'one dollar'
482
505
  * toCurrency(0.99) // 'ninety-nine cents'
483
506
  * toCurrency(42.50, { and: false }) // 'forty-two dollars fifty cents'
484
507
  */
485
- function toCurrency (value, options) {
486
- options = validateOptions(options)
508
+ function toCurrency(value, options) {
487
509
  const { isNegative, dollars, cents } = parseCurrencyValue(value)
488
- const { and: useAnd = true } = options
510
+ checkMax(dollars, currencyMax)
511
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
489
512
 
490
513
  // Build result
491
514
  let result = ''