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/ru-RU.js CHANGED
@@ -13,7 +13,9 @@
13
13
  import { parseCardinalValue } from './utils/parse-cardinal.js'
14
14
  import { parseCurrencyValue } from './utils/parse-currency.js'
15
15
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
16
- import { validateOptions } from './utils/validate-options.js'
16
+ import { checkMax } from './utils/check-max.js'
17
+ import { western } from './utils/scale.js'
18
+ import { resolveOptions } from './utils/resolve-options.js'
17
19
 
18
20
  // ============================================================================
19
21
  // Vocabulary
@@ -44,7 +46,7 @@ const SCALE_FORMS = [
44
46
  ['секстиллион', 'секстиллиона', 'секстиллионов'],
45
47
  ['септиллион', 'септиллиона', 'септиллионов'],
46
48
  ['октиллион', 'октиллиона', 'октиллионов'],
47
- ['нониллион', 'нониллиона', 'нониллионов']
49
+ ['нониллион', 'нониллиона', 'нониллионов'],
48
50
  ]
49
51
 
50
52
  // ============================================================================
@@ -74,7 +76,7 @@ const ORDINAL_SCALES = [
74
76
  'секстиллионный',
75
77
  'септиллионный',
76
78
  'октиллионный',
77
- 'нониллионный'
79
+ 'нониллионный',
78
80
  ]
79
81
 
80
82
  // Prefixes for compound ordinal thousands (двух-, трёх-, etc. + тысячный)
@@ -94,7 +96,13 @@ const KOPECK_FORMS = ['копейка', 'копейки', 'копеек']
94
96
  // Segment Building
95
97
  // ============================================================================
96
98
 
97
- function pluralize (n, forms) {
99
+ /**
100
+ * Selects the correct plural form based on Russian pluralization rules.
101
+ * @param {number | bigint} n - The count
102
+ * @param {string[]} forms - [one, few, many] forms
103
+ * @returns {string} The matching plural form
104
+ */
105
+ function pluralize(n, forms) {
98
106
  const num = typeof n === 'bigint' ? Number(n) : n
99
107
  const lastDigit = num % 10
100
108
  const lastTwoDigits = num % 100
@@ -108,7 +116,12 @@ function pluralize (n, forms) {
108
116
  return forms[2]
109
117
  }
110
118
 
111
- function buildSegmentMasc (n) {
119
+ /**
120
+ * Builds masculine cardinal words for a 0-999 segment.
121
+ * @param {number} n - Number 0-999
122
+ * @returns {string} Masculine cardinal words
123
+ */
124
+ function buildSegmentMasc(n) {
112
125
  if (n === 0) return ''
113
126
 
114
127
  const onesDigit = n % 10
@@ -127,14 +140,20 @@ function buildSegmentMasc (n) {
127
140
 
128
141
  if (tensDigit === 1) {
129
142
  parts.push(TEENS[onesDigit])
130
- } else if (onesDigit > 0) {
143
+ }
144
+ else if (onesDigit > 0) {
131
145
  parts.push(ONES_MASC[onesDigit])
132
146
  }
133
147
 
134
148
  return parts.join(' ')
135
149
  }
136
150
 
137
- function buildSegmentFem (n) {
151
+ /**
152
+ * Builds feminine cardinal words for a 0-999 segment.
153
+ * @param {number} n - Number 0-999
154
+ * @returns {string} Feminine cardinal words
155
+ */
156
+ function buildSegmentFem(n) {
138
157
  if (n === 0) return ''
139
158
 
140
159
  const onesDigit = n % 10
@@ -153,7 +172,8 @@ function buildSegmentFem (n) {
153
172
 
154
173
  if (tensDigit === 1) {
155
174
  parts.push(TEENS[onesDigit])
156
- } else if (onesDigit > 0) {
175
+ }
176
+ else if (onesDigit > 0) {
157
177
  parts.push(ONES_FEM[onesDigit])
158
178
  }
159
179
 
@@ -164,7 +184,13 @@ function buildSegmentFem (n) {
164
184
  // Conversion Functions
165
185
  // ============================================================================
166
186
 
167
- function integerToWords (n, gender) {
187
+ /**
188
+ * Converts a non-negative integer to Russian cardinal words.
189
+ * @param {bigint} n - Non-negative integer to convert
190
+ * @param {('masculine'|'feminine')} gender - Grammatical gender
191
+ * @returns {string} Cardinal Russian words
192
+ */
193
+ function integerToWords(n, gender) {
168
194
  if (n === 0n) return ZERO
169
195
 
170
196
  const feminine = gender === 'feminine'
@@ -193,7 +219,13 @@ function integerToWords (n, gender) {
193
219
  return buildLargeNumberWords(n, gender)
194
220
  }
195
221
 
196
- function buildLargeNumberWords (n, gender) {
222
+ /**
223
+ * Builds cardinal words for numbers >= 1,000,000 via scale decomposition.
224
+ * @param {bigint} n - Number >= 1,000,000
225
+ * @param {('masculine'|'feminine')} gender - Grammatical gender
226
+ * @returns {string} Cardinal Russian words
227
+ */
228
+ function buildLargeNumberWords(n, gender) {
197
229
  const feminine = gender === 'feminine'
198
230
  const numStr = n.toString()
199
231
  const len = numStr.length
@@ -221,7 +253,8 @@ function buildLargeNumberWords (n, gender) {
221
253
  if (segment !== 0) {
222
254
  if (scaleIndex === 0) {
223
255
  parts.push(feminine ? buildSegmentFem(segment) : buildSegmentMasc(segment))
224
- } else {
256
+ }
257
+ else {
225
258
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
226
259
  const scaleWord = pluralize(segment, scaleForms)
227
260
  // Thousands (scaleIndex=1) are feminine, others masculine
@@ -237,7 +270,13 @@ function buildLargeNumberWords (n, gender) {
237
270
  return parts.join(' ')
238
271
  }
239
272
 
240
- function decimalPartToWords (decimalPart, gender) {
273
+ /**
274
+ * Converts the fractional digit string to Russian words.
275
+ * @param {string} decimalPart - The fractional digits
276
+ * @param {('masculine'|'feminine')} gender - Grammatical gender
277
+ * @returns {string} Cardinal Russian words for the decimal part
278
+ */
279
+ function decimalPartToWords(decimalPart, gender) {
241
280
  let result = ''
242
281
  let i = 0
243
282
 
@@ -256,20 +295,38 @@ function decimalPartToWords (decimalPart, gender) {
256
295
  return result
257
296
  }
258
297
 
298
+ // Supported magnitude ceilings (checked at the public entry points). Both
299
+ // tables are indexed [scaleIndex - 1] (units separate), so the ceiling is
300
+ // 10^((length + 1) * 3): cardinal/currency 10^33, ordinal 10^33.
301
+ export const cardinalMax = western(SCALE_FORMS.length)
302
+ export const ordinalMax = western(ORDINAL_SCALES.length)
303
+ export const currencyMax = western(SCALE_FORMS.length)
304
+
305
+ /**
306
+ * @typedef {object} CardinalOptions
307
+ * @property {('masculine'|'feminine')} [gender] - Grammatical gender
308
+ */
309
+
310
+ /** @type {Required<CardinalOptions>} */
311
+ export const cardinalDefaults = { gender: 'masculine' }
312
+
313
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
314
+ export const cardinalValues = { gender: ['masculine', 'feminine'] }
315
+
259
316
  /**
260
317
  * Converts a numeric value to Russian words.
261
- *
262
318
  * @param {number | string | bigint} value - The numeric value to convert
263
- * @param {Object} [options] - Optional configuration
264
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
319
+ * @param {CardinalOptions} [options] - Optional configuration
265
320
  * @returns {string} The number in Russian words
266
321
  */
267
- function toCardinal (value, options) {
268
- options = validateOptions(options)
322
+ function toCardinal(value, options) {
269
323
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
324
+ // Both the integer part and the decimal's significant digits are spelled via
325
+ // the scale builder, so both must clear the ceiling.
326
+ checkMax(integerPart, cardinalMax, decimalPart)
270
327
 
271
328
  // Apply option defaults
272
- const { gender = 'masculine' } = options
329
+ const { gender } = resolveOptions(options, cardinalDefaults, cardinalValues)
273
330
 
274
331
  let result = ''
275
332
 
@@ -293,11 +350,10 @@ function toCardinal (value, options) {
293
350
  /**
294
351
  * Builds ordinal for a 0-99 segment when it's the final (ordinal) part.
295
352
  * Returns ordinal form: "первый", "двадцать первый", etc.
296
- *
297
353
  * @param {number} n - Number 0-99
298
354
  * @returns {string} Ordinal words
299
355
  */
300
- function buildOrdinalTensOnes (n) {
356
+ function buildOrdinalTensOnes(n) {
301
357
  if (n === 0) return ''
302
358
 
303
359
  const onesDigit = n % 10
@@ -328,11 +384,10 @@ function buildOrdinalTensOnes (n) {
328
384
  *
329
385
  * In Russian ordinals, only the LAST component becomes ordinal.
330
386
  * E.g., 121 = "сто двадцать первый" (one hundred twenty first)
331
- *
332
387
  * @param {bigint} n - Positive integer to convert
333
388
  * @returns {string} Ordinal Russian words
334
389
  */
335
- function integerToOrdinal (n) {
390
+ function integerToOrdinal(n) {
336
391
  // Fast path: numbers < 100
337
392
  if (n < 100n) {
338
393
  return buildOrdinalTensOnes(Number(n))
@@ -383,11 +438,10 @@ function integerToOrdinal (n) {
383
438
  /**
384
439
  * Builds ordinal words for numbers >= 1,000,000.
385
440
  * All segments except the final one are cardinal; final segment is ordinal.
386
- *
387
441
  * @param {bigint} n - Number >= 1,000,000
388
442
  * @returns {string} Ordinal Russian words
389
443
  */
390
- function buildLargeOrdinal (n) {
444
+ function buildLargeOrdinal(n) {
391
445
  const numStr = n.toString()
392
446
  const len = numStr.length
393
447
 
@@ -425,22 +479,26 @@ function buildLargeOrdinal (n) {
425
479
  // Units position (no scale)
426
480
  if (isLastNonZero) {
427
481
  parts.push(integerToOrdinal(BigInt(segment)))
428
- } else {
482
+ }
483
+ else {
429
484
  parts.push(buildSegmentMasc(segment))
430
485
  }
431
- } else {
486
+ }
487
+ else {
432
488
  // Has scale word
433
489
  if (isLastNonZero) {
434
490
  // This scale position is the final ordinal
435
491
  if (segment === 1) {
436
492
  parts.push(ORDINAL_SCALES[scaleIndex - 1])
437
- } else {
493
+ }
494
+ else {
438
495
  // Use cardinal segment + ordinal scale
439
496
  const isFeminine = scaleIndex === 1 // thousands are feminine
440
497
  const segmentWord = isFeminine ? buildSegmentFem(segment) : buildSegmentMasc(segment)
441
498
  parts.push(segmentWord + ' ' + ORDINAL_SCALES[scaleIndex - 1])
442
499
  }
443
- } else {
500
+ }
501
+ else {
444
502
  // Not the final segment: use cardinal
445
503
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
446
504
  const scaleWord = pluralize(segment, scaleForms)
@@ -459,12 +517,10 @@ function buildLargeOrdinal (n) {
459
517
 
460
518
  /**
461
519
  * Converts a numeric value to Russian ordinal words (masculine nominative).
462
- *
463
520
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
464
521
  * @returns {string} The number as ordinal words (e.g., "первый", "сорок второй")
465
522
  * @throws {TypeError} If value is not a valid numeric type
466
523
  * @throws {RangeError} If value is negative, zero, or has a decimal part
467
- *
468
524
  * @example
469
525
  * toOrdinal(1) // 'первый'
470
526
  * toOrdinal(2) // 'второй'
@@ -475,8 +531,9 @@ function buildLargeOrdinal (n) {
475
531
  * toOrdinal(101) // 'сто первый'
476
532
  * toOrdinal(1000) // 'тысячный'
477
533
  */
478
- function toOrdinal (value) {
534
+ function toOrdinal(value) {
479
535
  const integerPart = parseOrdinalValue(value)
536
+ checkMax(integerPart, ordinalMax)
480
537
  return integerToOrdinal(integerPart)
481
538
  }
482
539
 
@@ -484,16 +541,21 @@ function toOrdinal (value) {
484
541
  // CURRENCY: toCurrency(value, options?)
485
542
  // ============================================================================
486
543
 
544
+ /**
545
+ * @typedef {object} CurrencyOptions
546
+ * @property {boolean} [and] - Use "и" between rubles and kopecks
547
+ */
548
+
549
+ /** @type {Required<CurrencyOptions>} */
550
+ export const currencyDefaults = { and: true }
551
+
487
552
  /**
488
553
  * Converts a numeric value to Russian currency words (Russian Ruble).
489
- *
490
554
  * @param {number | string | bigint} value - The currency amount to convert
491
- * @param {Object} [options] - Optional configuration
492
- * @param {boolean} [options.and=true] - Use "и" between rubles and kopecks
555
+ * @param {CurrencyOptions} [options] - Optional configuration
493
556
  * @returns {string} The amount in Russian currency words
494
557
  * @throws {TypeError} If value is not a valid numeric type
495
558
  * @throws {Error} If value is not a valid number format
496
- *
497
559
  * @example
498
560
  * toCurrency(42.50) // 'сорок два рубля и пятьдесят копеек'
499
561
  * toCurrency(1) // 'один рубль'
@@ -501,10 +563,10 @@ function toOrdinal (value) {
501
563
  * toCurrency(0.01) // 'одна копейка'
502
564
  * toCurrency(42.50, { and: false }) // 'сорок два рубля пятьдесят копеек'
503
565
  */
504
- function toCurrency (value, options) {
505
- options = validateOptions(options)
566
+ function toCurrency(value, options) {
506
567
  const { isNegative, dollars: rubles, cents: kopecks } = parseCurrencyValue(value)
507
- const { and: useAnd = true } = options
568
+ checkMax(rubles, currencyMax)
569
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
508
570
 
509
571
  // Build result
510
572
  let result = ''
@@ -1,22 +1,47 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CardinalOptions
6
+ * @property {('masculine'|'feminine')} [gender] - Grammatical gender
7
+ */
8
+ /** @type {Required<CardinalOptions>} */
9
+ export const cardinalDefaults: Required<CardinalOptions>;
10
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
11
+ export const cardinalValues: {
12
+ gender: ReadonlyArray<Required<CardinalOptions>["gender"]>;
13
+ };
14
+ /**
15
+ * @typedef {object} CurrencyOptions
16
+ * @property {boolean} [and] - Use "и" between dinars and para
17
+ */
18
+ /** @type {Required<CurrencyOptions>} */
19
+ export const currencyDefaults: Required<CurrencyOptions>;
20
+ export type CardinalOptions = {
21
+ /**
22
+ * - Grammatical gender
23
+ */
24
+ gender?: "feminine" | "masculine" | undefined;
25
+ };
26
+ export type CurrencyOptions = {
27
+ /**
28
+ * - Use "и" between dinars and para
29
+ */
30
+ and?: boolean | undefined;
31
+ };
1
32
  /**
2
33
  * Converts a numeric value to Serbian (Cyrillic) words.
3
- *
4
34
  * @param {number | string | bigint} value - The numeric value to convert
5
- * @param {Object} [options] - Optional configuration
6
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
35
+ * @param {CardinalOptions} [options] - Optional configuration
7
36
  * @returns {string} The number in Serbian Cyrillic words
8
37
  */
9
- export function toCardinal(value: number | string | bigint, options?: {
10
- gender?: "masculine" | "feminine" | undefined;
11
- }): string;
38
+ export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
12
39
  /**
13
40
  * Converts a numeric value to Serbian ordinal words (masculine nominative).
14
- *
15
41
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
16
42
  * @returns {string} The number as ordinal words (e.g., "први", "четрдесет други")
17
43
  * @throws {TypeError} If value is not a valid numeric type
18
44
  * @throws {RangeError} If value is negative, zero, or has a decimal part
19
- *
20
45
  * @example
21
46
  * toOrdinal(1) // 'први'
22
47
  * toOrdinal(2) // 'други'
@@ -29,14 +54,11 @@ export function toCardinal(value: number | string | bigint, options?: {
29
54
  export function toOrdinal(value: number | string | bigint): string;
30
55
  /**
31
56
  * Converts a numeric value to Serbian currency words (Serbian Dinar).
32
- *
33
57
  * @param {number | string | bigint} value - The currency amount to convert
34
- * @param {Object} [options] - Optional configuration
35
- * @param {boolean} [options.and=true] - Use "и" between dinars and para
58
+ * @param {CurrencyOptions} [options] - Optional configuration
36
59
  * @returns {string} The amount in Serbian currency words
37
60
  * @throws {TypeError} If value is not a valid numeric type
38
61
  * @throws {Error} If value is not a valid number format
39
- *
40
62
  * @example
41
63
  * toCurrency(42.50) // 'четрдесет два динара и педесет пара'
42
64
  * toCurrency(1) // 'један динар'
@@ -44,6 +66,4 @@ export function toOrdinal(value: number | string | bigint): string;
44
66
  * toCurrency(0.01) // 'једна пара'
45
67
  * toCurrency(42.50, { and: false }) // 'четрдесет два динара педесет пара'
46
68
  */
47
- export function toCurrency(value: number | string | bigint, options?: {
48
- and?: boolean | undefined;
49
- }): string;
69
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;