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/ar-SA.d.ts CHANGED
@@ -1,46 +1,72 @@
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
+ * @property {string} [negativeWord] - Custom word for negative numbers
8
+ */
9
+ /** @type {Required<CardinalOptions>} */
10
+ export const cardinalDefaults: Required<CardinalOptions>;
11
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
12
+ export const cardinalValues: {
13
+ gender: ReadonlyArray<Required<CardinalOptions>["gender"]>;
14
+ };
15
+ /**
16
+ * @typedef {object} OrdinalOptions
17
+ * @property {('masculine'|'feminine')} [gender] - Grammatical gender
18
+ */
19
+ /** @type {Required<OrdinalOptions>} */
20
+ export const ordinalDefaults: Required<OrdinalOptions>;
21
+ /** @type {{ gender: ReadonlyArray<Required<OrdinalOptions>['gender']> }} */
22
+ export const ordinalValues: {
23
+ gender: ReadonlyArray<Required<OrdinalOptions>["gender"]>;
24
+ };
25
+ export type CardinalOptions = {
26
+ /**
27
+ * - Grammatical gender
28
+ */
29
+ gender?: "feminine" | "masculine" | undefined;
30
+ /**
31
+ * - Custom word for negative numbers
32
+ */
33
+ negativeWord?: string | undefined;
34
+ };
35
+ export type OrdinalOptions = {
36
+ /**
37
+ * - Grammatical gender
38
+ */
39
+ gender?: "feminine" | "masculine" | undefined;
40
+ };
1
41
  /**
2
42
  * Converts a numeric value to Arabic words.
3
- *
4
43
  * @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
7
- * @param {string} [options.negativeWord] - Custom word for negative numbers
44
+ * @param {CardinalOptions} [options] - Optional configuration
8
45
  * @returns {string} The number in Arabic words
9
- *
10
46
  * @example
11
47
  * toCardinal(1) // 'واحد'
12
48
  * toCardinal(1, {gender: 'feminine'}) // 'واحدة'
13
49
  */
14
- export function toCardinal(value: number | string | bigint, options?: {
15
- gender?: "masculine" | "feminine" | undefined;
16
- negativeWord?: string | undefined;
17
- }): string;
50
+ export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
18
51
  /**
19
52
  * Converts a numeric value to Arabic ordinal words.
20
- *
21
53
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
22
- * @param {Object} [options] - Optional configuration
23
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
54
+ * @param {OrdinalOptions} [options] - Optional configuration
24
55
  * @returns {string} The number as ordinal words
25
56
  * @throws {TypeError} If value is not a valid numeric type
26
57
  * @throws {RangeError} If value is negative, zero, or has a decimal part
27
- *
28
58
  * @example
29
59
  * toOrdinal(1) // 'الأول'
30
60
  * toOrdinal(1, {gender: 'feminine'}) // 'الأولى'
31
61
  * toOrdinal(3) // 'الثالث'
32
62
  */
33
- export function toOrdinal(value: number | string | bigint, options?: {
34
- gender?: "masculine" | "feminine" | undefined;
35
- }): string;
63
+ export function toOrdinal(value: number | string | bigint, options?: OrdinalOptions): string;
36
64
  /**
37
65
  * Converts a numeric value to Arabic currency words (Saudi Riyal).
38
- *
39
66
  * @param {number | string | bigint} value - The currency amount to convert
40
67
  * @returns {string} The amount in Arabic currency words
41
68
  * @throws {TypeError} If value is not a valid numeric type
42
69
  * @throws {Error} If value is not a valid number format
43
- *
44
70
  * @example
45
71
  * toCurrency(42.50) // 'اثنان وأربعون ريالاً وخمسون هللة'
46
72
  * toCurrency(1) // 'ريال واحد'
package/src/ar-SA.js CHANGED
@@ -15,7 +15,9 @@
15
15
  import { parseCardinalValue } from './utils/parse-cardinal.js'
16
16
  import { parseCurrencyValue } from './utils/parse-currency.js'
17
17
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
18
- import { validateOptions } from './utils/validate-options.js'
18
+ import { checkMax } from './utils/check-max.js'
19
+ import { western } from './utils/scale.js'
20
+ import { resolveOptions } from './utils/resolve-options.js'
19
21
 
20
22
  // ============================================================================
21
23
  // Vocabulary
@@ -26,6 +28,11 @@ const HUNDREDS = ['', 'مائة', 'مئتان', 'ثلاثمائة', 'أربعم
26
28
 
27
29
  // Magnitude words with three forms: singular, appended (tanween), plural
28
30
  const SCALE_WORDS = ['مائة', 'ألف', 'مليون', 'مليار', 'تريليون', 'كوادريليون', 'كوينتليون', 'سكستيليون']
31
+
32
+ // Supported magnitude ceiling (checked at the public entry points), derived from the scale table.
33
+ export const cardinalMax = western(SCALE_WORDS.length - 1)
34
+ export const ordinalMax = western(SCALE_WORDS.length - 1)
35
+ export const currencyMax = western(SCALE_WORDS.length - 1)
29
36
  const SCALE_APPENDED = ['', 'ألفاً', 'مليوناً', 'ملياراً', 'تريليوناً', 'كوادريليوناً', 'كوينتليوناً', 'سكستيليوناً']
30
37
  const SCALE_PLURAL = ['', 'آلاف', 'ملايين', 'مليارات', 'تريليونات', 'كوادريليونات', 'كوينتليونات', 'سكستيليونات']
31
38
 
@@ -76,8 +83,13 @@ const HALALA_PLURAL_11 = 'هللة'
76
83
  * Convert a 3-digit group to words.
77
84
  * Returns a clean string with no leading/trailing spaces.
78
85
  * Arabic "و" (and) is attached to following word: "مائة وخمسة" not "مائة و خمسة"
86
+ * @param {number} groupNumber - The 3-digit group value (0-999)
87
+ * @param {number} groupLevel - The scale level of this group (0 = units, 1 = thousands, ...)
88
+ * @param {bigint} fullNumber - The full number being converted
89
+ * @param {string[]} ones - Gender-specific ones words (1-19)
90
+ * @returns {string} The group rendered as words
79
91
  */
80
- function segmentToWords (groupNumber, groupLevel, fullNumber, ones) {
92
+ function segmentToWords(groupNumber, groupLevel, fullNumber, ones) {
81
93
  const tensValue = groupNumber % 100
82
94
  const hundredsDigit = Math.trunc(groupNumber / 100)
83
95
  let result = ''
@@ -86,7 +98,8 @@ function segmentToWords (groupNumber, groupLevel, fullNumber, ones) {
86
98
  if (hundredsDigit > 0) {
87
99
  if (tensValue === 0 && hundredsDigit === 2) {
88
100
  result = DUAL[0]
89
- } else {
101
+ }
102
+ else {
90
103
  const hundredsWord = HUNDREDS[hundredsDigit]
91
104
  if (hundredsWord) {
92
105
  result = hundredsWord
@@ -105,15 +118,19 @@ function segmentToWords (groupNumber, groupLevel, fullNumber, ones) {
105
118
  const pow = Math.trunc(Math.log10(numValue))
106
119
  if (pow % 3 === 0 && fullNumber === BigInt(2 * Math.pow(10, pow))) {
107
120
  result += (groupNumber === 2 ? DUAL[groupLevel] : DUAL_APPENDED[groupLevel])
108
- } else {
121
+ }
122
+ else {
109
123
  result += DUAL[groupLevel]
110
124
  }
111
- } else if (tensValue === 1 && groupLevel > 0) {
125
+ }
126
+ else if (tensValue === 1 && groupLevel > 0) {
112
127
  result += SCALE_WORDS[groupLevel]
113
- } else {
128
+ }
129
+ else {
114
130
  result += ones[tensValue - 1]
115
131
  }
116
- } else {
132
+ }
133
+ else {
117
134
  const onesDigit = tensValue % 10
118
135
  const tensIndex = Math.trunc(tensValue / 10) - 2
119
136
 
@@ -127,7 +144,13 @@ function segmentToWords (groupNumber, groupLevel, fullNumber, ones) {
127
144
  return result
128
145
  }
129
146
 
130
- function integerToWords (n, gender) {
147
+ /**
148
+ * Convert a non-negative integer to Arabic words.
149
+ * @param {bigint} n - The non-negative integer to convert
150
+ * @param {string} gender - 'masculine' or 'feminine'
151
+ * @returns {string} The number rendered as words
152
+ */
153
+ function integerToWords(n, gender) {
131
154
  if (n === 0n) return ZERO
132
155
 
133
156
  const ones = gender === 'feminine' ? ONES_FEM : ONES_MASC
@@ -151,9 +174,11 @@ function integerToWords (n, gender) {
151
174
  const remainder = numberToProcess % 100
152
175
  if (remainder === 1) {
153
176
  groupText += ' ' + SCALE_WORDS[group]
154
- } else if (numberToProcess >= 3 && numberToProcess <= 10) {
177
+ }
178
+ else if (numberToProcess >= 3 && numberToProcess <= 10) {
155
179
  groupText += ' ' + SCALE_PLURAL[group]
156
- } else {
180
+ }
181
+ else {
157
182
  groupText += ' ' + (groups.length > 0 ? SCALE_APPENDED[group] : SCALE_WORDS[group])
158
183
  }
159
184
  }
@@ -177,7 +202,13 @@ function integerToWords (n, gender) {
177
202
  return result
178
203
  }
179
204
 
180
- function decimalPartToWords (decimalPart, gender) {
205
+ /**
206
+ * Convert the fractional digits of a number to Arabic words.
207
+ * @param {string} decimalPart - The decimal digits (after the separator)
208
+ * @param {string} gender - 'masculine' or 'feminine'
209
+ * @returns {string} The decimal part rendered as words
210
+ */
211
+ function decimalPartToWords(decimalPart, gender) {
181
212
  const parts = []
182
213
  let i = 0
183
214
 
@@ -194,28 +225,33 @@ function decimalPartToWords (decimalPart, gender) {
194
225
  return parts.join(' ')
195
226
  }
196
227
 
228
+ /**
229
+ * @typedef {object} CardinalOptions
230
+ * @property {('masculine'|'feminine')} [gender] - Grammatical gender
231
+ * @property {string} [negativeWord] - Custom word for negative numbers
232
+ */
233
+
234
+ /** @type {Required<CardinalOptions>} */
235
+ export const cardinalDefaults = { gender: 'masculine', negativeWord: NEGATIVE }
236
+
237
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
238
+ export const cardinalValues = { gender: ['masculine', 'feminine'] }
239
+
197
240
  /**
198
241
  * Converts a numeric value to Arabic words.
199
- *
200
242
  * @param {number | string | bigint} value - The numeric value to convert
201
- * @param {Object} [options] - Optional configuration
202
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
203
- * @param {string} [options.negativeWord] - Custom word for negative numbers
243
+ * @param {CardinalOptions} [options] - Optional configuration
204
244
  * @returns {string} The number in Arabic words
205
- *
206
245
  * @example
207
246
  * toCardinal(1) // 'واحد'
208
247
  * toCardinal(1, {gender: 'feminine'}) // 'واحدة'
209
248
  */
210
- function toCardinal (value, options) {
211
- options = validateOptions(options)
249
+ function toCardinal(value, options) {
212
250
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
251
+ checkMax(integerPart, cardinalMax, decimalPart)
213
252
 
214
253
  // Apply option defaults
215
- const {
216
- gender = 'masculine',
217
- negativeWord = NEGATIVE
218
- } = options
254
+ const { gender, negativeWord } = resolveOptions(options, cardinalDefaults, cardinalValues)
219
255
 
220
256
  const parts = []
221
257
 
@@ -241,12 +277,11 @@ function toCardinal (value, options) {
241
277
  * Gets the Arabic ordinal form for a number.
242
278
  *
243
279
  * Arabic ordinals 1-10 have special forms, beyond 10 use cardinal + position.
244
- *
245
280
  * @param {bigint} n - Positive integer to convert
246
281
  * @param {string} gender - 'masculine' or 'feminine'
247
282
  * @returns {string} Arabic ordinal words
248
283
  */
249
- function integerToOrdinal (n, gender) {
284
+ function integerToOrdinal(n, gender) {
250
285
  const ordinals = gender === 'feminine' ? ORDINAL_FEM : ORDINAL_MASC
251
286
 
252
287
  // Special ordinals for 1-10
@@ -259,25 +294,33 @@ function integerToOrdinal (n, gender) {
259
294
  return 'ال' + cardinal
260
295
  }
261
296
 
297
+ /**
298
+ * @typedef {object} OrdinalOptions
299
+ * @property {('masculine'|'feminine')} [gender] - Grammatical gender
300
+ */
301
+
302
+ /** @type {Required<OrdinalOptions>} */
303
+ export const ordinalDefaults = { gender: 'masculine' }
304
+
305
+ /** @type {{ gender: ReadonlyArray<Required<OrdinalOptions>['gender']> }} */
306
+ export const ordinalValues = { gender: ['masculine', 'feminine'] }
307
+
262
308
  /**
263
309
  * Converts a numeric value to Arabic ordinal words.
264
- *
265
310
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
266
- * @param {Object} [options] - Optional configuration
267
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
311
+ * @param {OrdinalOptions} [options] - Optional configuration
268
312
  * @returns {string} The number as ordinal words
269
313
  * @throws {TypeError} If value is not a valid numeric type
270
314
  * @throws {RangeError} If value is negative, zero, or has a decimal part
271
- *
272
315
  * @example
273
316
  * toOrdinal(1) // 'الأول'
274
317
  * toOrdinal(1, {gender: 'feminine'}) // 'الأولى'
275
318
  * toOrdinal(3) // 'الثالث'
276
319
  */
277
- function toOrdinal (value, options) {
278
- options = validateOptions(options)
320
+ function toOrdinal(value, options) {
279
321
  const integerPart = parseOrdinalValue(value)
280
- const { gender = 'masculine' } = options
322
+ checkMax(integerPart, ordinalMax)
323
+ const { gender } = resolveOptions(options, ordinalDefaults, ordinalValues)
281
324
  return integerToOrdinal(integerPart, gender)
282
325
  }
283
326
 
@@ -293,15 +336,22 @@ function toOrdinal (value, options) {
293
336
  * - 2: dual
294
337
  * - 3-10: plural form 1
295
338
  * - 11+: plural form 2 (different ending)
339
+ * @param {bigint} n - The riyal count
340
+ * @returns {string} The appropriate riyal word form
296
341
  */
297
- function getRiyalForm (n) {
342
+ function getRiyalForm(n) {
298
343
  if (n === 1n) return RIYAL_SINGULAR
299
344
  if (n === 2n) return RIYAL_DUAL
300
345
  if (n >= 3n && n <= 10n) return RIYAL_PLURAL_3_10
301
346
  return RIYAL_PLURAL_11
302
347
  }
303
348
 
304
- function getHalalaForm (n) {
349
+ /**
350
+ * Gets the appropriate halala word form based on number.
351
+ * @param {bigint} n - The halala count
352
+ * @returns {string} The appropriate halala word form
353
+ */
354
+ function getHalalaForm(n) {
305
355
  if (n === 1n) return HALALA_SINGULAR
306
356
  if (n === 2n) return HALALA_DUAL
307
357
  if (n >= 3n && n <= 10n) return HALALA_PLURAL_3_10
@@ -310,19 +360,18 @@ function getHalalaForm (n) {
310
360
 
311
361
  /**
312
362
  * Converts a numeric value to Arabic currency words (Saudi Riyal).
313
- *
314
363
  * @param {number | string | bigint} value - The currency amount to convert
315
364
  * @returns {string} The amount in Arabic currency words
316
365
  * @throws {TypeError} If value is not a valid numeric type
317
366
  * @throws {Error} If value is not a valid number format
318
- *
319
367
  * @example
320
368
  * toCurrency(42.50) // 'اثنان وأربعون ريالاً وخمسون هللة'
321
369
  * toCurrency(1) // 'ريال واحد'
322
370
  * toCurrency(0.01) // 'هللة واحدة'
323
371
  */
324
- function toCurrency (value) {
372
+ function toCurrency(value) {
325
373
  const { isNegative, dollars: riyals, cents: halalas } = parseCurrencyValue(value)
374
+ checkMax(riyals, currencyMax)
326
375
 
327
376
  // Build result
328
377
  let result = ''
@@ -333,9 +382,11 @@ function toCurrency (value) {
333
382
  // Special case for 1 and 2: currency word comes first
334
383
  if (riyals === 1n) {
335
384
  result += RIYAL_SINGULAR + ' ' + ONES_MASC[0]
336
- } else if (riyals === 2n) {
385
+ }
386
+ else if (riyals === 2n) {
337
387
  result += RIYAL_DUAL
338
- } else {
388
+ }
389
+ else {
339
390
  const riyalWord = integerToWords(riyals, 'masculine')
340
391
  result += riyalWord + ' ' + getRiyalForm(riyals)
341
392
  }
@@ -349,9 +400,11 @@ function toCurrency (value) {
349
400
  // Special case for 1 and 2: currency word comes first
350
401
  if (halalas === 1n) {
351
402
  result += HALALA_SINGULAR + ' ' + ONES_FEM[0]
352
- } else if (halalas === 2n) {
403
+ }
404
+ else if (halalas === 2n) {
353
405
  result += HALALA_DUAL
354
- } else {
406
+ }
407
+ else {
355
408
  const halalaWord = integerToWords(halalas, 'feminine')
356
409
  result += halalaWord + ' ' + getHalalaForm(halalas)
357
410
  }
package/src/az-AZ.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 Azerbaijani words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Azerbaijani words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Azerbaijani 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) // 'birinci'
18
18
  * toOrdinal(2) // 'ikinci'
@@ -23,12 +23,10 @@ export function toOrdinal(value: number | string | bigint): string;
23
23
  * Converts a numeric value to Azerbaijani currency words (Manat).
24
24
  *
25
25
  * Uses manat and qəpik (100 qəpik = 1 manat).
26
- *
27
26
  * @param {number | string | bigint} value - The currency amount to convert
28
27
  * @returns {string} The amount in Azerbaijani 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) // 'qırx iki manat'
34
32
  * toCurrency(1.50) // 'bir manat əlli qəpik'
package/src/az-AZ.js CHANGED
@@ -12,6 +12,8 @@
12
12
  import { parseCardinalValue } from './utils/parse-cardinal.js'
13
13
  import { parseCurrencyValue } from './utils/parse-currency.js'
14
14
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
15
+ import { checkMax } from './utils/check-max.js'
16
+ import { western } from './utils/scale.js'
15
17
 
16
18
  // ============================================================================
17
19
  // Vocabulary
@@ -31,12 +33,22 @@ const DECIMAL_SEP = 'nöqtə'
31
33
  // Short scale
32
34
  const SCALE_WORDS = ['', THOUSAND, 'milyon', 'milyar', 'trilyon', 'katrilyon', 'kentilyon']
33
35
 
36
+ // Supported magnitude ceiling (checked at the public entry points). SCALE_WORDS
37
+ // reaches index SCALE_WORDS.length-1 (kentilyon, 10^18), so values must stay
38
+ // below 10^(SCALE_WORDS.length * 3) = 10^21. Ordinal builds on the cardinal
39
+ // (integerToWords + suffix) and currency too, so all three share the ceiling —
40
+ // and the decimal is also spelled via integerToWords, so it's guarded as well.
41
+ export const cardinalMax = western(SCALE_WORDS.length - 1)
42
+ export const ordinalMax = western(SCALE_WORDS.length - 1)
43
+ export const currencyMax = western(SCALE_WORDS.length - 1)
44
+
34
45
  // ============================================================================
35
46
  // Ordinal Vocabulary
36
47
  // ============================================================================
37
48
 
38
49
  // Azerbaijani ordinals use -(i/ı/u/ü)nci/ncı/ncu/ncü suffix with vowel harmony
39
50
  // Special forms for 1-10
51
+ /** @type {Record<number, string>} */
40
52
  const ORDINAL_SPECIAL = {
41
53
  1: 'birinci',
42
54
  2: 'ikinci',
@@ -47,7 +59,7 @@ const ORDINAL_SPECIAL = {
47
59
  7: 'yeddinci',
48
60
  8: 'səkkizinci',
49
61
  9: 'doqquzuncu',
50
- 10: 'onuncu'
62
+ 10: 'onuncu',
51
63
  }
52
64
 
53
65
  // ============================================================================
@@ -61,7 +73,12 @@ const QEPIK = 'qəpik' // subunit (100 qəpik = 1 manat)
61
73
  // Precomputed Lookup Table
62
74
  // ============================================================================
63
75
 
64
- function buildSegment (n) {
76
+ /**
77
+ * Builds the Azerbaijani words for a 3-digit segment (0-999).
78
+ * @param {number} n - The segment value
79
+ * @returns {string} The segment in words
80
+ */
81
+ function buildSegment(n) {
65
82
  if (n === 0) return ''
66
83
 
67
84
  const ones = n % 10
@@ -73,14 +90,16 @@ function buildSegment (n) {
73
90
  if (hundredsDigit > 0) {
74
91
  if (hundredsDigit === 1) {
75
92
  parts.push(HUNDRED)
76
- } else {
93
+ }
94
+ else {
77
95
  parts.push(ONES[hundredsDigit] + ' ' + HUNDRED)
78
96
  }
79
97
  }
80
98
 
81
99
  if (tensDigit === 1) {
82
100
  parts.push(TEENS[ones])
83
- } else {
101
+ }
102
+ else {
84
103
  if (tensDigit > 1) {
85
104
  parts.push(TENS[tensDigit])
86
105
  }
@@ -96,7 +115,12 @@ function buildSegment (n) {
96
115
  // Conversion Functions
97
116
  // ============================================================================
98
117
 
99
- function integerToWords (n) {
118
+ /**
119
+ * Converts a non-negative integer to Azerbaijani words.
120
+ * @param {bigint} n - The integer value
121
+ * @returns {string} The integer in words
122
+ */
123
+ function integerToWords(n) {
100
124
  if (n === 0n) return ZERO
101
125
 
102
126
  if (n < 1000n) {
@@ -106,7 +130,12 @@ function integerToWords (n) {
106
130
  return buildLargeNumberWords(n)
107
131
  }
108
132
 
109
- function buildLargeNumberWords (n) {
133
+ /**
134
+ * Builds Azerbaijani words for numbers >= 1000 using scale words.
135
+ * @param {bigint} n - The integer value
136
+ * @returns {string} The number in words
137
+ */
138
+ function buildLargeNumberWords(n) {
110
139
  const numStr = n.toString()
111
140
  const len = numStr.length
112
141
 
@@ -135,10 +164,12 @@ function buildLargeNumberWords (n) {
135
164
 
136
165
  if (scaleIndex === 0) {
137
166
  parts.push(buildSegment(segment))
138
- } else if (scaleIndex === 1 && segment === 1) {
167
+ }
168
+ else if (scaleIndex === 1 && segment === 1) {
139
169
  // Omit "bir" before thousand
140
170
  parts.push(scaleWord)
141
- } else {
171
+ }
172
+ else {
142
173
  parts.push(buildSegment(segment) + ' ' + scaleWord)
143
174
  }
144
175
  }
@@ -149,7 +180,12 @@ function buildLargeNumberWords (n) {
149
180
  return parts.join(' ')
150
181
  }
151
182
 
152
- function decimalPartToWords (decimalPart) {
183
+ /**
184
+ * Converts the decimal-part digit string to Azerbaijani words.
185
+ * @param {string} decimalPart - The decimal digits as a string
186
+ * @returns {string} The decimal part in words
187
+ */
188
+ function decimalPartToWords(decimalPart) {
153
189
  let result = ''
154
190
  let i = 0
155
191
 
@@ -170,12 +206,14 @@ function decimalPartToWords (decimalPart) {
170
206
 
171
207
  /**
172
208
  * Converts a numeric value to Azerbaijani words.
173
- *
174
209
  * @param {number | string | bigint} value - The numeric value to convert
175
210
  * @returns {string} The number in Azerbaijani words
176
211
  */
177
- function toCardinal (value) {
212
+ function toCardinal(value) {
178
213
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
214
+ // Both the integer part and the decimal's significant digits are spelled via
215
+ // the scale builder, so both must clear the ceiling.
216
+ checkMax(integerPart, cardinalMax, decimalPart)
179
217
 
180
218
  let result = ''
181
219
 
@@ -201,7 +239,7 @@ function toCardinal (value) {
201
239
  * @param {string} word - The cardinal word
202
240
  * @returns {string} The appropriate suffix
203
241
  */
204
- function getOrdinalSuffix (word) {
242
+ function getOrdinalSuffix(word) {
205
243
  // Azerbaijani vowel harmony: back vowels (a,ı,o,u) vs front vowels (ə,e,i,ö,ü)
206
244
  const backVowels = 'aıou'
207
245
  const frontVowels = 'əeiöü'
@@ -228,11 +266,10 @@ function getOrdinalSuffix (word) {
228
266
  *
229
267
  * Azerbaijani ordinals: birinci (1st), ikinci (2nd), üçüncü (3rd), etc.
230
268
  * Uses vowel harmony for suffix selection.
231
- *
232
269
  * @param {bigint} n - Positive integer to convert
233
270
  * @returns {string} Azerbaijani ordinal words
234
271
  */
235
- function integerToOrdinal (n) {
272
+ function integerToOrdinal(n) {
236
273
  // Special forms for 1-10
237
274
  if (n >= 1n && n <= 10n) {
238
275
  return ORDINAL_SPECIAL[Number(n)]
@@ -246,19 +283,21 @@ function integerToOrdinal (n) {
246
283
 
247
284
  /**
248
285
  * Converts a numeric value to Azerbaijani ordinal words.
249
- *
250
286
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
251
287
  * @returns {string} The number as ordinal words
252
288
  * @throws {TypeError} If value is not a valid numeric type
253
289
  * @throws {RangeError} If value is negative, zero, or has a decimal part
254
- *
255
290
  * @example
256
291
  * toOrdinal(1) // 'birinci'
257
292
  * toOrdinal(2) // 'ikinci'
258
293
  * toOrdinal(21) // 'iyirmibirinci'
259
294
  */
260
- function toOrdinal (value) {
295
+ function toOrdinal(value) {
261
296
  const integerPart = parseOrdinalValue(value)
297
+ // Ordinal builds on the cardinal (integerToWords + suffix), so it shares the
298
+ // ceiling — past it the cardinal's trailing-space artifact would be hidden by
299
+ // the space-stripping and emit a silently-wrong ordinal.
300
+ checkMax(integerPart, ordinalMax)
262
301
  return integerToOrdinal(integerPart)
263
302
  }
264
303
 
@@ -270,19 +309,18 @@ function toOrdinal (value) {
270
309
  * Converts a numeric value to Azerbaijani currency words (Manat).
271
310
  *
272
311
  * Uses manat and qəpik (100 qəpik = 1 manat).
273
- *
274
312
  * @param {number | string | bigint} value - The currency amount to convert
275
313
  * @returns {string} The amount in Azerbaijani currency words
276
314
  * @throws {TypeError} If value is not a valid numeric type
277
315
  * @throws {Error} If value is not a valid number format
278
- *
279
316
  * @example
280
317
  * toCurrency(42) // 'qırx iki manat'
281
318
  * toCurrency(1.50) // 'bir manat əlli qəpik'
282
319
  * toCurrency(-5) // 'mənfi beş manat'
283
320
  */
284
- function toCurrency (value) {
321
+ function toCurrency(value) {
285
322
  const { isNegative, dollars: manat, cents: qepik } = parseCurrencyValue(value)
323
+ checkMax(manat, currencyMax)
286
324
 
287
325
  let result = ''
288
326
  if (isNegative) {
package/src/bn-BD.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 Bengali words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Bengali words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Bengali 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 Bengali currency words (Bangladeshi Taka).
25
- *
26
25
  * @param {number | string | bigint} value - The currency amount to convert
27
26
  * @returns {string} The amount in Bengali 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) // 'এক টাকা'