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/fr-FR.js CHANGED
@@ -14,7 +14,9 @@
14
14
  import { parseCardinalValue } from './utils/parse-cardinal.js'
15
15
  import { parseCurrencyValue } from './utils/parse-currency.js'
16
16
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
17
- import { validateOptions } from './utils/validate-options.js'
17
+ import { checkMax } from './utils/check-max.js'
18
+ import { longScale } from './utils/scale.js'
19
+ import { resolveOptions } from './utils/resolve-options.js'
18
20
 
19
21
  // ============================================================================
20
22
  // Vocabulary (module-level constants)
@@ -28,6 +30,15 @@ const TENS = ['', '', 'vingt', 'trente', 'quarante', 'cinquante', 'soixante']
28
30
  const SCALES = ['million', 'billion', 'trillion', 'quadrillion']
29
31
  const SCALES_ARD = ['milliard', 'billiard', 'trilliard', 'quadrilliard']
30
32
 
33
+ // Supported magnitude ceiling (checked at the public entry points). Long scale:
34
+ // each SCALES entry has a base and an "-ard" form spanning two segment groups,
35
+ // so with the units group that's 2 * SCALES.length + 2 groups of 3 digits.
36
+ // Cardinals must stay below 10^30; ordinals and currency build on the cardinal,
37
+ // so they share the same ceiling.
38
+ export const cardinalMax = longScale(SCALES.length)
39
+ export const ordinalMax = longScale(SCALES.length)
40
+ export const currencyMax = longScale(SCALES.length)
41
+
31
42
  const THOUSAND = 'mille'
32
43
  const HUNDRED = 'cent'
33
44
  const ZERO = 'zéro'
@@ -60,8 +71,10 @@ const CENTIMES = 'centimes'
60
71
  /**
61
72
  * Builds segment word for 0-999.
62
73
  * Returns object with { word, endsWithCents, endsWithVingts } for pluralization handling.
74
+ * @param {number} n - Segment value (0-999)
75
+ * @returns {{ word: string, endsWithCents: boolean, endsWithVingts: boolean }} Segment words and pluralization flags
63
76
  */
64
- function buildSegment (n) {
77
+ function buildSegment(n) {
65
78
  if (n === 0) return { word: '', endsWithCents: false, endsWithVingts: false }
66
79
 
67
80
  const tensOnes = n % 100
@@ -76,15 +89,18 @@ function buildSegment (n) {
76
89
  if (hundreds === 1) {
77
90
  if (tensOnes === 0) {
78
91
  parts.push(HUNDRED)
79
- } else {
92
+ }
93
+ else {
80
94
  parts.push(HUNDRED)
81
95
  }
82
- } else {
96
+ }
97
+ else {
83
98
  if (tensOnes === 0) {
84
99
  // "deux cents", "trois cents" (with 's')
85
100
  parts.push(ONES[hundreds] + ' ' + HUNDRED + 's')
86
101
  endsWithCents = true
87
- } else {
102
+ }
103
+ else {
88
104
  // "deux cent", "trois cent" (no 's' when followed by more)
89
105
  parts.push(ONES[hundreds] + ' ' + HUNDRED)
90
106
  }
@@ -94,48 +110,59 @@ function buildSegment (n) {
94
110
  // Tens and ones - vigesimal pattern
95
111
  if (tensOnes === 0) {
96
112
  // Just hundreds, nothing more
97
- } else if (tensOnes < 10) {
113
+ }
114
+ else if (tensOnes < 10) {
98
115
  // Single digit
99
116
  parts.push(ONES[tensOnes])
100
- } else if (tensOnes < 17) {
117
+ }
118
+ else if (tensOnes < 17) {
101
119
  // 10-16: regular teens
102
120
  parts.push(TEENS[tensOnes - 10])
103
- } else if (tensOnes < 20) {
121
+ }
122
+ else if (tensOnes < 20) {
104
123
  // 17-19: dix-sept, dix-huit, dix-neuf
105
124
  parts.push(TEENS[tensOnes - 10])
106
- } else if (tensOnes < 70) {
125
+ }
126
+ else if (tensOnes < 70) {
107
127
  // 20-69: standard tens + ones
108
128
  const t = Math.trunc(tensOnes / 10)
109
129
  const o = tensOnes % 10
110
130
  if (o === 0) {
111
131
  parts.push(TENS[t])
112
- } else if (o === 1) {
132
+ }
133
+ else if (o === 1) {
113
134
  // "et un" for 21, 31, 41, 51, 61
114
135
  parts.push(TENS[t] + ' et ' + ONES[1])
115
- } else {
136
+ }
137
+ else {
116
138
  parts.push(TENS[t] + '-' + ONES[o])
117
139
  }
118
- } else if (tensOnes < 80) {
140
+ }
141
+ else if (tensOnes < 80) {
119
142
  // 70-79: soixante-dix, soixante et onze, soixante-douze...
120
143
  const remainder = tensOnes - 60
121
144
  if (remainder === 11) {
122
145
  // 71: soixante et onze
123
146
  parts.push('soixante et onze')
124
- } else {
147
+ }
148
+ else {
125
149
  // 70, 72-79: soixante-dix, soixante-douze...
126
150
  parts.push('soixante-' + TEENS[remainder - 10])
127
151
  }
128
- } else if (tensOnes === 80) {
152
+ }
153
+ else if (tensOnes === 80) {
129
154
  // 80: quatre-vingts (with 's')
130
155
  parts.push('quatre-vingts')
131
156
  endsWithVingts = true
132
- } else if (tensOnes < 100) {
157
+ }
158
+ else if (tensOnes < 100) {
133
159
  // 81-99: quatre-vingt-un, quatre-vingt-dix...
134
160
  const remainder = tensOnes - 80
135
161
  if (remainder < 10) {
136
162
  // 81-89
137
163
  parts.push('quatre-vingt-' + ONES[remainder])
138
- } else {
164
+ }
165
+ else {
139
166
  // 90-99
140
167
  parts.push('quatre-vingt-' + TEENS[remainder - 10])
141
168
  }
@@ -151,12 +178,11 @@ function buildSegment (n) {
151
178
 
152
179
  /**
153
180
  * Gets scale word for French long scale with -ard pattern.
154
- *
155
181
  * @param {number} scaleIndex - Scale level (1 = thousand, 2 = million, etc.)
156
182
  * @param {bigint} segment - Segment value for pluralization
157
183
  * @returns {string} Scale word
158
184
  */
159
- function getScaleWord (scaleIndex, segment) {
185
+ function getScaleWord(scaleIndex, segment) {
160
186
  if (scaleIndex === 1) return THOUSAND
161
187
 
162
188
  // Even indices (2, 4, 6, 8): million, billion, trillion, quadrillion
@@ -166,7 +192,8 @@ function getScaleWord (scaleIndex, segment) {
166
192
  const baseWord = SCALES[arrayIndex]
167
193
  if (!baseWord) return ''
168
194
  return segment > 1n ? baseWord + 's' : baseWord
169
- } else {
195
+ }
196
+ else {
170
197
  const arrayIndex = ((scaleIndex - 1) / 2) - 1
171
198
  const ardWord = SCALES_ARD[arrayIndex]
172
199
  if (!ardWord) return THOUSAND
@@ -180,12 +207,11 @@ function getScaleWord (scaleIndex, segment) {
180
207
 
181
208
  /**
182
209
  * Converts a non-negative integer to French words.
183
- *
184
210
  * @param {bigint} n - Non-negative integer to convert
185
211
  * @param {boolean} withHyphen - Whether to use hyphen separators
186
212
  * @returns {string} French words
187
213
  */
188
- function integerToWords (n, withHyphen = false) {
214
+ function integerToWords(n, withHyphen = false) {
189
215
  if (n === 0n) return ZERO
190
216
 
191
217
  // Fast path: numbers < 1000
@@ -203,7 +229,8 @@ function integerToWords (n, withHyphen = false) {
203
229
  if (thousands === 1) {
204
230
  // "mille" not "un mille"
205
231
  result = THOUSAND
206
- } else {
232
+ }
233
+ else {
207
234
  // Check if segment ends with "cents" or "vingts" - need to strip 's' before mille
208
235
  const { word: thousandsWord, endsWithCents, endsWithVingts } = buildSegment(thousands)
209
236
  let adjustedWord = thousandsWord
@@ -231,12 +258,11 @@ function integerToWords (n, withHyphen = false) {
231
258
 
232
259
  /**
233
260
  * Builds words for numbers >= 1,000,000.
234
- *
235
261
  * @param {bigint} n - Number >= 1,000,000
236
262
  * @param {boolean} withHyphen - Whether to use hyphen separators
237
263
  * @returns {string} French words
238
264
  */
239
- function buildLargeNumberWords (n, withHyphen) {
265
+ function buildLargeNumberWords(n, withHyphen) {
240
266
  const numStr = n.toString()
241
267
  const len = numStr.length
242
268
 
@@ -269,11 +295,13 @@ function buildLargeNumberWords (n, withHyphen) {
269
295
  if (scaleIndex === 0) {
270
296
  // Units segment
271
297
  parts.push(segWords)
272
- } else if (scaleIndex === 1) {
298
+ }
299
+ else if (scaleIndex === 1) {
273
300
  // Thousands: "mille" not "un mille"
274
301
  if (segment === 1) {
275
302
  parts.push(THOUSAND)
276
- } else {
303
+ }
304
+ else {
277
305
  // Strip 's' from cents/vingts before mille
278
306
  let adjustedWord = segWords
279
307
  if (endsWithCents || endsWithVingts) {
@@ -282,7 +310,8 @@ function buildLargeNumberWords (n, withHyphen) {
282
310
  parts.push(adjustedWord)
283
311
  parts.push(scaleWord)
284
312
  }
285
- } else {
313
+ }
314
+ else {
286
315
  // Million and above
287
316
  parts.push(segWords)
288
317
  parts.push(scaleWord)
@@ -304,12 +333,11 @@ function buildLargeNumberWords (n, withHyphen) {
304
333
 
305
334
  /**
306
335
  * Converts decimal digits to French words.
307
- *
308
336
  * @param {string} decimalPart - Decimal digits (without the point)
309
337
  * @param {boolean} withHyphen - Whether to use hyphen separators
310
338
  * @returns {string} French words for decimal part
311
339
  */
312
- function decimalPartToWords (decimalPart, withHyphen) {
340
+ function decimalPartToWords(decimalPart, withHyphen) {
313
341
  let result = ''
314
342
  const sep = withHyphen ? '-' : ' '
315
343
 
@@ -331,30 +359,37 @@ function decimalPartToWords (decimalPart, withHyphen) {
331
359
  return result
332
360
  }
333
361
 
362
+ /**
363
+ * @typedef {object} CardinalOptions
364
+ * @property {boolean} [withHyphenSeparator] - Use hyphens between all words
365
+ */
366
+
367
+ /** @type {Required<CardinalOptions>} */
368
+ export const cardinalDefaults = { withHyphenSeparator: false }
369
+
334
370
  /**
335
371
  * Converts a numeric value to French words.
336
372
  *
337
373
  * This is the main public API. It accepts any valid numeric input
338
374
  * (number, string, or bigint) and handles parsing internally.
339
- *
340
375
  * @param {number | string | bigint} value - The numeric value to convert
341
- * @param {Object} [options] - Optional configuration
342
- * @param {boolean} [options.withHyphenSeparator=false] - Use hyphens between all words
376
+ * @param {CardinalOptions} [options] - Optional configuration
343
377
  * @returns {string} The number in French words
344
378
  * @throws {TypeError} If value is not a valid numeric type
345
379
  * @throws {Error} If value is not a valid number format
346
- *
347
380
  * @example
348
381
  * toCardinal(21) // 'vingt et un'
349
382
  * toCardinal(80) // 'quatre-vingts'
350
383
  * toCardinal(1000000) // 'un million'
351
384
  */
352
- function toCardinal (value, options) {
353
- options = validateOptions(options)
385
+ function toCardinal(value, options) {
354
386
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
387
+ // Both the integer part and the decimal's significant digits are spelled via
388
+ // the scale builder, so both must clear the ceiling.
389
+ checkMax(integerPart, cardinalMax, decimalPart)
355
390
 
356
391
  // Apply option defaults
357
- const { withHyphenSeparator = false } = options
392
+ const { withHyphenSeparator } = resolveOptions(options, cardinalDefaults)
358
393
 
359
394
  let result = ''
360
395
  const sep = withHyphenSeparator ? '-' : ' '
@@ -383,11 +418,10 @@ function toCardinal (value, options) {
383
418
  * - Drop final -e before adding -ième (quatre → quatrième)
384
419
  * - cinq → cinquième (add -u- before -ième)
385
420
  * - neuf → neuvième (f → v before -ième)
386
- *
387
421
  * @param {string} cardinalWord - Cardinal word to convert
388
422
  * @returns {string} Ordinal form
389
423
  */
390
- function cardinalToOrdinal (cardinalWord) {
424
+ function cardinalToOrdinal(cardinalWord) {
391
425
  // Handle special endings
392
426
  if (cardinalWord.endsWith('cinq')) {
393
427
  // cinq → cinquième (add 'u')
@@ -401,16 +435,16 @@ function cardinalToOrdinal (cardinalWord) {
401
435
 
402
436
  // Drop plural -s from cents/vingts/millions/etc. (quatre-vingts → quatre-vingtième)
403
437
  // Note: "trois", "six" also end in s but that's not a plural
404
- if (cardinalWord.endsWith('cents') ||
405
- cardinalWord.endsWith('vingts') ||
406
- cardinalWord.endsWith('millions') ||
407
- cardinalWord.endsWith('milliards') ||
408
- cardinalWord.endsWith('billions') ||
409
- cardinalWord.endsWith('billiards') ||
410
- cardinalWord.endsWith('trillions') ||
411
- cardinalWord.endsWith('trilliards') ||
412
- cardinalWord.endsWith('quadrillions') ||
413
- cardinalWord.endsWith('quadrilliards')) {
438
+ if (cardinalWord.endsWith('cents')
439
+ || cardinalWord.endsWith('vingts')
440
+ || cardinalWord.endsWith('millions')
441
+ || cardinalWord.endsWith('milliards')
442
+ || cardinalWord.endsWith('billions')
443
+ || cardinalWord.endsWith('billiards')
444
+ || cardinalWord.endsWith('trillions')
445
+ || cardinalWord.endsWith('trilliards')
446
+ || cardinalWord.endsWith('quadrillions')
447
+ || cardinalWord.endsWith('quadrilliards')) {
414
448
  return cardinalWord.slice(0, -1) + ORDINAL_SUFFIX
415
449
  }
416
450
 
@@ -425,11 +459,10 @@ function cardinalToOrdinal (cardinalWord) {
425
459
 
426
460
  /**
427
461
  * Converts a positive integer to French ordinal words.
428
- *
429
462
  * @param {bigint} n - Positive integer
430
463
  * @returns {string} French ordinal words
431
464
  */
432
- function integerToOrdinal (n) {
465
+ function integerToOrdinal(n) {
433
466
  // Special case: 1 → premier
434
467
  if (n === 1n) {
435
468
  return PREMIER
@@ -445,12 +478,10 @@ function integerToOrdinal (n) {
445
478
  *
446
479
  * French ordinals: premier (1st), then cardinal + ième.
447
480
  * Special rules: quatre→quatrième, cinq→cinquième, neuf→neuvième.
448
- *
449
481
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
450
482
  * @returns {string} The number as ordinal words
451
483
  * @throws {TypeError} If value is not a valid numeric type
452
484
  * @throws {RangeError} If value is negative, zero, or has a decimal part
453
- *
454
485
  * @example
455
486
  * toOrdinal(1) // 'premier'
456
487
  * toOrdinal(2) // 'deuxième'
@@ -461,8 +492,9 @@ function integerToOrdinal (n) {
461
492
  * toOrdinal(100) // 'centième'
462
493
  * toOrdinal(1000) // 'millième'
463
494
  */
464
- function toOrdinal (value) {
495
+ function toOrdinal(value) {
465
496
  const integerPart = parseOrdinalValue(value)
497
+ checkMax(integerPart, ordinalMax)
466
498
  return integerToOrdinal(integerPart)
467
499
  }
468
500
 
@@ -470,16 +502,21 @@ function toOrdinal (value) {
470
502
  // CURRENCY: toCurrency(value, options?)
471
503
  // ============================================================================
472
504
 
505
+ /**
506
+ * @typedef {object} CurrencyOptions
507
+ * @property {boolean} [and] - Use "et" between euros and centimes
508
+ */
509
+
510
+ /** @type {Required<CurrencyOptions>} */
511
+ export const currencyDefaults = { and: true }
512
+
473
513
  /**
474
514
  * Converts a numeric value to French currency words (Euro).
475
- *
476
515
  * @param {number | string | bigint} value - The currency amount to convert
477
- * @param {Object} [options] - Optional configuration
478
- * @param {boolean} [options.and=true] - Use "et" between euros and centimes
516
+ * @param {CurrencyOptions} [options] - Optional configuration
479
517
  * @returns {string} The amount in French currency words
480
518
  * @throws {TypeError} If value is not a valid numeric type
481
519
  * @throws {Error} If value is not a valid number format
482
- *
483
520
  * @example
484
521
  * toCurrency(42.50) // 'quarante-deux euros et cinquante centimes'
485
522
  * toCurrency(1) // 'un euro'
@@ -487,10 +524,10 @@ function toOrdinal (value) {
487
524
  * toCurrency(0.01) // 'un centime'
488
525
  * toCurrency(42.50, { and: false }) // 'quarante-deux euros cinquante centimes'
489
526
  */
490
- function toCurrency (value, options) {
491
- options = validateOptions(options)
527
+ function toCurrency(value, options) {
492
528
  const { isNegative, dollars: euros, cents: centimes } = parseCurrencyValue(value)
493
- const { and: useAnd = true } = options
529
+ checkMax(euros, currencyMax)
530
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
494
531
 
495
532
  // Build result
496
533
  let result = ''
package/src/gu-IN.d.ts CHANGED
@@ -1,18 +1,18 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
1
4
  /**
2
5
  * Converts a numeric value to Gujarati words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Gujarati words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Gujarati ordinal words.
10
- *
11
12
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
12
13
  * @returns {string} The number as ordinal words
13
14
  * @throws {TypeError} If value is not a valid numeric type
14
15
  * @throws {RangeError} If value is negative, zero, or has a decimal part
15
- *
16
16
  * @example
17
17
  * toOrdinal(1) // 'પહેલું'
18
18
  * toOrdinal(2) // 'બીજું'
@@ -21,12 +21,10 @@ export function toCardinal(value: number | string | bigint): string;
21
21
  export function toOrdinal(value: number | string | bigint): string;
22
22
  /**
23
23
  * Converts a numeric value to Gujarati currency words (Indian Rupee).
24
- *
25
24
  * @param {number | string | bigint} value - The currency amount to convert
26
25
  * @returns {string} The amount in Gujarati currency words
27
26
  * @throws {TypeError} If value is not a valid numeric type
28
27
  * @throws {Error} If value is not a valid number format
29
- *
30
28
  * @example
31
29
  * toCurrency(42.50) // 'બેતાળીસ રૂપિયા પચાસ પૈસા'
32
30
  * toCurrency(1) // 'એક રૂપિયો'
package/src/gu-IN.js CHANGED
@@ -14,6 +14,8 @@
14
14
  import { parseCardinalValue } from './utils/parse-cardinal.js'
15
15
  import { parseCurrencyValue } from './utils/parse-currency.js'
16
16
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
17
+ import { checkMax } from './utils/check-max.js'
18
+ import { indian } from './utils/scale.js'
17
19
 
18
20
  // ============================================================================
19
21
  // Vocabulary
@@ -56,20 +58,29 @@ const BELOW_HUNDRED = [
56
58
  'સાઠ', 'એકસઠ', 'બાસઠ', 'ત્રેસઠ', 'ચોસઠ', 'પાંસઠ', 'છાસઠ', 'સડસઠ', 'અડસઠ', 'અગણોસિત્તેર',
57
59
  'સિત્તેર', 'એકોતેર', 'બોતેર', 'તોતેર', 'ચુમોતેર', 'પંચોતેર', 'છોતેર', 'સિત્યોતેર', 'ઇઠ્યોતેર', 'ઓગણાએંસી',
58
60
  'એંસી', 'એક્યાસી', 'બ્યાસી', 'ત્યાસી', 'ચોર્યાસી', 'પંચાસી', 'છ્યાસી', 'સિત્યાસી', 'અઠ્યાસી', 'નેવ્યાસી',
59
- 'નેવું', 'એકાણું', 'બાણું', 'ત્રાણું', 'ચોરાણું', 'પંચાણું', 'છન્નું', 'સત્તાણું', 'અઠ્ઠાણું', 'નવ્વાણું'
61
+ 'નેવું', 'એકાણું', 'બાણું', 'ત્રાણું', 'ચોરાણું', 'પંચાણું', 'છન્નું', 'સત્તાણું', 'અઠ્ઠાણું', 'નવ્વાણું',
60
62
  ]
61
63
 
62
64
  // Scale words: index 0 = units (empty), 1 = thousand, 2 = lakh, 3 = crore, etc.
63
65
  const SCALE_WORDS = ['', 'હજાર', 'લાખ', 'કરોડ', 'અબજ', 'ખરબ', 'નીલ', 'પદ્મ', 'શંખ']
64
66
 
67
+ // 3-2-2 Indian grouping: a 3-digit base segment, then 2 digits per scale word
68
+ // (SCALE_WORDS[0] = '' is the units slot). Past the table the scale word is
69
+ // dropped, which collapses the magnitude — so cap there.
70
+ export const cardinalMax = indian(SCALE_WORDS.length)
71
+ export const ordinalMax = indian(SCALE_WORDS.length)
72
+ export const currencyMax = indian(SCALE_WORDS.length)
73
+
65
74
  // ============================================================================
66
75
  // Segment Building
67
76
  // ============================================================================
68
77
 
69
78
  /**
70
79
  * Builds words for a 0-999 segment.
80
+ * @param {number} n - Segment value (0-999)
81
+ * @returns {string} Gujarati words for the segment
71
82
  */
72
- function buildSegment (n) {
83
+ function buildSegment(n) {
73
84
  if (n === 0) return ''
74
85
  if (n < 100) return BELOW_HUNDRED[n]
75
86
 
@@ -91,11 +102,10 @@ function buildSegment (n) {
91
102
  *
92
103
  * Uses BigInt modulo for segment extraction (faster than string slicing).
93
104
  * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
94
- *
95
105
  * @param {bigint} n - Non-negative integer to convert
96
106
  * @returns {string} Gujarati words
97
107
  */
98
- function integerToWords (n) {
108
+ function integerToWords(n) {
99
109
  if (n === 0n) return ZERO
100
110
 
101
111
  // Fast path: numbers < 1000 (direct lookup)
@@ -121,7 +131,8 @@ function integerToWords (n) {
121
131
 
122
132
  if (i === 0) {
123
133
  words.push(buildSegment(segment))
124
- } else {
134
+ }
135
+ else {
125
136
  words.push(BELOW_HUNDRED[segment])
126
137
  }
127
138
 
@@ -133,7 +144,12 @@ function integerToWords (n) {
133
144
  return words.join(' ')
134
145
  }
135
146
 
136
- function decimalPartToWords (decimalPart) {
147
+ /**
148
+ * Converts the fractional digits to per-digit Gujarati words.
149
+ * @param {string} decimalPart - Decimal digits as a string
150
+ * @returns {string} Gujarati words for each digit
151
+ */
152
+ function decimalPartToWords(decimalPart) {
137
153
  // Per-digit decimal reading
138
154
  const digits = []
139
155
  for (const char of decimalPart) {
@@ -145,12 +161,13 @@ function decimalPartToWords (decimalPart) {
145
161
 
146
162
  /**
147
163
  * Converts a numeric value to Gujarati words.
148
- *
149
164
  * @param {number | string | bigint} value - The numeric value to convert
150
165
  * @returns {string} The number in Gujarati words
151
166
  */
152
- function toCardinal (value) {
167
+ function toCardinal(value) {
153
168
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
169
+ // The fraction is spelled digit by digit, so only the integer part has a ceiling.
170
+ checkMax(integerPart, cardinalMax)
154
171
 
155
172
  let result = ''
156
173
 
@@ -175,11 +192,10 @@ function toCardinal (value) {
175
192
  * Converts a positive integer to Gujarati ordinal words.
176
193
  *
177
194
  * Gujarati ordinals: First 6 are irregular, then add -મું suffix.
178
- *
179
195
  * @param {bigint} n - Positive integer to convert
180
196
  * @returns {string} Gujarati ordinal words
181
197
  */
182
- function integerToOrdinal (n) {
198
+ function integerToOrdinal(n) {
183
199
  // Special ordinals for 1-6
184
200
  if (n >= 1n && n <= 6n) {
185
201
  return ORDINAL_SPECIAL[Number(n)]
@@ -192,19 +208,19 @@ function integerToOrdinal (n) {
192
208
 
193
209
  /**
194
210
  * Converts a numeric value to Gujarati ordinal words.
195
- *
196
211
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
197
212
  * @returns {string} The number as ordinal words
198
213
  * @throws {TypeError} If value is not a valid numeric type
199
214
  * @throws {RangeError} If value is negative, zero, or has a decimal part
200
- *
201
215
  * @example
202
216
  * toOrdinal(1) // 'પહેલું'
203
217
  * toOrdinal(2) // 'બીજું'
204
218
  * toOrdinal(10) // 'દસમું'
205
219
  */
206
- function toOrdinal (value) {
220
+ function toOrdinal(value) {
207
221
  const integerPart = parseOrdinalValue(value)
222
+ // Ordinals build on the cardinal speller, so they share its ceiling.
223
+ checkMax(integerPart, ordinalMax)
208
224
  return integerToOrdinal(integerPart)
209
225
  }
210
226
 
@@ -214,19 +230,18 @@ function toOrdinal (value) {
214
230
 
215
231
  /**
216
232
  * Converts a numeric value to Gujarati currency words (Indian Rupee).
217
- *
218
233
  * @param {number | string | bigint} value - The currency amount to convert
219
234
  * @returns {string} The amount in Gujarati currency words
220
235
  * @throws {TypeError} If value is not a valid numeric type
221
236
  * @throws {Error} If value is not a valid number format
222
- *
223
237
  * @example
224
238
  * toCurrency(42.50) // 'બેતાળીસ રૂપિયા પચાસ પૈસા'
225
239
  * toCurrency(1) // 'એક રૂપિયો'
226
240
  * toCurrency(0.01) // 'એક પૈસો'
227
241
  */
228
- function toCurrency (value) {
242
+ function toCurrency(value) {
229
243
  const { isNegative, dollars: rupees, cents: paise } = parseCurrencyValue(value)
244
+ checkMax(rupees, currencyMax)
230
245
 
231
246
  // Build result
232
247
  let result = ''
package/src/ha-NG.d.ts CHANGED
@@ -1,18 +1,18 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
1
4
  /**
2
5
  * Converts a numeric value to Hausa words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Hausa words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Hausa ordinal words.
10
- *
11
12
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
12
13
  * @returns {string} The number as ordinal words
13
14
  * @throws {TypeError} If value is not a valid numeric type
14
15
  * @throws {RangeError} If value is negative, zero, or has a decimal part
15
- *
16
16
  * @example
17
17
  * toOrdinal(1) // 'na farko'
18
18
  * toOrdinal(2) // 'na biyu'
@@ -23,12 +23,10 @@ export function toOrdinal(value: number | string | bigint): string;
23
23
  * Converts a numeric value to Hausa currency words (Nigerian Naira).
24
24
  *
25
25
  * Uses naira and kobo (100 kobo = 1 naira).
26
- *
27
26
  * @param {number | string | bigint} value - The currency amount to convert
28
27
  * @returns {string} The amount in Hausa currency words
29
28
  * @throws {TypeError} If value is not a valid numeric type
30
29
  * @throws {Error} If value is not a valid number format
31
- *
32
30
  * @example
33
31
  * toCurrency(42) // 'arba'in da biyu naira'
34
32
  * toCurrency(1.50) // 'ɗaya naira da hamsin kobo'