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-PH.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 CENTAVOS = 'centavos'
60
66
 
61
67
  const segmentResult = { word: '', hasHundred: false }
62
68
 
63
- function buildSegment (n) {
69
+ /**
70
+ * @param {number} n The 0-999 segment value to convert.
71
+ * @returns {{word: string, hasHundred: boolean}} The segment words and whether it includes a hundreds place.
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 The non-negative integer to convert.
118
+ * @returns {string} The integer in 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 The integer of one million or greater to convert.
147
+ * @returns {string} The integer in 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 The fractional digits to convert.
196
+ * @returns {string} The decimal digits in English words.
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 Philippine 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 The 0-999 segment value to convert.
251
+ * @returns {string} The segment as ordinal words.
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 The positive integer to convert.
288
+ * @returns {string} The integer as ordinal 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 The integer of one million or greater to convert.
312
+ * @returns {string} The integer as ordinal 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,16 @@ function buildLargeOrdinal (n) {
309
356
  return result
310
357
  }
311
358
 
312
- function toOrdinal (value) {
359
+ /**
360
+ * Converts a numeric value to Philippine 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
+ * @throws {TypeError} If value is not a valid numeric type
364
+ * @throws {RangeError} If value is negative, zero, or has a decimal part
365
+ */
366
+ function toOrdinal(value) {
313
367
  const integerPart = parseOrdinalValue(value)
368
+ checkMax(integerPart, ordinalMax)
314
369
  return integerToOrdinal(integerPart)
315
370
  }
316
371
 
@@ -318,10 +373,26 @@ function toOrdinal (value) {
318
373
  // CURRENCY
319
374
  // ============================================================================
320
375
 
321
- function toCurrency (value, options) {
322
- options = validateOptions(options)
376
+ /**
377
+ * @typedef {object} CurrencyOptions
378
+ * @property {boolean} [and] - Use "and" between pesos and centavos
379
+ */
380
+
381
+ /** @type {Required<CurrencyOptions>} */
382
+ export const currencyDefaults = { and: true }
383
+
384
+ /**
385
+ * Converts a numeric value to Philippine English currency words.
386
+ * @param {number | string | bigint} value - The numeric value to convert
387
+ * @param {CurrencyOptions} [options] - Optional configuration
388
+ * @returns {string} The amount in Philippine English currency words
389
+ * @throws {TypeError} If value is not a valid numeric type
390
+ * @throws {Error} If value is not a valid number format
391
+ */
392
+ function toCurrency(value, options) {
323
393
  const { isNegative, dollars: pesos, cents: centavos } = parseCurrencyValue(value)
324
- const { and: useAnd = true } = options
394
+ checkMax(pesos, currencyMax)
395
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
325
396
 
326
397
  let result = ''
327
398
  if (isNegative) result = NEGATIVE + ' '
package/src/en-PK.d.ts CHANGED
@@ -1,11 +1,24 @@
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 rupees and paise
7
+ */
8
+ /** @type {Required<CurrencyOptions>} */
9
+ export const currencyDefaults: Required<CurrencyOptions>;
10
+ export type CurrencyOptions = {
11
+ /**
12
+ * - Use "and" between rupees and paise
13
+ */
14
+ and?: boolean | undefined;
15
+ };
1
16
  /**
2
17
  * Converts a numeric value to English words using Indian numbering.
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
- *
9
22
  * @example
10
23
  * toCardinal(42) // 'forty-two'
11
24
  * toCardinal(100000) // 'one lakh'
@@ -15,12 +28,10 @@
15
28
  export function toCardinal(value: number | string | bigint): string;
16
29
  /**
17
30
  * Converts a numeric value to English ordinal words using Indian numbering.
18
- *
19
31
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
20
32
  * @returns {string} The number as ordinal words
21
33
  * @throws {TypeError} If value is not a valid numeric type
22
34
  * @throws {RangeError} If value is negative, zero, or has a decimal part
23
- *
24
35
  * @example
25
36
  * toOrdinal(1) // 'first'
26
37
  * toOrdinal(100000) // 'one lakhth'
@@ -29,14 +40,11 @@ export function toCardinal(value: number | string | bigint): string;
29
40
  export function toOrdinal(value: number | string | bigint): string;
30
41
  /**
31
42
  * Converts a numeric value to Pakistani English currency words.
32
- *
33
43
  * @param {number | string | bigint} value - The currency amount to convert
34
- * @param {Object} [options] - Optional configuration
35
- * @param {boolean} [options.and=true] - Use "and" between rupees and paise
44
+ * @param {CurrencyOptions} [options] - Optional configuration
36
45
  * @returns {string} The amount in Pakistani English currency words
37
46
  * @throws {TypeError} If value is not a valid numeric type
38
47
  * @throws {Error} If value is not a valid number format
39
- *
40
48
  * @example
41
49
  * toCurrency(42.50) // 'forty-two rupees and fifty paise'
42
50
  * toCurrency(100000) // 'one lakh rupees'
@@ -44,6 +52,4 @@ export function toOrdinal(value: number | string | bigint): string;
44
52
  * toCurrency(0.50) // 'fifty paise'
45
53
  * toCurrency(42.50, { and: false }) // 'forty-two rupees fifty paise'
46
54
  */
47
- export function toCurrency(value: number | string | bigint, options?: {
48
- and?: boolean | undefined;
49
- }): string;
55
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;
package/src/en-PK.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 { indian } from './utils/scale.js'
18
+ import { resolveOptions } from './utils/resolve-options.js'
17
19
 
18
20
  // ============================================================================
19
21
  // Vocabulary (module-level constants)
@@ -26,6 +28,11 @@ const TENS = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy',
26
28
  // Indian numbering scales: 10^3, 10^5, 10^7, 10^9, 10^11, 10^13, 10^15, 10^17
27
29
  const SCALES = ['thousand', 'lakh', 'crore', 'arab', 'kharab', 'neel', 'padma', 'shankh']
28
30
 
31
+ // 3-2-2 grouping: a 3-digit base segment then 2 digits per Indian scale word.
32
+ export const cardinalMax = indian(SCALES.length + 1)
33
+ export const ordinalMax = indian(SCALES.length + 1)
34
+ export const currencyMax = indian(SCALES.length + 1)
35
+
29
36
  const HUNDRED = 'hundred'
30
37
  const ZERO = 'zero'
31
38
  const NEGATIVE = 'minus'
@@ -51,11 +58,10 @@ const segmentResult = { word: '', hasHundred: false }
51
58
 
52
59
  /**
53
60
  * Builds words for a 0-999 segment (British-style with "and" after hundreds).
54
- *
55
61
  * @param {number} n - Number 0-999
56
- * @returns {{ word: string, hasHundred: boolean }}
62
+ * @returns {{ word: string, hasHundred: boolean }} The segment words and whether it includes a hundreds place
57
63
  */
58
- function buildSegment (n) {
64
+ function buildSegment(n) {
59
65
  if (n === 0) {
60
66
  segmentResult.word = ''
61
67
  segmentResult.hasHundred = false
@@ -70,9 +76,11 @@ function buildSegment (n) {
70
76
  let tensOnes = ''
71
77
  if (tens === 1) {
72
78
  tensOnes = TEENS[ones]
73
- } else if (tens >= 2) {
79
+ }
80
+ else if (tens >= 2) {
74
81
  tensOnes = ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
75
- } else if (ones > 0) {
82
+ }
83
+ else if (ones > 0) {
76
84
  tensOnes = ONES[ones]
77
85
  }
78
86
 
@@ -80,11 +88,13 @@ function buildSegment (n) {
80
88
  if (hundreds > 0) {
81
89
  if (tensOnes) {
82
90
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED + ' and ' + tensOnes
83
- } else {
91
+ }
92
+ else {
84
93
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED
85
94
  }
86
95
  segmentResult.hasHundred = true
87
- } else {
96
+ }
97
+ else {
88
98
  segmentResult.word = tensOnes
89
99
  segmentResult.hasHundred = false
90
100
  }
@@ -94,11 +104,10 @@ function buildSegment (n) {
94
104
 
95
105
  /**
96
106
  * Builds words for a 0-99 segment (no hundreds).
97
- *
98
107
  * @param {number} n - Number 0-99
99
- * @returns {string}
108
+ * @returns {string} The number in words
100
109
  */
101
- function buildSmallSegment (n) {
110
+ function buildSmallSegment(n) {
102
111
  if (n === 0) return ''
103
112
 
104
113
  const ones = n % 10
@@ -106,7 +115,8 @@ function buildSmallSegment (n) {
106
115
 
107
116
  if (tens === 1) {
108
117
  return TEENS[ones]
109
- } else if (tens >= 2) {
118
+ }
119
+ else if (tens >= 2) {
110
120
  return ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
111
121
  }
112
122
  return ONES[ones]
@@ -121,11 +131,10 @@ function buildSmallSegment (n) {
121
131
  *
122
132
  * Uses BigInt modulo for segment extraction.
123
133
  * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
124
- *
125
134
  * @param {bigint} n - Non-negative integer to convert
126
135
  * @returns {string} English words
127
136
  */
128
- function integerToWords (n) {
137
+ function integerToWords(n) {
129
138
  if (n === 0n) return ZERO
130
139
 
131
140
  // Fast path: numbers < 1000
@@ -162,7 +171,8 @@ function integerToWords (n) {
162
171
  words.push('and')
163
172
  }
164
173
  words.push(word)
165
- } else {
174
+ }
175
+ else {
166
176
  // Other segments are 0-99
167
177
  words.push(buildSmallSegment(segment))
168
178
  // Add scale word
@@ -177,11 +187,10 @@ function integerToWords (n) {
177
187
 
178
188
  /**
179
189
  * Converts decimal digits to English words.
180
- *
181
190
  * @param {string} decimalPart - Decimal digits (without the point)
182
191
  * @returns {string} English words for decimal part
183
192
  */
184
- function decimalPartToWords (decimalPart) {
193
+ function decimalPartToWords(decimalPart) {
185
194
  let result = ''
186
195
 
187
196
  // Handle leading zeros
@@ -204,20 +213,21 @@ function decimalPartToWords (decimalPart) {
204
213
 
205
214
  /**
206
215
  * Converts a numeric value to English words using Indian numbering.
207
- *
208
216
  * @param {number | string | bigint} value - The numeric value to convert
209
217
  * @returns {string} The number in English words
210
218
  * @throws {TypeError} If value is not a valid numeric type
211
219
  * @throws {Error} If value is not a valid number format
212
- *
213
220
  * @example
214
221
  * toCardinal(42) // 'forty-two'
215
222
  * toCardinal(100000) // 'one lakh'
216
223
  * toCardinal(10000000) // 'one crore'
217
224
  * toCardinal(1234567) // 'twelve lakh thirty-four thousand five hundred and sixty-seven'
218
225
  */
219
- function toCardinal (value) {
226
+ function toCardinal(value) {
220
227
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
228
+ // Both the integer part and the decimal's significant digits are spelled via
229
+ // the scale builder, so both must clear the ceiling.
230
+ checkMax(integerPart, cardinalMax, decimalPart)
221
231
 
222
232
  let result = ''
223
233
 
@@ -240,11 +250,10 @@ function toCardinal (value) {
240
250
 
241
251
  /**
242
252
  * Builds ordinal words for a 0-999 segment (final segment only).
243
- *
244
253
  * @param {number} n - Number 0-999
245
254
  * @returns {string} Ordinal words for this segment
246
255
  */
247
- function buildOrdinalSegment (n) {
256
+ function buildOrdinalSegment(n) {
248
257
  const ones = n % 10
249
258
  const tens = Math.trunc(n / 10) % 10
250
259
  const hundreds = Math.trunc(n / 100)
@@ -253,13 +262,16 @@ function buildOrdinalSegment (n) {
253
262
  let tensOnesOrdinal = ''
254
263
  if (tens === 1) {
255
264
  tensOnesOrdinal = ORDINAL_TEENS[ones]
256
- } else if (tens >= 2) {
265
+ }
266
+ else if (tens >= 2) {
257
267
  if (ones > 0) {
258
268
  tensOnesOrdinal = TENS[tens] + '-' + ORDINAL_ONES[ones]
259
- } else {
269
+ }
270
+ else {
260
271
  tensOnesOrdinal = ORDINAL_TENS[tens]
261
272
  }
262
- } else if (ones > 0) {
273
+ }
274
+ else if (ones > 0) {
263
275
  tensOnesOrdinal = ORDINAL_ONES[ones]
264
276
  }
265
277
 
@@ -267,7 +279,8 @@ function buildOrdinalSegment (n) {
267
279
  if (hundreds > 0) {
268
280
  if (tensOnesOrdinal) {
269
281
  return ONES[hundreds] + ' ' + HUNDRED + ' ' + tensOnesOrdinal
270
- } else {
282
+ }
283
+ else {
271
284
  return ONES[hundreds] + ' hundredth'
272
285
  }
273
286
  }
@@ -277,11 +290,10 @@ function buildOrdinalSegment (n) {
277
290
 
278
291
  /**
279
292
  * Converts a positive integer to ordinal words using Indian numbering.
280
- *
281
293
  * @param {bigint} n - Positive integer to convert
282
294
  * @returns {string} Ordinal English words
283
295
  */
284
- function integerToOrdinal (n) {
296
+ function integerToOrdinal(n) {
285
297
  // Fast path: numbers < 1000
286
298
  if (n < 1000n) {
287
299
  return buildOrdinalSegment(Number(n))
@@ -323,16 +335,19 @@ function integerToOrdinal (n) {
323
335
  if (i === 0) {
324
336
  // Units position: use ordinal segment
325
337
  words.push(buildOrdinalSegment(segment))
326
- } else {
338
+ }
339
+ else {
327
340
  // Scale position with no remainder below: "one lakhth"
328
341
  words.push(buildSmallSegment(segment))
329
342
  words.push(SCALES[i - 1] + 'th')
330
343
  }
331
- } else {
344
+ }
345
+ else {
332
346
  // Non-final segments are cardinal
333
347
  if (i === 0) {
334
348
  words.push(buildSegment(segment).word)
335
- } else {
349
+ }
350
+ else {
336
351
  words.push(buildSmallSegment(segment))
337
352
  words.push(SCALES[i - 1])
338
353
  }
@@ -344,19 +359,18 @@ function integerToOrdinal (n) {
344
359
 
345
360
  /**
346
361
  * Converts a numeric value to English ordinal words using Indian numbering.
347
- *
348
362
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
349
363
  * @returns {string} The number as ordinal words
350
364
  * @throws {TypeError} If value is not a valid numeric type
351
365
  * @throws {RangeError} If value is negative, zero, or has a decimal part
352
- *
353
366
  * @example
354
367
  * toOrdinal(1) // 'first'
355
368
  * toOrdinal(100000) // 'one lakhth'
356
369
  * toOrdinal(100001) // 'one lakh first'
357
370
  */
358
- function toOrdinal (value) {
371
+ function toOrdinal(value) {
359
372
  const integerPart = parseOrdinalValue(value)
373
+ checkMax(integerPart, ordinalMax)
360
374
  return integerToOrdinal(integerPart)
361
375
  }
362
376
 
@@ -364,16 +378,21 @@ function toOrdinal (value) {
364
378
  // CURRENCY: toCurrency(value, options?)
365
379
  // ============================================================================
366
380
 
381
+ /**
382
+ * @typedef {object} CurrencyOptions
383
+ * @property {boolean} [and] - Use "and" between rupees and paise
384
+ */
385
+
386
+ /** @type {Required<CurrencyOptions>} */
387
+ export const currencyDefaults = { and: true }
388
+
367
389
  /**
368
390
  * Converts a numeric value to Pakistani English currency words.
369
- *
370
391
  * @param {number | string | bigint} value - The currency amount to convert
371
- * @param {Object} [options] - Optional configuration
372
- * @param {boolean} [options.and=true] - Use "and" between rupees and paise
392
+ * @param {CurrencyOptions} [options] - Optional configuration
373
393
  * @returns {string} The amount in Pakistani English currency words
374
394
  * @throws {TypeError} If value is not a valid numeric type
375
395
  * @throws {Error} If value is not a valid number format
376
- *
377
396
  * @example
378
397
  * toCurrency(42.50) // 'forty-two rupees and fifty paise'
379
398
  * toCurrency(100000) // 'one lakh rupees'
@@ -381,10 +400,10 @@ function toOrdinal (value) {
381
400
  * toCurrency(0.50) // 'fifty paise'
382
401
  * toCurrency(42.50, { and: false }) // 'forty-two rupees fifty paise'
383
402
  */
384
- function toCurrency (value, options) {
385
- options = validateOptions(options)
403
+ function toCurrency(value, options) {
386
404
  const { isNegative, dollars: rupees, cents: paise } = parseCurrencyValue(value)
387
- const { and: useAnd = true } = options
405
+ checkMax(rupees, currencyMax)
406
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
388
407
 
389
408
  // Build result
390
409
  let result = ''
package/src/en-SG.d.ts CHANGED
@@ -1,11 +1,36 @@
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 dollars and cents
7
+ */
8
+ /** @type {Required<CurrencyOptions>} */
9
+ export const currencyDefaults: Required<CurrencyOptions>;
10
+ export type CurrencyOptions = {
11
+ /**
12
+ * - Use "and" between dollars and cents
13
+ */
14
+ and?: boolean | undefined;
15
+ };
1
16
  /**
2
17
  * Converts a numeric value to Singaporean 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 Singaporean 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
+ */
29
+ export function toOrdinal(value: number | string | bigint): string;
30
+ /**
31
+ * Converts a numeric value to Singaporean English currency words.
32
+ * @param {number | string | bigint} value - The currency amount to convert
33
+ * @param {CurrencyOptions} [options] - Optional configuration
34
+ * @returns {string} The amount in Singaporean English currency words
35
+ */
36
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;