n2words 5.0.0 → 5.1.1

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 +133 -40
  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 +33 -24
  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 +106 -43
  266. package/src/sr-Latn-RS.d.ts +35 -15
  267. package/src/sr-Latn-RS.js +106 -43
  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/lv-LV.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 { western } from './utils/scale.js'
19
+ import { resolveOptions } from './utils/resolve-options.js'
18
20
 
19
21
  // ============================================================================
20
22
  // Vocabulary (module-level constants)
@@ -72,9 +74,18 @@ const SCALE_FORMS = [
72
74
  ['kvintiljons', 'kvintiljoni', 'kvintiljonu'],
73
75
  ['sikstiljons', 'sikstiljoni', 'sikstiljonu'],
74
76
  ['septiljons', 'septiljoni', 'septiljonu'],
75
- ['oktiljons', 'oktiljoni', 'oktiljonu']
77
+ ['oktiljons', 'oktiljoni', 'oktiljonu'],
76
78
  ]
77
79
 
80
+ // Supported magnitude ceilings (checked at the public entry points). Both
81
+ // tables are indexed [scaleIndex - 1] (units separate), so the cardinal/
82
+ // currency ceiling is 10^((SCALE_FORMS.length + 1) * 3) = 10^30, and the
83
+ // shorter ORDINAL_SCALES bounds ordinals at 10^((ORDINAL_SCALES.length + 1) *
84
+ // 3) = 10^15.
85
+ export const cardinalMax = western(SCALE_FORMS.length)
86
+ export const ordinalMax = western(ORDINAL_SCALES.length)
87
+ export const currencyMax = western(SCALE_FORMS.length)
88
+
78
89
  // ============================================================================
79
90
  // Segment Building
80
91
  // ============================================================================
@@ -83,8 +94,10 @@ const SCALE_FORMS = [
83
94
  * Builds segment word for 0-999 (masculine form).
84
95
  * Does NOT include special handling for segment=1 (omitOneBeforeScale).
85
96
  * That's handled at join time.
97
+ * @param {number} n - Segment value 0-999
98
+ * @returns {string} The segment in Latvian words
86
99
  */
87
- function buildSegment (n) {
100
+ function buildSegment(n) {
88
101
  if (n === 0) return ''
89
102
 
90
103
  const ones = n % 10
@@ -98,11 +111,13 @@ function buildSegment (n) {
98
111
  if (hundreds === 1 && tens === 0 && ones > 0) {
99
112
  // 101-109: use genitive form "simtu"
100
113
  parts.push(HUNDRED_GENITIVE)
101
- } else if (hundreds > 1) {
114
+ }
115
+ else if (hundreds > 1) {
102
116
  // 200-999: use plural "simti"
103
117
  parts.push(ONES_MASC[hundreds])
104
118
  parts.push(HUNDRED_PLURAL)
105
- } else {
119
+ }
120
+ else {
106
121
  // 100, 110-199: use singular "simts"
107
122
  parts.push(HUNDRED_SINGULAR)
108
123
  }
@@ -116,7 +131,8 @@ function buildSegment (n) {
116
131
  // Teens or ones
117
132
  if (tens === 1) {
118
133
  parts.push(TEENS[ones])
119
- } else if (ones > 0) {
134
+ }
135
+ else if (ones > 0) {
120
136
  parts.push(ONES_MASC[ones])
121
137
  }
122
138
 
@@ -125,8 +141,10 @@ function buildSegment (n) {
125
141
 
126
142
  /**
127
143
  * Builds segment word for 0-999 (feminine form - only differs in ones).
144
+ * @param {number} n - Segment value 0-999
145
+ * @returns {string} The segment in Latvian words
128
146
  */
129
- function buildSegmentFeminine (n) {
147
+ function buildSegmentFeminine(n) {
130
148
  if (n === 0) return ''
131
149
 
132
150
  const ones = n % 10
@@ -139,10 +157,12 @@ function buildSegmentFeminine (n) {
139
157
  if (hundreds > 0) {
140
158
  if (hundreds === 1 && tens === 0 && ones > 0) {
141
159
  parts.push(HUNDRED_GENITIVE)
142
- } else if (hundreds > 1) {
160
+ }
161
+ else if (hundreds > 1) {
143
162
  parts.push(ONES_MASC[hundreds])
144
163
  parts.push(HUNDRED_PLURAL)
145
- } else {
164
+ }
165
+ else {
146
166
  parts.push(HUNDRED_SINGULAR)
147
167
  }
148
168
  }
@@ -155,7 +175,8 @@ function buildSegmentFeminine (n) {
155
175
  // Teens or ones - feminine for ones only
156
176
  if (tens === 1) {
157
177
  parts.push(TEENS[ones])
158
- } else if (ones > 0) {
178
+ }
179
+ else if (ones > 0) {
159
180
  parts.push(ONES_FEM[ones])
160
181
  }
161
182
 
@@ -170,12 +191,11 @@ function buildSegmentFeminine (n) {
170
191
  * Latvian pluralization - simpler than Slavic.
171
192
  * Singular: ends in 1 (except 11)
172
193
  * Plural: everything else
173
- *
174
194
  * @param {number} n - The segment value
175
195
  * @param {string[]} forms - [singular, plural, genitive]
176
196
  * @returns {string} The appropriate form
177
197
  */
178
- function pluralize (n, forms) {
198
+ function pluralize(n, forms) {
179
199
  if (n === 0) return forms[2]
180
200
 
181
201
  const lastDigit = n % 10
@@ -191,12 +211,11 @@ function pluralize (n, forms) {
191
211
  /**
192
212
  * Latvian currency pluralization.
193
213
  * Uses genitive for 0, 10-19, and multiples of 10.
194
- *
195
214
  * @param {number} n - The segment value
196
215
  * @param {string[]} forms - [singular, plural, genitive]
197
216
  * @returns {string} The appropriate form
198
217
  */
199
- function pluralizeCurrency (n, forms) {
218
+ function pluralizeCurrency(n, forms) {
200
219
  if (n === 0) return forms[2]
201
220
 
202
221
  const lastDigit = n % 10
@@ -227,12 +246,11 @@ function pluralizeCurrency (n, forms) {
227
246
 
228
247
  /**
229
248
  * Converts a non-negative integer to Latvian words.
230
- *
231
249
  * @param {bigint} n - Non-negative integer to convert
232
- * @param {Object} options - Conversion options
250
+ * @param {string} gender - Gender for numbers < 1000
233
251
  * @returns {string} Latvian words
234
252
  */
235
- function integerToWords (n, gender) {
253
+ function integerToWords(n, gender) {
236
254
  if (n === 0n) return ZERO
237
255
 
238
256
  // Fast path: numbers < 1000
@@ -243,17 +261,15 @@ function integerToWords (n, gender) {
243
261
 
244
262
  // For numbers >= 1000, feminine only applies to final segment if < 1000
245
263
  // But we use masculine for all segments when n >= 1000
246
- return buildLargeNumberWords(n, gender)
264
+ return buildLargeNumberWords(n)
247
265
  }
248
266
 
249
267
  /**
250
268
  * Builds words for numbers >= 1000.
251
- *
252
269
  * @param {bigint} n - Number >= 1000
253
- * @param {Object} options - Conversion options
254
270
  * @returns {string} Latvian words
255
271
  */
256
- function buildLargeNumberWords (n, gender) {
272
+ function buildLargeNumberWords(n) {
257
273
  const numStr = n.toString()
258
274
  const len = numStr.length
259
275
 
@@ -285,7 +301,8 @@ function buildLargeNumberWords (n, gender) {
285
301
  if (scaleIndex === 0) {
286
302
  // Units segment - use masculine (feminine doesn't apply when n >= 1000)
287
303
  parts.push(segmentWord)
288
- } else {
304
+ }
305
+ else {
289
306
  // Segment with scale word
290
307
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
291
308
  const scaleWord = pluralize(segment, scaleForms)
@@ -293,7 +310,8 @@ function buildLargeNumberWords (n, gender) {
293
310
  // Latvian omits "one" before scale words
294
311
  if (segment === 1) {
295
312
  parts.push(scaleWord)
296
- } else {
313
+ }
314
+ else {
297
315
  parts.push(segmentWord + ' ' + scaleWord)
298
316
  }
299
317
  }
@@ -307,12 +325,11 @@ function buildLargeNumberWords (n, gender) {
307
325
 
308
326
  /**
309
327
  * Converts decimal digits to Latvian words.
310
- *
311
328
  * @param {string} decimalPart - Decimal digits (without the point)
312
- * @param {Object} options - Conversion options
329
+ * @param {string} gender - Gender for numbers < 1000
313
330
  * @returns {string} Latvian words for decimal part
314
331
  */
315
- function decimalPartToWords (decimalPart, gender) {
332
+ function decimalPartToWords(decimalPart, gender) {
316
333
  let result = ''
317
334
 
318
335
  // Handle leading zeros
@@ -333,27 +350,37 @@ function decimalPartToWords (decimalPart, gender) {
333
350
  return result
334
351
  }
335
352
 
353
+ /**
354
+ * @typedef {object} CardinalOptions
355
+ * @property {('masculine'|'feminine')} [gender] - Gender for numbers < 1000
356
+ */
357
+
358
+ /** @type {Required<CardinalOptions>} */
359
+ export const cardinalDefaults = { gender: 'masculine' }
360
+
361
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
362
+ export const cardinalValues = { gender: ['masculine', 'feminine'] }
363
+
336
364
  /**
337
365
  * Converts a numeric value to Latvian words.
338
- *
339
366
  * @param {number | string | bigint} value - The numeric value to convert
340
- * @param {Object} [options] - Conversion options
341
- * @param {string} [options.gender='masculine'] - Gender for numbers < 1000
367
+ * @param {CardinalOptions} [options] - Conversion options
342
368
  * @returns {string} The number in Latvian words
343
369
  * @throws {TypeError} If value is not a valid numeric type
344
370
  * @throws {Error} If value is not a valid number format
345
- *
346
371
  * @example
347
372
  * toCardinal(42) // 'četrdesmit divi'
348
373
  * toCardinal(1, { gender: 'feminine' }) // 'viena'
349
374
  * toCardinal(1000) // 'tūkstotis'
350
375
  */
351
- function toCardinal (value, options) {
352
- options = validateOptions(options)
376
+ function toCardinal(value, options) {
353
377
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
378
+ // Both the integer part and the decimal's significant digits are spelled via
379
+ // the scale builder, so both must clear the ceiling.
380
+ checkMax(integerPart, cardinalMax, decimalPart)
354
381
 
355
382
  // Apply option defaults
356
- const { gender = 'masculine' } = options
383
+ const { gender } = resolveOptions(options, cardinalDefaults, cardinalValues)
357
384
 
358
385
  let result = ''
359
386
 
@@ -376,11 +403,10 @@ function toCardinal (value, options) {
376
403
 
377
404
  /**
378
405
  * Builds ordinal for a 0-99 segment when it's the final (ordinal) part.
379
- *
380
406
  * @param {number} n - Number 0-99
381
407
  * @returns {string} Ordinal words
382
408
  */
383
- function buildOrdinalTensOnes (n) {
409
+ function buildOrdinalTensOnes(n) {
384
410
  if (n === 0) return ''
385
411
 
386
412
  const onesDigit = n % 10
@@ -403,11 +429,10 @@ function buildOrdinalTensOnes (n) {
403
429
 
404
430
  /**
405
431
  * Converts a positive integer to Latvian ordinal words (masculine nominative).
406
- *
407
432
  * @param {bigint} n - Positive integer to convert
408
433
  * @returns {string} Ordinal Latvian words
409
434
  */
410
- function integerToOrdinal (n) {
435
+ function integerToOrdinal(n) {
411
436
  if (n < 100n) {
412
437
  return buildOrdinalTensOnes(Number(n))
413
438
  }
@@ -425,7 +450,8 @@ function integerToOrdinal (n) {
425
450
  let hundredWord
426
451
  if (hundredsDigit === 1) {
427
452
  hundredWord = HUNDRED_SINGULAR
428
- } else {
453
+ }
454
+ else {
429
455
  hundredWord = ONES_MASC[hundredsDigit] + ' ' + HUNDRED_PLURAL
430
456
  }
431
457
  return hundredWord + ' ' + buildOrdinalTensOnes(remainder)
@@ -454,11 +480,10 @@ function integerToOrdinal (n) {
454
480
 
455
481
  /**
456
482
  * Builds ordinal words for numbers >= 1,000,000.
457
- *
458
483
  * @param {bigint} n - Number >= 1,000,000
459
484
  * @returns {string} Ordinal Latvian words
460
485
  */
461
- function buildLargeOrdinal (n) {
486
+ function buildLargeOrdinal(n) {
462
487
  const numStr = n.toString()
463
488
  const len = numStr.length
464
489
 
@@ -493,25 +518,30 @@ function buildLargeOrdinal (n) {
493
518
  if (scaleIndex === 0) {
494
519
  if (isLastNonZero) {
495
520
  parts.push(integerToOrdinal(BigInt(segment)))
496
- } else {
521
+ }
522
+ else {
497
523
  parts.push(buildSegment(segment))
498
524
  }
499
- } else {
525
+ }
526
+ else {
500
527
  if (isLastNonZero) {
501
528
  if (segment === 1) {
502
529
  parts.push(ORDINAL_SCALES[scaleIndex - 1])
503
- } else {
530
+ }
531
+ else {
504
532
  // For 2+, include cardinal scale word before ordinal
505
533
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
506
534
  const cardinalScaleWord = pluralize(segment, scaleForms)
507
535
  parts.push(buildSegment(segment) + ' ' + cardinalScaleWord + ' ' + ORDINAL_SCALES[scaleIndex - 1])
508
536
  }
509
- } else {
537
+ }
538
+ else {
510
539
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
511
540
  const scaleWord = pluralize(segment, scaleForms)
512
541
  if (segment === 1) {
513
542
  parts.push(scaleWord)
514
- } else {
543
+ }
544
+ else {
515
545
  parts.push(buildSegment(segment) + ' ' + scaleWord)
516
546
  }
517
547
  }
@@ -526,12 +556,10 @@ function buildLargeOrdinal (n) {
526
556
 
527
557
  /**
528
558
  * Converts a numeric value to Latvian ordinal words (masculine nominative).
529
- *
530
559
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
531
560
  * @returns {string} The number as ordinal words
532
561
  * @throws {TypeError} If value is not a valid numeric type
533
562
  * @throws {RangeError} If value is negative, zero, or has a decimal part
534
- *
535
563
  * @example
536
564
  * toOrdinal(1) // 'pirmais'
537
565
  * toOrdinal(2) // 'otrais'
@@ -539,8 +567,9 @@ function buildLargeOrdinal (n) {
539
567
  * toOrdinal(100) // 'simtais'
540
568
  * toOrdinal(1000) // 'tūkstošais'
541
569
  */
542
- function toOrdinal (value) {
570
+ function toOrdinal(value) {
543
571
  const integerPart = parseOrdinalValue(value)
572
+ checkMax(integerPart, ordinalMax)
544
573
  return integerToOrdinal(integerPart)
545
574
  }
546
575
 
@@ -550,20 +579,19 @@ function toOrdinal (value) {
550
579
 
551
580
  /**
552
581
  * Converts a numeric value to Latvian currency words (Euro).
553
- *
554
582
  * @param {number | string | bigint} value - The currency amount to convert
555
583
  * @returns {string} The amount in Latvian currency words
556
584
  * @throws {TypeError} If value is not a valid numeric type
557
585
  * @throws {Error} If value is not a valid number format
558
- *
559
586
  * @example
560
587
  * toCurrency(42) // 'četrdesmit divi eiro'
561
588
  * toCurrency(1) // 'viens eiro'
562
589
  * toCurrency(1.50) // 'viens eiro piecdesmit centu'
563
590
  * toCurrency(-5) // 'mīnus pieci eiro'
564
591
  */
565
- function toCurrency (value) {
592
+ function toCurrency(value) {
566
593
  const { isNegative, dollars: euros, cents } = parseCurrencyValue(value)
594
+ checkMax(euros, currencyMax)
567
595
 
568
596
  let result = ''
569
597
  if (isNegative) {
package/src/mr-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 Marathi words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Marathi words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Marathi 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) // 'दुसरा'
@@ -22,12 +22,10 @@ export function toCardinal(value: number | string | bigint): string;
22
22
  export function toOrdinal(value: number | string | bigint): string;
23
23
  /**
24
24
  * Converts a numeric value to Marathi currency words (Indian Rupee).
25
- *
26
25
  * @param {number | string | bigint} value - The currency amount to convert
27
26
  * @returns {string} The amount in Marathi currency words
28
27
  * @throws {TypeError} If value is not a valid numeric type
29
28
  * @throws {Error} If value is not a valid number format
30
- *
31
29
  * @example
32
30
  * toCurrency(42.50) // 'बेचाळीस रुपये पन्नास पैसे'
33
31
  * toCurrency(1) // 'एक रुपया'
package/src/mr-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} Marathi 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} Marathi 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
+ * Reads the fractional digits per-digit in Marathi.
149
+ * @param {string} decimalPart - Decimal digit string
150
+ * @returns {string} Marathi 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 Marathi words.
148
- *
149
164
  * @param {number | string | bigint} value - The numeric value to convert
150
165
  * @returns {string} The number in Marathi 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 Marathi ordinal words.
176
193
  *
177
194
  * Marathi ordinals: First 6 are irregular, then add -वा suffix.
178
- *
179
195
  * @param {bigint} n - Positive integer to convert
180
196
  * @returns {string} Marathi 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,20 +208,20 @@ function integerToOrdinal (n) {
192
208
 
193
209
  /**
194
210
  * Converts a numeric value to Marathi 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(3) // 'तिसरा'
205
219
  * toOrdinal(10) // 'दहावा'
206
220
  */
207
- function toOrdinal (value) {
221
+ function toOrdinal(value) {
208
222
  const integerPart = parseOrdinalValue(value)
223
+ // Ordinals build on the cardinal speller, so they share its ceiling.
224
+ checkMax(integerPart, ordinalMax)
209
225
  return integerToOrdinal(integerPart)
210
226
  }
211
227
 
@@ -215,19 +231,18 @@ function toOrdinal (value) {
215
231
 
216
232
  /**
217
233
  * Converts a numeric value to Marathi currency words (Indian Rupee).
218
- *
219
234
  * @param {number | string | bigint} value - The currency amount to convert
220
235
  * @returns {string} The amount in Marathi currency words
221
236
  * @throws {TypeError} If value is not a valid numeric type
222
237
  * @throws {Error} If value is not a valid number format
223
- *
224
238
  * @example
225
239
  * toCurrency(42.50) // 'बेचाळीस रुपये पन्नास पैसे'
226
240
  * toCurrency(1) // 'एक रुपया'
227
241
  * toCurrency(0.01) // 'एक पैसा'
228
242
  */
229
- function toCurrency (value) {
243
+ function toCurrency(value) {
230
244
  const { isNegative, dollars: rupees, cents: paise } = parseCurrencyValue(value)
245
+ checkMax(rupees, currencyMax)
231
246
 
232
247
  // Build result
233
248
  let result = ''
package/src/ms-MY.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 Malay words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Malay words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Malay 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) // 'pertama'
18
18
  * toOrdinal(2) // 'kedua'
@@ -23,12 +23,10 @@ export function toOrdinal(value: number | string | bigint): string;
23
23
  * Converts a numeric value to Malay currency words (Ringgit).
24
24
  *
25
25
  * Malaysian Ringgit uses sen as subunit (100 sen = 1 ringgit).
26
- *
27
26
  * @param {number | string | bigint} value - The currency amount to convert
28
27
  * @returns {string} The amount in Malay 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) // 'empat puluh dua ringgit'
34
32
  * toCurrency(1.50) // 'satu ringgit lima puluh sen'