n2words 5.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 +128 -42
  2. package/README.md +6 -4
  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 -2
  112. package/dist/pt-BR.umd.js +2 -2
  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 +31 -22
  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 +22 -11
  257. package/src/pt-BR.js +93 -53
  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-GB.js CHANGED
@@ -13,7 +13,9 @@
13
13
  import { parseCardinalValue } from './utils/parse-cardinal.js'
14
14
  import { parseCurrencyValue } from './utils/parse-currency.js'
15
15
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
16
- import { validateOptions } from './utils/validate-options.js'
16
+ import { checkMax } from './utils/check-max.js'
17
+ import { western } from './utils/scale.js'
18
+ import { resolveOptions } from './utils/resolve-options.js'
17
19
 
18
20
  // ============================================================================
19
21
  // Vocabulary (module-level constants)
@@ -28,9 +30,13 @@ const SCALES = [
28
30
  'quintillion', 'sextillion', 'septillion', 'octillion', 'nonillion',
29
31
  'decillion', 'undecillion', 'duodecillion', 'tredecillion', 'quattuordecillion',
30
32
  'quindecillion', 'sexdecillion', 'septendecillion', 'octodecillion', 'novemdecillion',
31
- 'vigintillion'
33
+ 'vigintillion',
32
34
  ]
33
35
 
36
+ export const cardinalMax = western(SCALES.length)
37
+ export const ordinalMax = western(SCALES.length)
38
+ export const currencyMax = western(SCALES.length)
39
+
34
40
  const HUNDRED = 'hundred'
35
41
  const ZERO = 'zero'
36
42
  const NEGATIVE = 'minus'
@@ -56,11 +62,10 @@ const segmentResult = { word: '', hasHundred: false }
56
62
 
57
63
  /**
58
64
  * Builds words for a 0-999 segment.
59
- *
60
65
  * @param {number} n - Number 0-999
61
- * @returns {{ word: string, hasHundred: boolean }}
66
+ * @returns {{ word: string, hasHundred: boolean }} The segment words and whether it includes a hundreds place
62
67
  */
63
- function buildSegment (n) {
68
+ function buildSegment(n) {
64
69
  if (n === 0) {
65
70
  segmentResult.word = ''
66
71
  segmentResult.hasHundred = false
@@ -75,9 +80,11 @@ function buildSegment (n) {
75
80
  let tensOnes = ''
76
81
  if (tens === 1) {
77
82
  tensOnes = TEENS[ones]
78
- } else if (tens >= 2) {
83
+ }
84
+ else if (tens >= 2) {
79
85
  tensOnes = ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
80
- } else if (ones > 0) {
86
+ }
87
+ else if (ones > 0) {
81
88
  tensOnes = ONES[ones]
82
89
  }
83
90
 
@@ -85,11 +92,13 @@ function buildSegment (n) {
85
92
  if (hundreds > 0) {
86
93
  if (tensOnes) {
87
94
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED + ' and ' + tensOnes
88
- } else {
95
+ }
96
+ else {
89
97
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED
90
98
  }
91
99
  segmentResult.hasHundred = true
92
- } else {
100
+ }
101
+ else {
93
102
  segmentResult.word = tensOnes
94
103
  segmentResult.hasHundred = false
95
104
  }
@@ -103,11 +112,10 @@ function buildSegment (n) {
103
112
 
104
113
  /**
105
114
  * Converts a non-negative integer to English words.
106
- *
107
115
  * @param {bigint} n - Non-negative integer to convert
108
116
  * @returns {string} English words
109
117
  */
110
- function integerToWords (n) {
118
+ function integerToWords(n) {
111
119
  if (n === 0n) return ZERO
112
120
 
113
121
  // Fast path: numbers < 1000
@@ -138,11 +146,10 @@ function integerToWords (n) {
138
146
  /**
139
147
  * Builds words for numbers >= 1,000,000.
140
148
  * Uses BigInt division for faster segment extraction.
141
- *
142
149
  * @param {bigint} n - Number >= 1,000,000
143
150
  * @returns {string} English words
144
151
  */
145
- function buildLargeNumberWords (n) {
152
+ function buildLargeNumberWords(n) {
146
153
  // Extract segments using BigInt division
147
154
  // Segments are stored least-significant first (index 0 = ones, 1 = thousands, etc.)
148
155
  const segments = []
@@ -185,7 +192,8 @@ function buildLargeNumberWords (n) {
185
192
  if (i > 0) {
186
193
  result += ' ' + SCALES[i - 1]
187
194
  prevWasScale = true
188
- } else {
195
+ }
196
+ else {
189
197
  prevWasScale = false
190
198
  }
191
199
  }
@@ -195,11 +203,10 @@ function buildLargeNumberWords (n) {
195
203
 
196
204
  /**
197
205
  * Converts decimal digits to English words.
198
- *
199
206
  * @param {string} decimalPart - Decimal digits (without the point)
200
207
  * @returns {string} English words for decimal part
201
208
  */
202
- function decimalPartToWords (decimalPart) {
209
+ function decimalPartToWords(decimalPart) {
203
210
  let result = ''
204
211
 
205
212
  // Handle leading zeros
@@ -225,19 +232,20 @@ function decimalPartToWords (decimalPart) {
225
232
  *
226
233
  * This is the main public API. It accepts any valid numeric input
227
234
  * (number, string, or bigint) and handles parsing internally.
228
- *
229
235
  * @param {number | string | bigint} value - The numeric value to convert
230
236
  * @returns {string} The number in English words
231
237
  * @throws {TypeError} If value is not a valid numeric type
232
238
  * @throws {Error} If value is not a valid number format
233
- *
234
239
  * @example
235
240
  * toCardinal(42) // 'forty-two'
236
241
  * toCardinal(-3.14) // 'minus three point fourteen'
237
242
  * toCardinal('1000000') // 'one million'
238
243
  */
239
- function toCardinal (value) {
244
+ function toCardinal(value) {
240
245
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
246
+ // Both the integer part and the decimal's significant digits are spelled via
247
+ // the scale builder, so both must clear the ceiling.
248
+ checkMax(integerPart, cardinalMax, decimalPart)
241
249
 
242
250
  let result = ''
243
251
 
@@ -261,11 +269,10 @@ function toCardinal (value) {
261
269
  /**
262
270
  * Builds ordinal words for a 0-999 segment (final segment only).
263
271
  * Returns ordinal form: "first", "twenty-third", "one hundred forty-fifth"
264
- *
265
272
  * @param {number} n - Number 0-999
266
273
  * @returns {string} Ordinal words for this segment
267
274
  */
268
- function buildOrdinalSegment (n) {
275
+ function buildOrdinalSegment(n) {
269
276
  const ones = n % 10
270
277
  const tens = Math.trunc(n / 10) % 10
271
278
  const hundreds = Math.trunc(n / 100)
@@ -275,15 +282,18 @@ function buildOrdinalSegment (n) {
275
282
  if (tens === 1) {
276
283
  // Teens: 10-19 → "tenth" through "nineteenth"
277
284
  tensOnesOrdinal = ORDINAL_TEENS[ones]
278
- } else if (tens >= 2) {
285
+ }
286
+ else if (tens >= 2) {
279
287
  if (ones > 0) {
280
288
  // Compound: "twenty-first", "thirty-second", etc.
281
289
  tensOnesOrdinal = TENS[tens] + '-' + ORDINAL_ONES[ones]
282
- } else {
290
+ }
291
+ else {
283
292
  // Round tens: "twentieth", "thirtieth", etc.
284
293
  tensOnesOrdinal = ORDINAL_TENS[tens]
285
294
  }
286
- } else if (ones > 0) {
295
+ }
296
+ else if (ones > 0) {
287
297
  // Single digit: "first", "second", etc.
288
298
  tensOnesOrdinal = ORDINAL_ONES[ones]
289
299
  }
@@ -293,7 +303,8 @@ function buildOrdinalSegment (n) {
293
303
  if (tensOnesOrdinal) {
294
304
  // "one hundred twenty-first"
295
305
  return ONES[hundreds] + ' ' + HUNDRED + ' ' + tensOnesOrdinal
296
- } else {
306
+ }
307
+ else {
297
308
  // "one hundredth", "two hundredth", etc.
298
309
  return ONES[hundreds] + ' hundredth'
299
310
  }
@@ -305,11 +316,10 @@ function buildOrdinalSegment (n) {
305
316
  /**
306
317
  * Converts a positive integer to ordinal words.
307
318
  * Generates ordinals directly without string manipulation.
308
- *
309
319
  * @param {bigint} n - Positive integer to convert
310
320
  * @returns {string} Ordinal English words
311
321
  */
312
- function integerToOrdinal (n) {
322
+ function integerToOrdinal(n) {
313
323
  // Fast path: numbers < 1000
314
324
  if (n < 1000n) {
315
325
  return buildOrdinalSegment(Number(n))
@@ -337,11 +347,10 @@ function integerToOrdinal (n) {
337
347
  /**
338
348
  * Builds ordinal words for numbers >= 1,000,000.
339
349
  * All segments except the final one are cardinal; final segment is ordinal.
340
- *
341
350
  * @param {bigint} n - Number >= 1,000,000
342
351
  * @returns {string} Ordinal English words
343
352
  */
344
- function buildLargeOrdinal (n) {
353
+ function buildLargeOrdinal(n) {
345
354
  // Extract segments (least-significant first)
346
355
  const segments = []
347
356
  let temp = n
@@ -375,11 +384,13 @@ function buildLargeOrdinal (n) {
375
384
  if (i === 0) {
376
385
  // Units position: use ordinal segment
377
386
  result += buildOrdinalSegment(segment)
378
- } else {
387
+ }
388
+ else {
379
389
  // Scale position with no remainder below: "one millionth"
380
390
  result += buildSegment(segment).word + ' ' + SCALES[i - 1] + 'th'
381
391
  }
382
- } else {
392
+ }
393
+ else {
383
394
  // Non-final segments are cardinal
384
395
  result += buildSegment(segment).word
385
396
  if (i > 0) {
@@ -393,12 +404,10 @@ function buildLargeOrdinal (n) {
393
404
 
394
405
  /**
395
406
  * Converts a numeric value to British English ordinal words.
396
- *
397
407
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
398
408
  * @returns {string} The number as ordinal words (e.g., "first", "forty-second")
399
409
  * @throws {TypeError} If value is not a valid numeric type
400
410
  * @throws {RangeError} If value is negative, zero, or has a decimal part
401
- *
402
411
  * @example
403
412
  * toOrdinal(1) // 'first'
404
413
  * toOrdinal(2) // 'second'
@@ -409,8 +418,9 @@ function buildLargeOrdinal (n) {
409
418
  * toOrdinal(101) // 'one hundred first'
410
419
  * toOrdinal(1000) // 'one thousandth'
411
420
  */
412
- function toOrdinal (value) {
421
+ function toOrdinal(value) {
413
422
  const integerPart = parseOrdinalValue(value)
423
+ checkMax(integerPart, ordinalMax)
414
424
  return integerToOrdinal(integerPart)
415
425
  }
416
426
 
@@ -418,16 +428,21 @@ function toOrdinal (value) {
418
428
  // CURRENCY: toCurrency(value, options?)
419
429
  // ============================================================================
420
430
 
431
+ /**
432
+ * @typedef {object} CurrencyOptions
433
+ * @property {boolean} [and] - Use "and" between pounds and pence (e.g., "one pound and fifty pence")
434
+ */
435
+
436
+ /** @type {Required<CurrencyOptions>} */
437
+ export const currencyDefaults = { and: true }
438
+
421
439
  /**
422
440
  * Converts a numeric value to British English currency words.
423
- *
424
441
  * @param {number | string | bigint} value - The currency amount to convert
425
- * @param {Object} [options] - Optional configuration
426
- * @param {boolean} [options.and=true] - Use "and" between pounds and pence (e.g., "one pound and fifty pence")
442
+ * @param {CurrencyOptions} [options] - Optional configuration
427
443
  * @returns {string} The amount in British English currency words
428
444
  * @throws {TypeError} If value is not a valid numeric type
429
445
  * @throws {Error} If value is not a valid number format
430
- *
431
446
  * @example
432
447
  * toCurrency(42.50) // 'forty-two pounds and fifty pence'
433
448
  * toCurrency(1) // 'one pound'
@@ -435,10 +450,10 @@ function toOrdinal (value) {
435
450
  * toCurrency(0.01) // 'one penny'
436
451
  * toCurrency(42.50, { and: false }) // 'forty-two pounds fifty pence'
437
452
  */
438
- function toCurrency (value, options) {
439
- options = validateOptions(options)
453
+ function toCurrency(value, options) {
440
454
  const { isNegative, dollars: pounds, cents: pence } = parseCurrencyValue(value)
441
- const { and: useAnd = true } = options
455
+ checkMax(pounds, currencyMax)
456
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
442
457
 
443
458
  // Build result
444
459
  let result = ''
package/src/en-GH.d.ts CHANGED
@@ -1,11 +1,40 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CurrencyOptions
6
+ * @property {boolean} [and] - Use "and" between cedis and pesewas
7
+ */
8
+ /** @type {Required<CurrencyOptions>} */
9
+ export const currencyDefaults: Required<CurrencyOptions>;
10
+ export type CurrencyOptions = {
11
+ /**
12
+ * - Use "and" between cedis and pesewas
13
+ */
14
+ and?: boolean | undefined;
15
+ };
1
16
  /**
2
17
  * Converts a numeric value to Ghanaian English words.
3
- *
4
18
  * @param {number | string | bigint} value - The numeric value to convert
5
19
  * @returns {string} The number in English words
6
20
  * @throws {TypeError} If value is not a valid numeric type
7
21
  * @throws {Error} If value is not a valid number format
8
22
  */
9
23
  export function toCardinal(value: number | string | bigint): string;
10
- export function toOrdinal(value: any): string;
11
- export function toCurrency(value: any, options: any): string;
24
+ /**
25
+ * Converts a numeric value to Ghanaian English ordinal words.
26
+ * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
27
+ * @returns {string} The number as ordinal words (e.g., "first", "forty-second")
28
+ * @throws {TypeError} If value is not a valid numeric type
29
+ * @throws {RangeError} If value is negative, zero, or has a decimal part
30
+ */
31
+ export function toOrdinal(value: number | string | bigint): string;
32
+ /**
33
+ * Converts a numeric value to Ghanaian English currency words.
34
+ * @param {number | string | bigint} value - The currency amount to convert
35
+ * @param {CurrencyOptions} [options] - Optional configuration
36
+ * @returns {string} The amount in Ghanaian English currency words
37
+ * @throws {TypeError} If value is not a valid numeric type
38
+ * @throws {Error} If value is not a valid number format
39
+ */
40
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;
package/src/en-GH.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,12 @@ const PESEWAS = 'pesewas'
60
66
 
61
67
  const segmentResult = { word: '', hasHundred: false }
62
68
 
63
- function buildSegment (n) {
69
+ /**
70
+ * Builds the words for a three-digit segment (0-999).
71
+ * @param {number} n - The segment value (0-999)
72
+ * @returns {{word: string, hasHundred: boolean}} The segment words and whether it includes a hundred
73
+ */
74
+ function buildSegment(n) {
64
75
  if (n === 0) {
65
76
  segmentResult.word = ''
66
77
  segmentResult.hasHundred = false
@@ -74,20 +85,24 @@ function buildSegment (n) {
74
85
  let tensOnes = ''
75
86
  if (tens === 1) {
76
87
  tensOnes = TEENS[ones]
77
- } else if (tens >= 2) {
88
+ }
89
+ else if (tens >= 2) {
78
90
  tensOnes = ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
79
- } else if (ones > 0) {
91
+ }
92
+ else if (ones > 0) {
80
93
  tensOnes = ONES[ones]
81
94
  }
82
95
 
83
96
  if (hundreds > 0) {
84
97
  if (tensOnes) {
85
98
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED + ' and ' + tensOnes
86
- } else {
99
+ }
100
+ else {
87
101
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED
88
102
  }
89
103
  segmentResult.hasHundred = true
90
- } else {
104
+ }
105
+ else {
91
106
  segmentResult.word = tensOnes
92
107
  segmentResult.hasHundred = false
93
108
  }
@@ -99,7 +114,12 @@ function buildSegment (n) {
99
114
  // Conversion Functions
100
115
  // ============================================================================
101
116
 
102
- function integerToWords (n) {
117
+ /**
118
+ * Converts a non-negative integer to cardinal words.
119
+ * @param {bigint} n - The non-negative integer to convert
120
+ * @returns {string} The number in English words
121
+ */
122
+ function integerToWords(n) {
103
123
  if (n === 0n) return ZERO
104
124
 
105
125
  if (n < 1000n) {
@@ -124,7 +144,12 @@ function integerToWords (n) {
124
144
  return buildLargeNumberWords(n)
125
145
  }
126
146
 
127
- function buildLargeNumberWords (n) {
147
+ /**
148
+ * Builds cardinal words for integers of one million or greater.
149
+ * @param {bigint} n - The integer to convert (>= 1,000,000)
150
+ * @returns {string} The number in English words
151
+ */
152
+ function buildLargeNumberWords(n) {
128
153
  const segments = []
129
154
  let temp = n
130
155
  while (temp > 0n) {
@@ -160,7 +185,8 @@ function buildLargeNumberWords (n) {
160
185
  if (i > 0) {
161
186
  result += ' ' + SCALES[i - 1]
162
187
  prevWasScale = true
163
- } else {
188
+ }
189
+ else {
164
190
  prevWasScale = false
165
191
  }
166
192
  }
@@ -168,7 +194,12 @@ function buildLargeNumberWords (n) {
168
194
  return result
169
195
  }
170
196
 
171
- function decimalPartToWords (decimalPart) {
197
+ /**
198
+ * Converts the fractional digits of a decimal to words.
199
+ * @param {string} decimalPart - The decimal digits (e.g., "05")
200
+ * @returns {string} The decimal part in English words
201
+ */
202
+ function decimalPartToWords(decimalPart) {
172
203
  let result = ''
173
204
 
174
205
  let i = 0
@@ -189,14 +220,16 @@ function decimalPartToWords (decimalPart) {
189
220
 
190
221
  /**
191
222
  * Converts a numeric value to Ghanaian English words.
192
- *
193
223
  * @param {number | string | bigint} value - The numeric value to convert
194
224
  * @returns {string} The number in English words
195
225
  * @throws {TypeError} If value is not a valid numeric type
196
226
  * @throws {Error} If value is not a valid number format
197
227
  */
198
- function toCardinal (value) {
228
+ function toCardinal(value) {
199
229
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
230
+ // Both the integer part and the decimal's significant digits are spelled via
231
+ // the scale builder, so both must clear the ceiling.
232
+ checkMax(integerPart, cardinalMax, decimalPart)
200
233
 
201
234
  let result = ''
202
235
 
@@ -217,7 +250,12 @@ function toCardinal (value) {
217
250
  // ORDINAL
218
251
  // ============================================================================
219
252
 
220
- function buildOrdinalSegment (n) {
253
+ /**
254
+ * Builds the ordinal words for a three-digit segment (0-999).
255
+ * @param {number} n - The segment value (0-999)
256
+ * @returns {string} The ordinal segment words
257
+ */
258
+ function buildOrdinalSegment(n) {
221
259
  const ones = n % 10
222
260
  const tens = Math.trunc(n / 10) % 10
223
261
  const hundreds = Math.trunc(n / 100)
@@ -225,20 +263,24 @@ function buildOrdinalSegment (n) {
225
263
  let tensOnesOrdinal = ''
226
264
  if (tens === 1) {
227
265
  tensOnesOrdinal = ORDINAL_TEENS[ones]
228
- } else if (tens >= 2) {
266
+ }
267
+ else if (tens >= 2) {
229
268
  if (ones > 0) {
230
269
  tensOnesOrdinal = TENS[tens] + '-' + ORDINAL_ONES[ones]
231
- } else {
270
+ }
271
+ else {
232
272
  tensOnesOrdinal = ORDINAL_TENS[tens]
233
273
  }
234
- } else if (ones > 0) {
274
+ }
275
+ else if (ones > 0) {
235
276
  tensOnesOrdinal = ORDINAL_ONES[ones]
236
277
  }
237
278
 
238
279
  if (hundreds > 0) {
239
280
  if (tensOnesOrdinal) {
240
281
  return ONES[hundreds] + ' ' + HUNDRED + ' ' + tensOnesOrdinal
241
- } else {
282
+ }
283
+ else {
242
284
  return ONES[hundreds] + ' hundredth'
243
285
  }
244
286
  }
@@ -246,7 +288,12 @@ function buildOrdinalSegment (n) {
246
288
  return tensOnesOrdinal
247
289
  }
248
290
 
249
- function integerToOrdinal (n) {
291
+ /**
292
+ * Converts a positive integer to ordinal words.
293
+ * @param {bigint} n - The positive integer to convert
294
+ * @returns {string} The ordinal in English words
295
+ */
296
+ function integerToOrdinal(n) {
250
297
  if (n < 1000n) {
251
298
  return buildOrdinalSegment(Number(n))
252
299
  }
@@ -266,7 +313,12 @@ function integerToOrdinal (n) {
266
313
  return buildLargeOrdinal(n)
267
314
  }
268
315
 
269
- function buildLargeOrdinal (n) {
316
+ /**
317
+ * Builds ordinal words for integers of one million or greater.
318
+ * @param {bigint} n - The integer to convert (>= 1,000,000)
319
+ * @returns {string} The ordinal in English words
320
+ */
321
+ function buildLargeOrdinal(n) {
270
322
  const segments = []
271
323
  let temp = n
272
324
  while (temp > 0n) {
@@ -295,10 +347,12 @@ function buildLargeOrdinal (n) {
295
347
  if (isLowestSegment) {
296
348
  if (i === 0) {
297
349
  result += buildOrdinalSegment(segment)
298
- } else {
350
+ }
351
+ else {
299
352
  result += buildSegment(segment).word + ' ' + SCALES[i - 1] + 'th'
300
353
  }
301
- } else {
354
+ }
355
+ else {
302
356
  result += buildSegment(segment).word
303
357
  if (i > 0) {
304
358
  result += ' ' + SCALES[i - 1]
@@ -309,8 +363,16 @@ function buildLargeOrdinal (n) {
309
363
  return result
310
364
  }
311
365
 
312
- function toOrdinal (value) {
366
+ /**
367
+ * Converts a numeric value to Ghanaian English ordinal words.
368
+ * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
369
+ * @returns {string} The number as ordinal words (e.g., "first", "forty-second")
370
+ * @throws {TypeError} If value is not a valid numeric type
371
+ * @throws {RangeError} If value is negative, zero, or has a decimal part
372
+ */
373
+ function toOrdinal(value) {
313
374
  const integerPart = parseOrdinalValue(value)
375
+ checkMax(integerPart, ordinalMax)
314
376
  return integerToOrdinal(integerPart)
315
377
  }
316
378
 
@@ -318,10 +380,26 @@ function toOrdinal (value) {
318
380
  // CURRENCY
319
381
  // ============================================================================
320
382
 
321
- function toCurrency (value, options) {
322
- options = validateOptions(options)
383
+ /**
384
+ * @typedef {object} CurrencyOptions
385
+ * @property {boolean} [and] - Use "and" between cedis and pesewas
386
+ */
387
+
388
+ /** @type {Required<CurrencyOptions>} */
389
+ export const currencyDefaults = { and: true }
390
+
391
+ /**
392
+ * Converts a numeric value to Ghanaian English currency words.
393
+ * @param {number | string | bigint} value - The currency amount to convert
394
+ * @param {CurrencyOptions} [options] - Optional configuration
395
+ * @returns {string} The amount in Ghanaian English currency words
396
+ * @throws {TypeError} If value is not a valid numeric type
397
+ * @throws {Error} If value is not a valid number format
398
+ */
399
+ function toCurrency(value, options) {
323
400
  const { isNegative, dollars: cedis, cents: pesewas } = parseCurrencyValue(value)
324
- const { and: useAnd = true } = options
401
+ checkMax(cedis, currencyMax)
402
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
325
403
 
326
404
  let result = ''
327
405
  if (isNegative) result = NEGATIVE + ' '
package/src/en-IE.d.ts CHANGED
@@ -1,14 +1,27 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CurrencyOptions
6
+ * @property {boolean} [and] - Use "and" between euro and cent (e.g., "one euro and fifty cents")
7
+ */
8
+ /** @type {Required<CurrencyOptions>} */
9
+ export const currencyDefaults: Required<CurrencyOptions>;
10
+ export type CurrencyOptions = {
11
+ /**
12
+ * - Use "and" between euro and cent (e.g., "one euro and fifty cents")
13
+ */
14
+ and?: boolean | undefined;
15
+ };
1
16
  /**
2
17
  * Converts a numeric value to Irish English words.
3
18
  *
4
19
  * This is the main public API. It accepts any valid numeric input
5
20
  * (number, string, or bigint) and handles parsing internally.
6
- *
7
21
  * @param {number | string | bigint} value - The numeric value to convert
8
22
  * @returns {string} The number in English words
9
23
  * @throws {TypeError} If value is not a valid numeric type
10
24
  * @throws {Error} If value is not a valid number format
11
- *
12
25
  * @example
13
26
  * toCardinal(42) // 'forty-two'
14
27
  * toCardinal(-3.14) // 'minus three point fourteen'
@@ -17,12 +30,10 @@
17
30
  export function toCardinal(value: number | string | bigint): string;
18
31
  /**
19
32
  * Converts a numeric value to Irish English ordinal words.
20
- *
21
33
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
22
34
  * @returns {string} The number as ordinal words (e.g., "first", "forty-second")
23
35
  * @throws {TypeError} If value is not a valid numeric type
24
36
  * @throws {RangeError} If value is negative, zero, or has a decimal part
25
- *
26
37
  * @example
27
38
  * toOrdinal(1) // 'first'
28
39
  * toOrdinal(2) // 'second'
@@ -36,14 +47,11 @@ export function toCardinal(value: number | string | bigint): string;
36
47
  export function toOrdinal(value: number | string | bigint): string;
37
48
  /**
38
49
  * Converts a numeric value to Irish English currency words.
39
- *
40
50
  * @param {number | string | bigint} value - The currency amount to convert
41
- * @param {Object} [options] - Optional configuration
42
- * @param {boolean} [options.and=true] - Use "and" between euro and cent (e.g., "one euro and fifty cents")
51
+ * @param {CurrencyOptions} [options] - Optional configuration
43
52
  * @returns {string} The amount in Irish English currency words
44
53
  * @throws {TypeError} If value is not a valid numeric type
45
54
  * @throws {Error} If value is not a valid number format
46
- *
47
55
  * @example
48
56
  * toCurrency(42.50) // 'forty-two euro and fifty cents'
49
57
  * toCurrency(1) // 'one euro'
@@ -51,6 +59,4 @@ export function toOrdinal(value: number | string | bigint): string;
51
59
  * toCurrency(0.01) // 'one cent'
52
60
  * toCurrency(42.50, { and: false }) // 'forty-two euro fifty cents'
53
61
  */
54
- export function toCurrency(value: number | string | bigint, options?: {
55
- and?: boolean | undefined;
56
- }): string;
62
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;