n2words 4.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +14 -12
  3. package/dist/am-ET.js +2 -2
  4. package/dist/am-ET.umd.js +2 -2
  5. package/dist/am-Latn-ET.js +2 -2
  6. package/dist/am-Latn-ET.umd.js +2 -2
  7. package/dist/ar-SA.js +2 -2
  8. package/dist/ar-SA.umd.js +2 -2
  9. package/dist/az-AZ.js +2 -2
  10. package/dist/az-AZ.umd.js +2 -2
  11. package/dist/bn-BD.js +2 -2
  12. package/dist/bn-BD.umd.js +2 -2
  13. package/dist/cs-CZ.js +2 -2
  14. package/dist/cs-CZ.umd.js +2 -2
  15. package/dist/da-DK.js +2 -2
  16. package/dist/da-DK.umd.js +2 -2
  17. package/dist/de-DE.js +2 -2
  18. package/dist/de-DE.umd.js +2 -2
  19. package/dist/el-GR.js +2 -2
  20. package/dist/el-GR.umd.js +2 -2
  21. package/dist/en-AU.js +2 -2
  22. package/dist/en-AU.umd.js +2 -2
  23. package/dist/en-BD.js +2 -2
  24. package/dist/en-BD.umd.js +2 -2
  25. package/dist/en-CA.js +2 -2
  26. package/dist/en-CA.umd.js +2 -2
  27. package/dist/en-GB.js +2 -2
  28. package/dist/en-GB.umd.js +2 -2
  29. package/dist/en-GH.js +2 -2
  30. package/dist/en-GH.umd.js +2 -2
  31. package/dist/en-IE.js +2 -2
  32. package/dist/en-IE.umd.js +2 -2
  33. package/dist/en-IN.js +2 -2
  34. package/dist/en-IN.umd.js +2 -2
  35. package/dist/en-KE.js +2 -2
  36. package/dist/en-KE.umd.js +2 -2
  37. package/dist/en-MY.js +2 -2
  38. package/dist/en-MY.umd.js +2 -2
  39. package/dist/en-NG.js +2 -2
  40. package/dist/en-NG.umd.js +2 -2
  41. package/dist/en-NZ.js +2 -2
  42. package/dist/en-NZ.umd.js +2 -2
  43. package/dist/en-PH.js +2 -2
  44. package/dist/en-PH.umd.js +2 -2
  45. package/dist/en-PK.js +2 -2
  46. package/dist/en-PK.umd.js +2 -2
  47. package/dist/en-SG.js +2 -2
  48. package/dist/en-SG.umd.js +2 -2
  49. package/dist/en-US.js +2 -2
  50. package/dist/en-US.umd.js +2 -2
  51. package/dist/en-ZA.js +2 -2
  52. package/dist/en-ZA.umd.js +2 -2
  53. package/dist/es-ES.js +2 -2
  54. package/dist/es-ES.umd.js +2 -2
  55. package/dist/es-MX.js +2 -2
  56. package/dist/es-MX.umd.js +2 -2
  57. package/dist/es-US.js +2 -2
  58. package/dist/es-US.umd.js +2 -2
  59. package/dist/fa-IR.js +2 -2
  60. package/dist/fa-IR.umd.js +2 -2
  61. package/dist/fi-FI.js +2 -2
  62. package/dist/fi-FI.umd.js +2 -2
  63. package/dist/fil-PH.js +2 -2
  64. package/dist/fil-PH.umd.js +2 -2
  65. package/dist/fr-BE.js +2 -2
  66. package/dist/fr-BE.umd.js +2 -2
  67. package/dist/fr-FR.js +2 -2
  68. package/dist/fr-FR.umd.js +2 -2
  69. package/dist/gu-IN.js +2 -2
  70. package/dist/gu-IN.umd.js +2 -2
  71. package/dist/ha-NG.js +2 -2
  72. package/dist/ha-NG.umd.js +2 -2
  73. package/dist/hbo-IL.js +2 -2
  74. package/dist/hbo-IL.umd.js +2 -2
  75. package/dist/he-IL.js +2 -2
  76. package/dist/he-IL.umd.js +2 -2
  77. package/dist/hi-IN.js +2 -2
  78. package/dist/hi-IN.umd.js +2 -2
  79. package/dist/hr-HR.js +2 -2
  80. package/dist/hr-HR.umd.js +2 -2
  81. package/dist/hu-HU.js +2 -2
  82. package/dist/hu-HU.umd.js +2 -2
  83. package/dist/id-ID.js +2 -2
  84. package/dist/id-ID.umd.js +2 -2
  85. package/dist/it-IT.js +2 -2
  86. package/dist/it-IT.umd.js +2 -2
  87. package/dist/ja-JP.js +2 -2
  88. package/dist/ja-JP.umd.js +2 -2
  89. package/dist/ka-GE.js +2 -2
  90. package/dist/ka-GE.umd.js +2 -2
  91. package/dist/kn-IN.js +2 -2
  92. package/dist/kn-IN.umd.js +2 -2
  93. package/dist/ko-KR.js +2 -2
  94. package/dist/ko-KR.umd.js +2 -2
  95. package/dist/lt-LT.js +2 -2
  96. package/dist/lt-LT.umd.js +2 -2
  97. package/dist/lv-LV.js +2 -2
  98. package/dist/lv-LV.umd.js +2 -2
  99. package/dist/mr-IN.js +2 -2
  100. package/dist/mr-IN.umd.js +2 -2
  101. package/dist/ms-MY.js +2 -2
  102. package/dist/ms-MY.umd.js +2 -2
  103. package/dist/nb-NO.js +2 -2
  104. package/dist/nb-NO.umd.js +2 -2
  105. package/dist/nl-NL.js +2 -2
  106. package/dist/nl-NL.umd.js +2 -2
  107. package/dist/pa-IN.js +2 -2
  108. package/dist/pa-IN.umd.js +2 -2
  109. package/dist/pl-PL.js +2 -2
  110. package/dist/pl-PL.umd.js +2 -2
  111. package/dist/pt-BR.js +2 -0
  112. package/dist/pt-BR.umd.js +2 -0
  113. package/dist/pt-PT.js +2 -2
  114. package/dist/pt-PT.umd.js +2 -2
  115. package/dist/ro-RO.js +2 -2
  116. package/dist/ro-RO.umd.js +2 -2
  117. package/dist/ru-RU.js +2 -2
  118. package/dist/ru-RU.umd.js +2 -2
  119. package/dist/sr-Cyrl-RS.js +2 -2
  120. package/dist/sr-Cyrl-RS.umd.js +2 -2
  121. package/dist/sr-Latn-RS.js +2 -2
  122. package/dist/sr-Latn-RS.umd.js +2 -2
  123. package/dist/sv-SE.js +2 -2
  124. package/dist/sv-SE.umd.js +2 -2
  125. package/dist/sw-KE.js +2 -2
  126. package/dist/sw-KE.umd.js +2 -2
  127. package/dist/ta-IN.js +2 -2
  128. package/dist/ta-IN.umd.js +2 -2
  129. package/dist/te-IN.js +2 -2
  130. package/dist/te-IN.umd.js +2 -2
  131. package/dist/th-TH.js +2 -2
  132. package/dist/th-TH.umd.js +2 -2
  133. package/dist/tr-TR.js +2 -2
  134. package/dist/tr-TR.umd.js +2 -2
  135. package/dist/uk-UA.js +2 -2
  136. package/dist/uk-UA.umd.js +2 -2
  137. package/dist/ur-PK.js +2 -2
  138. package/dist/ur-PK.umd.js +2 -2
  139. package/dist/vi-VN.js +2 -2
  140. package/dist/vi-VN.umd.js +2 -2
  141. package/dist/yo-NG.js +2 -2
  142. package/dist/yo-NG.umd.js +2 -2
  143. package/dist/zh-Hans-CN.js +2 -2
  144. package/dist/zh-Hans-CN.umd.js +2 -2
  145. package/dist/zh-Hant-TW.js +2 -2
  146. package/dist/zh-Hant-TW.umd.js +2 -2
  147. package/package.json +53 -36
  148. package/src/am-ET.d.ts +3 -5
  149. package/src/am-ET.js +41 -16
  150. package/src/am-Latn-ET.d.ts +3 -5
  151. package/src/am-Latn-ET.js +45 -16
  152. package/src/ar-SA.d.ts +44 -18
  153. package/src/ar-SA.js +93 -40
  154. package/src/az-AZ.d.ts +3 -5
  155. package/src/az-AZ.js +58 -20
  156. package/src/bn-BD.d.ts +3 -5
  157. package/src/bn-BD.js +32 -16
  158. package/src/cs-CZ.d.ts +3 -6
  159. package/src/cs-CZ.js +66 -42
  160. package/src/da-DK.d.ts +3 -6
  161. package/src/da-DK.js +53 -48
  162. package/src/de-DE.d.ts +17 -11
  163. package/src/de-DE.js +88 -57
  164. package/src/el-GR.d.ts +3 -6
  165. package/src/el-GR.js +45 -32
  166. package/src/en-AU.d.ts +17 -11
  167. package/src/en-AU.js +56 -41
  168. package/src/en-BD.d.ts +17 -11
  169. package/src/en-BD.js +60 -41
  170. package/src/en-CA.d.ts +36 -18
  171. package/src/en-CA.js +67 -46
  172. package/src/en-GB.d.ts +17 -11
  173. package/src/en-GB.js +56 -41
  174. package/src/en-GH.d.ts +32 -3
  175. package/src/en-GH.js +104 -26
  176. package/src/en-IE.d.ts +17 -11
  177. package/src/en-IE.js +56 -41
  178. package/src/en-IN.d.ts +17 -11
  179. package/src/en-IN.js +60 -41
  180. package/src/en-KE.d.ts +28 -3
  181. package/src/en-KE.js +93 -26
  182. package/src/en-MY.d.ts +26 -3
  183. package/src/en-MY.js +91 -26
  184. package/src/en-NG.d.ts +17 -11
  185. package/src/en-NG.js +56 -41
  186. package/src/en-NZ.d.ts +32 -3
  187. package/src/en-NZ.js +85 -31
  188. package/src/en-PH.d.ts +32 -3
  189. package/src/en-PH.js +97 -26
  190. package/src/en-PK.d.ts +17 -11
  191. package/src/en-PK.js +60 -41
  192. package/src/en-SG.d.ts +28 -3
  193. package/src/en-SG.js +93 -26
  194. package/src/en-US.d.ts +36 -18
  195. package/src/en-US.js +70 -47
  196. package/src/en-ZA.d.ts +17 -11
  197. package/src/en-ZA.js +56 -41
  198. package/src/es-ES.d.ts +53 -21
  199. package/src/es-ES.js +104 -56
  200. package/src/es-MX.d.ts +53 -21
  201. package/src/es-MX.js +104 -56
  202. package/src/es-US.d.ts +53 -21
  203. package/src/es-US.js +92 -51
  204. package/src/fa-IR.d.ts +3 -5
  205. package/src/fa-IR.js +28 -13
  206. package/src/fi-FI.d.ts +3 -6
  207. package/src/fi-FI.js +47 -29
  208. package/src/fil-PH.d.ts +3 -5
  209. package/src/fil-PH.js +61 -28
  210. package/src/fr-BE.d.ts +31 -15
  211. package/src/fr-BE.js +128 -57
  212. package/src/fr-FR.d.ts +31 -16
  213. package/src/fr-FR.js +97 -60
  214. package/src/gu-IN.d.ts +3 -5
  215. package/src/gu-IN.js +31 -16
  216. package/src/ha-NG.d.ts +3 -5
  217. package/src/ha-NG.js +55 -27
  218. package/src/hbo-IL.d.ts +26 -12
  219. package/src/hbo-IL.js +92 -51
  220. package/src/he-IL.d.ts +17 -10
  221. package/src/he-IL.js +92 -50
  222. package/src/hi-IN.d.ts +3 -5
  223. package/src/hi-IN.js +30 -17
  224. package/src/hr-HR.d.ts +21 -10
  225. package/src/hr-HR.js +89 -33
  226. package/src/hu-HU.d.ts +3 -5
  227. package/src/hu-HU.js +57 -23
  228. package/src/id-ID.d.ts +3 -5
  229. package/src/id-ID.js +56 -23
  230. package/src/it-IT.d.ts +17 -11
  231. package/src/it-IT.js +74 -43
  232. package/src/ja-JP.d.ts +3 -6
  233. package/src/ja-JP.js +39 -26
  234. package/src/ka-GE.d.ts +3 -6
  235. package/src/ka-GE.js +38 -26
  236. package/src/kn-IN.d.ts +3 -5
  237. package/src/kn-IN.js +31 -16
  238. package/src/ko-KR.d.ts +3 -6
  239. package/src/ko-KR.js +34 -26
  240. package/src/lt-LT.d.ts +21 -11
  241. package/src/lt-LT.js +64 -42
  242. package/src/lv-LV.d.ts +21 -11
  243. package/src/lv-LV.js +79 -51
  244. package/src/mr-IN.d.ts +3 -5
  245. package/src/mr-IN.js +31 -16
  246. package/src/ms-MY.d.ts +3 -5
  247. package/src/ms-MY.js +58 -24
  248. package/src/nb-NO.d.ts +3 -6
  249. package/src/nb-NO.js +54 -34
  250. package/src/nl-NL.d.ts +41 -20
  251. package/src/nl-NL.js +111 -69
  252. package/src/pa-IN.d.ts +3 -5
  253. package/src/pa-IN.js +32 -16
  254. package/src/pl-PL.d.ts +21 -11
  255. package/src/pl-PL.js +69 -45
  256. package/src/pt-BR.d.ts +42 -0
  257. package/src/pt-BR.js +574 -0
  258. package/src/pt-PT.d.ts +17 -11
  259. package/src/pt-PT.js +80 -48
  260. package/src/ro-RO.d.ts +21 -11
  261. package/src/ro-RO.js +77 -39
  262. package/src/ru-RU.d.ts +35 -15
  263. package/src/ru-RU.js +100 -38
  264. package/src/sr-Cyrl-RS.d.ts +35 -15
  265. package/src/sr-Cyrl-RS.js +100 -38
  266. package/src/sr-Latn-RS.d.ts +35 -15
  267. package/src/sr-Latn-RS.js +100 -38
  268. package/src/sv-SE.d.ts +3 -6
  269. package/src/sv-SE.js +53 -34
  270. package/src/sw-KE.d.ts +3 -5
  271. package/src/sw-KE.js +50 -20
  272. package/src/ta-IN.d.ts +3 -5
  273. package/src/ta-IN.js +29 -17
  274. package/src/te-IN.d.ts +3 -5
  275. package/src/te-IN.js +31 -16
  276. package/src/th-TH.d.ts +3 -5
  277. package/src/th-TH.js +42 -19
  278. package/src/tr-TR.d.ts +17 -11
  279. package/src/tr-TR.js +63 -37
  280. package/src/uk-UA.d.ts +21 -10
  281. package/src/uk-UA.js +89 -33
  282. package/src/ur-PK.d.ts +3 -5
  283. package/src/ur-PK.js +32 -16
  284. package/src/utils/check-max.d.ts +26 -0
  285. package/src/utils/check-max.js +33 -0
  286. package/src/utils/expand-scientific.d.ts +0 -4
  287. package/src/utils/expand-scientific.js +7 -9
  288. package/src/utils/is-plain-object.d.ts +3 -4
  289. package/src/utils/is-plain-object.js +3 -4
  290. package/src/utils/parse-cardinal.d.ts +1 -2
  291. package/src/utils/parse-cardinal.js +12 -9
  292. package/src/utils/parse-currency.d.ts +1 -2
  293. package/src/utils/parse-currency.js +9 -11
  294. package/src/utils/parse-ordinal.d.ts +0 -1
  295. package/src/utils/parse-ordinal.js +9 -10
  296. package/src/utils/resolve-options.d.ts +17 -0
  297. package/src/utils/resolve-options.js +56 -0
  298. package/src/utils/scale.d.ts +49 -0
  299. package/src/utils/scale.js +65 -0
  300. package/src/vi-VN.d.ts +3 -6
  301. package/src/vi-VN.js +41 -28
  302. package/src/yo-NG.d.ts +3 -5
  303. package/src/yo-NG.js +49 -33
  304. package/src/zh-Hans-CN.d.ts +45 -20
  305. package/src/zh-Hans-CN.js +84 -31
  306. package/src/zh-Hant-TW.d.ts +45 -20
  307. package/src/zh-Hant-TW.js +85 -34
  308. package/src/utils/validate-options.d.ts +0 -8
  309. package/src/utils/validate-options.js +0 -16
package/src/ha-NG.js CHANGED
@@ -16,6 +16,8 @@
16
16
  import { parseCardinalValue } from './utils/parse-cardinal.js'
17
17
  import { parseCurrencyValue } from './utils/parse-currency.js'
18
18
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
19
+ import { checkMax } from './utils/check-max.js'
20
+ import { western } from './utils/scale.js'
19
21
 
20
22
  // ============================================================================
21
23
  // Vocabulary
@@ -24,7 +26,7 @@ import { parseOrdinalValue } from './utils/parse-ordinal.js'
24
26
  const ONES = ['', 'ɗaya', 'biyu', 'uku', 'huɗu', 'biyar', 'shida', 'bakwai', 'takwas', 'tara']
25
27
  const TEENS = ['goma', 'sha ɗaya', 'sha biyu', 'sha uku', 'sha huɗu', 'sha biyar', 'sha shida', 'sha bakwai', 'sha takwas', 'sha tara']
26
28
  // Arabic loanwords for tens
27
- const TENS = ['', '', 'ashirin', 'talatin', "arba'in", 'hamsin', 'sittin', "saba'in", 'tamanin', "tis'in"]
29
+ const TENS = ['', '', 'ashirin', 'talatin', 'arba\'in', 'hamsin', 'sittin', 'saba\'in', 'tamanin', 'tis\'in']
28
30
 
29
31
  const HUNDRED = 'ɗari'
30
32
  const THOUSAND = 'dubu'
@@ -36,6 +38,11 @@ const DECIMAL_SEP = 'digo'
36
38
  // Short scale
37
39
  const SCALE_WORDS = ['', THOUSAND, 'miliyan', 'biliyan']
38
40
 
41
+ // Supported magnitude ceiling (checked at the public entry points), derived from the scale table.
42
+ export const cardinalMax = western(SCALE_WORDS.length - 1)
43
+ export const ordinalMax = western(SCALE_WORDS.length - 1)
44
+ export const currencyMax = western(SCALE_WORDS.length - 1)
45
+
39
46
  // ============================================================================
40
47
  // Ordinal Vocabulary
41
48
  // ============================================================================
@@ -60,8 +67,10 @@ const KOBO = 'kobo'
60
67
  * Build segment for 0-999 with Hausa patterns.
61
68
  * Hausa uses reversed order for hundreds: "biyu ɗari" (200)
62
69
  * And "da" connector for ones: "ashirin da ɗaya" (21)
70
+ * @param {number} n - Integer segment value (0-999)
71
+ * @returns {string} The segment in Hausa words
63
72
  */
64
- function buildSegment (n) {
73
+ function buildSegment(n) {
65
74
  if (n === 0) return ''
66
75
 
67
76
  const ones = n % 10
@@ -74,7 +83,8 @@ function buildSegment (n) {
74
83
  if (hundredsDigit > 0) {
75
84
  if (hundredsDigit === 1) {
76
85
  parts.push(HUNDRED)
77
- } else {
86
+ }
87
+ else {
78
88
  // Reversed: multiplier + hundredWord
79
89
  parts.push(ONES[hundredsDigit] + ' ' + HUNDRED)
80
90
  }
@@ -85,20 +95,25 @@ function buildSegment (n) {
85
95
 
86
96
  if (tensOnes === 0) {
87
97
  // Just hundreds
88
- } else if (tensOnes < 10) {
98
+ }
99
+ else if (tensOnes < 10) {
89
100
  // Single digit - with "da" connector if after hundreds
90
101
  if (hundredsDigit > 0) {
91
102
  parts.push('da ' + ONES[ones])
92
- } else {
103
+ }
104
+ else {
93
105
  parts.push(ONES[ones])
94
106
  }
95
- } else if (tensOnes < 20) {
107
+ }
108
+ else if (tensOnes < 20) {
96
109
  // Teens (10-19): "sha X"
97
110
  parts.push(TEENS[ones])
98
- } else if (ones === 0) {
111
+ }
112
+ else if (ones === 0) {
99
113
  // Even tens (20, 30, 40, etc.)
100
114
  parts.push(TENS[tensDigit])
101
- } else {
115
+ }
116
+ else {
102
117
  // Tens + ones with "da" connector
103
118
  parts.push(TENS[tensDigit] + ' da ' + ONES[ones])
104
119
  }
@@ -110,7 +125,11 @@ function buildSegment (n) {
110
125
  // Conversion Functions
111
126
  // ============================================================================
112
127
 
113
- function integerToWords (n) {
128
+ /**
129
+ * @param {bigint} n - Non-negative integer value
130
+ * @returns {string} The integer in Hausa words
131
+ */
132
+ function integerToWords(n) {
114
133
  if (n === 0n) return ZERO
115
134
 
116
135
  if (n < 1000n) {
@@ -122,12 +141,18 @@ function integerToWords (n) {
122
141
 
123
142
  /**
124
143
  * Checks if a word is a single digit (1-9).
144
+ * @param {string} word - Word to test
145
+ * @returns {boolean} True if the word is a single digit (1-9)
125
146
  */
126
- function isSingleDigit (word) {
147
+ function isSingleDigit(word) {
127
148
  return ONES.slice(1).includes(word)
128
149
  }
129
150
 
130
- function buildLargeNumberWords (n) {
151
+ /**
152
+ * @param {bigint} n - Integer value of 1000 or greater
153
+ * @returns {string} The number in Hausa words
154
+ */
155
+ function buildLargeNumberWords(n) {
131
156
  const numStr = n.toString()
132
157
  const len = numStr.length
133
158
 
@@ -157,7 +182,8 @@ function buildLargeNumberWords (n) {
157
182
 
158
183
  if (scaleIndex === 0) {
159
184
  rawParts.push(buildSegment(segment))
160
- } else {
185
+ }
186
+ else {
161
187
  rawParts.push(buildSegment(segment))
162
188
  rawParts.push(scaleWord)
163
189
  }
@@ -188,11 +214,12 @@ function buildLargeNumberWords (n) {
188
214
 
189
215
  // Determine if we need "da" connector
190
216
  // Use "da" when current is a single digit following a scale word
191
- if (prevPart && isSingleDigit(part) &&
192
- (prevPart === THOUSAND || prevPart === HUNDRED ||
193
- SCALE_WORDS.includes(prevPart))) {
217
+ if (prevPart && isSingleDigit(part)
218
+ && (prevPart === THOUSAND || prevPart === HUNDRED
219
+ || SCALE_WORDS.includes(prevPart))) {
194
220
  result.push(' da ')
195
- } else if (i > 0) {
221
+ }
222
+ else if (i > 0) {
196
223
  result.push(' ')
197
224
  }
198
225
 
@@ -202,7 +229,11 @@ function buildLargeNumberWords (n) {
202
229
  return result.join('')
203
230
  }
204
231
 
205
- function decimalPartToWords (decimalPart) {
232
+ /**
233
+ * @param {string} decimalPart - Digit string of the fractional part
234
+ * @returns {string} The fractional digits in Hausa words
235
+ */
236
+ function decimalPartToWords(decimalPart) {
206
237
  // Per-digit decimal reading
207
238
  const digits = []
208
239
  for (const char of decimalPart) {
@@ -214,12 +245,12 @@ function decimalPartToWords (decimalPart) {
214
245
 
215
246
  /**
216
247
  * Converts a numeric value to Hausa words.
217
- *
218
248
  * @param {number | string | bigint} value - The numeric value to convert
219
249
  * @returns {string} The number in Hausa words
220
250
  */
221
- function toCardinal (value) {
251
+ function toCardinal(value) {
222
252
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
253
+ checkMax(integerPart, cardinalMax)
223
254
 
224
255
  let result = ''
225
256
 
@@ -244,11 +275,10 @@ function toCardinal (value) {
244
275
  * Converts a non-negative integer to Hausa ordinal words.
245
276
  *
246
277
  * Hausa ordinals: na farko (1st), na biyu (2nd), na uku (3rd), etc.
247
- *
248
278
  * @param {bigint} n - Positive integer to convert
249
279
  * @returns {string} Hausa ordinal words
250
280
  */
251
- function integerToOrdinal (n) {
281
+ function integerToOrdinal(n) {
252
282
  // Special form for first
253
283
  if (n === 1n) return ORDINAL_FIRST
254
284
 
@@ -258,19 +288,18 @@ function integerToOrdinal (n) {
258
288
 
259
289
  /**
260
290
  * Converts a numeric value to Hausa ordinal words.
261
- *
262
291
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
263
292
  * @returns {string} The number as ordinal words
264
293
  * @throws {TypeError} If value is not a valid numeric type
265
294
  * @throws {RangeError} If value is negative, zero, or has a decimal part
266
- *
267
295
  * @example
268
296
  * toOrdinal(1) // 'na farko'
269
297
  * toOrdinal(2) // 'na biyu'
270
298
  * toOrdinal(10) // 'na goma'
271
299
  */
272
- function toOrdinal (value) {
300
+ function toOrdinal(value) {
273
301
  const integerPart = parseOrdinalValue(value)
302
+ checkMax(integerPart, ordinalMax)
274
303
  return integerToOrdinal(integerPart)
275
304
  }
276
305
 
@@ -282,19 +311,18 @@ function toOrdinal (value) {
282
311
  * Converts a numeric value to Hausa currency words (Nigerian Naira).
283
312
  *
284
313
  * Uses naira and kobo (100 kobo = 1 naira).
285
- *
286
314
  * @param {number | string | bigint} value - The currency amount to convert
287
315
  * @returns {string} The amount in Hausa currency words
288
316
  * @throws {TypeError} If value is not a valid numeric type
289
317
  * @throws {Error} If value is not a valid number format
290
- *
291
318
  * @example
292
319
  * toCurrency(42) // 'arba'in da biyu naira'
293
320
  * toCurrency(1.50) // 'ɗaya naira da hamsin kobo'
294
321
  * toCurrency(-5) // 'babu biyar naira'
295
322
  */
296
- function toCurrency (value) {
323
+ function toCurrency(value) {
297
324
  const { isNegative, dollars: naira, cents: kobo } = parseCurrencyValue(value)
325
+ checkMax(naira, currencyMax)
298
326
 
299
327
  let result = ''
300
328
  if (isNegative) {
package/src/hbo-IL.d.ts CHANGED
@@ -1,24 +1,40 @@
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} [andWord] - Custom conjunction word
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
+ export type CardinalOptions = {
16
+ /**
17
+ * - Grammatical gender
18
+ */
19
+ gender?: "feminine" | "masculine" | undefined;
20
+ /**
21
+ * - Custom conjunction word
22
+ */
23
+ andWord?: string | undefined;
24
+ };
1
25
  /**
2
26
  * Converts a numeric value to Biblical Hebrew words.
3
- *
4
27
  * @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.andWord='ו'] - Custom conjunction word
28
+ * @param {CardinalOptions} [options] - Optional configuration
8
29
  * @returns {string} The number in Biblical Hebrew words
9
30
  */
10
- export function toCardinal(value: number | string | bigint, options?: {
11
- gender?: "masculine" | "feminine" | undefined;
12
- andWord?: string | undefined;
13
- }): string;
31
+ export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
14
32
  /**
15
33
  * Converts a numeric value to Biblical Hebrew ordinal words.
16
- *
17
34
  * @param {number | string | bigint} value - The numeric value to convert
18
35
  * @returns {string} The ordinal in Biblical Hebrew words
19
36
  * @throws {TypeError} If value is not a valid numeric type
20
37
  * @throws {Error} If value is not a positive integer
21
- *
22
38
  * @example
23
39
  * toOrdinal(1) // 'ראשון'
24
40
  * toOrdinal(21) // 'עשרים וראשון'
@@ -26,12 +42,10 @@ export function toCardinal(value: number | string | bigint, options?: {
26
42
  export function toOrdinal(value: number | string | bigint): string;
27
43
  /**
28
44
  * Converts a numeric value to Biblical Hebrew Shekel currency words.
29
- *
30
45
  * @param {number | string | bigint} value - The numeric value to convert
31
46
  * @returns {string} The currency in Biblical Hebrew words
32
47
  * @throws {TypeError} If value is not a valid numeric type
33
48
  * @throws {Error} If value is not a valid number format
34
- *
35
49
  * @example
36
50
  * toCurrency(1) // 'שקל אחד'
37
51
  * toCurrency(2.50) // 'שניים שקלים חמישים גרות'
package/src/hbo-IL.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 { bounded, western } from './utils/scale.js'
19
+ import { resolveOptions } from './utils/resolve-options.js'
18
20
 
19
21
  // ============================================================================
20
22
  // Vocabulary (arrays for indexed access - faster than object property lookup)
@@ -39,6 +41,15 @@ const HUNDREDS_FEM = ['', 'מאה', 'מאתיים', 'שלש מאות', 'ארבע
39
41
  const SCALE = ['', 'אלף', 'מיליון', 'מיליארד', 'טריליון', 'קוודרליון', 'קווינטיליון']
40
42
  const SCALE_PLURAL = ['', 'אלפים', 'מיליונים', 'מיליארדים', 'טריליונים', 'קוודרליונים', 'קווינטיליונים']
41
43
 
44
+ // Supported magnitude ceilings (checked at the public entry points). Cardinal
45
+ // SCALE reaches index SCALE.length-1 (10^18), so cardinals/currency must stay
46
+ // below 10^(SCALE.length * 3) = 10^21. Ordinals are bounded lower: past 10^6
47
+ // the millions multiplier is built with buildScaleSegment (0-999), so n must
48
+ // stay below 10^9. Decimals are read digit-by-digit (no ceiling).
49
+ export const cardinalMax = western(SCALE.length - 1)
50
+ export const ordinalMax = bounded(9)
51
+ export const currencyMax = western(SCALE.length - 1)
52
+
42
53
  const ZERO = 'אפס'
43
54
  const NEGATIVE = 'מינוס'
44
55
  const DECIMAL_SEP = 'נקודה'
@@ -62,8 +73,14 @@ const GERAH_PLURAL = 'גרות'
62
73
  /**
63
74
  * Builds segment word for scale segments (thousands, millions, etc.).
64
75
  * "ו" is added before tens and ones when following hundreds.
76
+ * @param {number} n - Segment value (0-999)
77
+ * @param {string} andWord - Conjunction word
78
+ * @param {string[]} ONES - Ones vocabulary
79
+ * @param {string[]} TEENS - Teens vocabulary
80
+ * @param {string[]} HUNDREDS_ARR - Hundreds vocabulary
81
+ * @returns {string} Segment word
65
82
  */
66
- function buildScaleSegment (n, andWord, ONES, TEENS, HUNDREDS_ARR) {
83
+ function buildScaleSegment(n, andWord, ONES, TEENS, HUNDREDS_ARR) {
67
84
  if (n === 0) return ''
68
85
 
69
86
  const ones = n % 10
@@ -83,15 +100,18 @@ function buildScaleSegment (n, andWord, ONES, TEENS, HUNDREDS_ARR) {
83
100
  const teenWord = TEENS[ones]
84
101
  if (result) {
85
102
  result += ' ' + andWord + teenWord
86
- } else {
103
+ }
104
+ else {
87
105
  result = teenWord
88
106
  }
89
- } else {
107
+ }
108
+ else {
90
109
  // Tens (20-90)
91
110
  if (tens >= 2) {
92
111
  if (result) {
93
112
  result += ' ' + andWord + TENS[tens]
94
- } else {
113
+ }
114
+ else {
95
115
  result = TENS[tens]
96
116
  }
97
117
  }
@@ -100,7 +120,8 @@ function buildScaleSegment (n, andWord, ONES, TEENS, HUNDREDS_ARR) {
100
120
  if (ones > 0) {
101
121
  if (result) {
102
122
  result += ' ' + andWord + ONES[ones]
103
- } else {
123
+ }
124
+ else {
104
125
  result = ONES[ones]
105
126
  }
106
127
  }
@@ -112,8 +133,14 @@ function buildScaleSegment (n, andWord, ONES, TEENS, HUNDREDS_ARR) {
112
133
  /**
113
134
  * Builds segment word for units segment (no scale word).
114
135
  * "ו" is only added before the final ones digit.
136
+ * @param {number} n - Segment value (0-999)
137
+ * @param {string} andWord - Conjunction word
138
+ * @param {string[]} ONES - Ones vocabulary
139
+ * @param {string[]} TEENS - Teens vocabulary
140
+ * @param {string[]} HUNDREDS_ARR - Hundreds vocabulary
141
+ * @returns {string} Segment word
115
142
  */
116
- function buildUnitsSegment (n, andWord, ONES, TEENS, HUNDREDS_ARR) {
143
+ function buildUnitsSegment(n, andWord, ONES, TEENS, HUNDREDS_ARR) {
117
144
  if (n === 0) return ''
118
145
 
119
146
  const ones = n % 10
@@ -132,14 +159,17 @@ function buildUnitsSegment (n, andWord, ONES, TEENS, HUNDREDS_ARR) {
132
159
  // Teens (10-19)
133
160
  if (result) {
134
161
  result += ' ' + TEENS[ones]
135
- } else {
162
+ }
163
+ else {
136
164
  result = TEENS[ones]
137
165
  }
138
- } else {
166
+ }
167
+ else {
139
168
  if (tens >= 2) {
140
169
  if (result) {
141
170
  result += ' ' + TENS[tens]
142
- } else {
171
+ }
172
+ else {
143
173
  result = TENS[tens]
144
174
  }
145
175
  }
@@ -148,7 +178,8 @@ function buildUnitsSegment (n, andWord, ONES, TEENS, HUNDREDS_ARR) {
148
178
  if (ones > 0) {
149
179
  if (result) {
150
180
  result += ' ' + andWord + ONES[ones]
151
- } else {
181
+ }
182
+ else {
152
183
  result = ONES[ones]
153
184
  }
154
185
  }
@@ -163,12 +194,12 @@ function buildUnitsSegment (n, andWord, ONES, TEENS, HUNDREDS_ARR) {
163
194
 
164
195
  /**
165
196
  * Converts a non-negative integer to Biblical Hebrew words.
166
- *
167
197
  * @param {bigint} n - Non-negative integer to convert
168
- * @param {Object} options - Conversion options
198
+ * @param {('masculine'|'feminine')} gender - Grammatical gender
199
+ * @param {string} andWord - Conjunction word
169
200
  * @returns {string} Biblical Hebrew words
170
201
  */
171
- function integerToWords (n, gender, andWord) {
202
+ function integerToWords(n, gender, andWord) {
172
203
  if (n === 0n) return ZERO
173
204
 
174
205
  const isFeminine = gender === 'feminine'
@@ -206,28 +237,34 @@ function integerToWords (n, gender, andWord) {
206
237
  // Add "ו" before single-digit units when following scale words
207
238
  if (segment <= 9) {
208
239
  result += ' ' + andWord + segmentWord
209
- } else {
240
+ }
241
+ else {
210
242
  result += ' ' + segmentWord
211
243
  }
212
- } else {
244
+ }
245
+ else {
213
246
  result = segmentWord
214
247
  }
215
- } else if (i === 1) {
248
+ }
249
+ else if (i === 1) {
216
250
  // Thousands - special handling for 1-9
217
251
  if (segment <= 9) {
218
252
  if (result) result += ' '
219
253
  result += THOUSANDS_SPECIAL[segment]
220
- } else {
254
+ }
255
+ else {
221
256
  const segmentWord = buildScaleSegment(segment, andWord, ONES, TEENS, HUNDREDS_ARR)
222
257
  if (result) result += ' '
223
258
  result += segmentWord + ' ' + SCALE[1]
224
259
  }
225
- } else {
260
+ }
261
+ else {
226
262
  // Millions and above
227
263
  if (segment === 1) {
228
264
  if (result) result += ' '
229
265
  result += SCALE[i]
230
- } else {
266
+ }
267
+ else {
231
268
  const segmentWord = buildScaleSegment(segment, andWord, ONES, TEENS, HUNDREDS_ARR)
232
269
  if (result) result += ' '
233
270
  result += segmentWord + ' ' + SCALE_PLURAL[i]
@@ -240,12 +277,11 @@ function integerToWords (n, gender, andWord) {
240
277
 
241
278
  /**
242
279
  * Converts decimal digits to Biblical Hebrew words (digit by digit).
243
- *
244
280
  * @param {string} decimalPart - Decimal digits (without the point)
245
- * @param {Object} options - Conversion options
281
+ * @param {('masculine'|'feminine')} gender - Grammatical gender
246
282
  * @returns {string} Biblical Hebrew words for decimal part
247
283
  */
248
- function decimalPartToWords (decimalPart, gender, andWord) {
284
+ function decimalPartToWords(decimalPart, gender) {
249
285
  const ONES = gender === 'feminine' ? ONES_FEM : ONES_MASC
250
286
 
251
287
  let result = ''
@@ -258,24 +294,28 @@ function decimalPartToWords (decimalPart, gender, andWord) {
258
294
  return result
259
295
  }
260
296
 
297
+ /**
298
+ * @typedef {object} CardinalOptions
299
+ * @property {('masculine'|'feminine')} [gender] - Grammatical gender
300
+ * @property {string} [andWord] - Custom conjunction word
301
+ */
302
+
303
+ /** @type {Required<CardinalOptions>} */
304
+ export const cardinalDefaults = { gender: 'masculine', andWord: 'ו' }
305
+
306
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
307
+ export const cardinalValues = { gender: ['masculine', 'feminine'] }
308
+
261
309
  /**
262
310
  * Converts a numeric value to Biblical Hebrew words.
263
- *
264
311
  * @param {number | string | bigint} value - The numeric value to convert
265
- * @param {Object} [options] - Optional configuration
266
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
267
- * @param {string} [options.andWord='ו'] - Custom conjunction word
312
+ * @param {CardinalOptions} [options] - Optional configuration
268
313
  * @returns {string} The number in Biblical Hebrew words
269
314
  */
270
- function toCardinal (value, options) {
271
- options = validateOptions(options)
315
+ function toCardinal(value, options) {
272
316
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
273
-
274
- // Apply option defaults
275
- const {
276
- gender = 'masculine',
277
- andWord = 'ו'
278
- } = options
317
+ checkMax(integerPart, cardinalMax)
318
+ const { gender, andWord } = resolveOptions(options, cardinalDefaults, cardinalValues)
279
319
 
280
320
  let result = ''
281
321
 
@@ -286,7 +326,7 @@ function toCardinal (value, options) {
286
326
  result += integerToWords(integerPart, gender, andWord)
287
327
 
288
328
  if (decimalPart) {
289
- result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, gender, andWord)
329
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, gender)
290
330
  }
291
331
 
292
332
  return result
@@ -298,11 +338,10 @@ function toCardinal (value, options) {
298
338
 
299
339
  /**
300
340
  * Builds ordinal for tens and ones (0-99).
301
- *
302
341
  * @param {number} n - Number 0-99
303
342
  * @returns {string} Ordinal word
304
343
  */
305
- function buildOrdinalTensOnes (n) {
344
+ function buildOrdinalTensOnes(n) {
306
345
  if (n === 0) return ''
307
346
  if (n < 10) return ORDINAL_ONES[n]
308
347
  if (n < 20) return ORDINAL_TEENS[n - 10]
@@ -320,11 +359,10 @@ function buildOrdinalTensOnes (n) {
320
359
 
321
360
  /**
322
361
  * Converts a non-negative integer to Biblical Hebrew ordinal words.
323
- *
324
362
  * @param {bigint} n - Non-negative integer to convert
325
363
  * @returns {string} Biblical Hebrew ordinal words
326
364
  */
327
- function integerToOrdinal (n) {
365
+ function integerToOrdinal(n) {
328
366
  if (n === 0n) return ''
329
367
  if (n === 1n) return ORDINAL_ONES[1]
330
368
 
@@ -360,7 +398,8 @@ function integerToOrdinal (n) {
360
398
  let result
361
399
  if (thousands <= 9) {
362
400
  result = THOUSANDS_MASC[thousands]
363
- } else {
401
+ }
402
+ else {
364
403
  result = buildScaleSegment(thousands, 'ו', ONES_MASC, TEENS_MASC, HUNDREDS) + ' ' + SCALE[1]
365
404
  }
366
405
 
@@ -392,7 +431,8 @@ function integerToOrdinal (n) {
392
431
  let result
393
432
  if (millions === 1) {
394
433
  result = SCALE[2]
395
- } else {
434
+ }
435
+ else {
396
436
  result = buildScaleSegment(millions, 'ו', ONES_MASC, TEENS_MASC, HUNDREDS) + ' ' + SCALE_PLURAL[2]
397
437
  }
398
438
 
@@ -401,18 +441,17 @@ function integerToOrdinal (n) {
401
441
 
402
442
  /**
403
443
  * Converts a numeric value to Biblical Hebrew ordinal words.
404
- *
405
444
  * @param {number | string | bigint} value - The numeric value to convert
406
445
  * @returns {string} The ordinal in Biblical Hebrew words
407
446
  * @throws {TypeError} If value is not a valid numeric type
408
447
  * @throws {Error} If value is not a positive integer
409
- *
410
448
  * @example
411
449
  * toOrdinal(1) // 'ראשון'
412
450
  * toOrdinal(21) // 'עשרים וראשון'
413
451
  */
414
- function toOrdinal (value) {
452
+ function toOrdinal(value) {
415
453
  const n = parseOrdinalValue(value)
454
+ checkMax(n, ordinalMax)
416
455
  return integerToOrdinal(n)
417
456
  }
418
457
 
@@ -422,18 +461,17 @@ function toOrdinal (value) {
422
461
 
423
462
  /**
424
463
  * Converts a numeric value to Biblical Hebrew Shekel currency words.
425
- *
426
464
  * @param {number | string | bigint} value - The numeric value to convert
427
465
  * @returns {string} The currency in Biblical Hebrew words
428
466
  * @throws {TypeError} If value is not a valid numeric type
429
467
  * @throws {Error} If value is not a valid number format
430
- *
431
468
  * @example
432
469
  * toCurrency(1) // 'שקל אחד'
433
470
  * toCurrency(2.50) // 'שניים שקלים חמישים גרות'
434
471
  */
435
- function toCurrency (value) {
472
+ function toCurrency(value) {
436
473
  const { isNegative, dollars, cents } = parseCurrencyValue(value)
474
+ checkMax(dollars, currencyMax)
437
475
 
438
476
  const parts = []
439
477
 
@@ -445,9 +483,11 @@ function toCurrency (value) {
445
483
  if (dollars > 0n || cents === 0n) {
446
484
  if (dollars === 1n) {
447
485
  parts.push(SHEKEL + ' ' + ONES_MASC[1])
448
- } else if (dollars === 2n) {
486
+ }
487
+ else if (dollars === 2n) {
449
488
  parts.push(ONES_MASC[2] + ' ' + SHEKEL_PLURAL)
450
- } else {
489
+ }
490
+ else {
451
491
  const shekelWord = integerToWords(dollars, 'masculine', 'ו')
452
492
  parts.push(shekelWord + ' ' + SHEKEL_PLURAL)
453
493
  }
@@ -458,7 +498,8 @@ function toCurrency (value) {
458
498
  const centNum = Number(cents)
459
499
  if (centNum === 1) {
460
500
  parts.push(GERAH + ' ' + ONES_FEM[1])
461
- } else {
501
+ }
502
+ else {
462
503
  const gerahWord = integerToWords(cents, 'feminine', 'ו')
463
504
  parts.push(gerahWord + ' ' + GERAH_PLURAL)
464
505
  }
package/src/he-IL.d.ts CHANGED
@@ -1,22 +1,31 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CardinalOptions
6
+ * @property {string} [andWord] - Custom conjunction word
7
+ */
8
+ /** @type {Required<CardinalOptions>} */
9
+ export const cardinalDefaults: Required<CardinalOptions>;
10
+ export type CardinalOptions = {
11
+ /**
12
+ * - Custom conjunction word
13
+ */
14
+ andWord?: string | undefined;
15
+ };
1
16
  /**
2
17
  * Converts a numeric value to Modern Hebrew words.
3
- *
4
18
  * @param {number | string | bigint} value - The numeric value to convert
5
- * @param {Object} [options] - Optional configuration
6
- * @param {string} [options.andWord='ו'] - Custom conjunction word
19
+ * @param {CardinalOptions} [options] - Optional configuration
7
20
  * @returns {string} The number in Modern Hebrew words
8
21
  */
9
- export function toCardinal(value: number | string | bigint, options?: {
10
- andWord?: string | undefined;
11
- }): string;
22
+ export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
12
23
  /**
13
24
  * Converts a numeric value to Hebrew ordinal words.
14
- *
15
25
  * @param {number | string | bigint} value - The numeric value to convert
16
26
  * @returns {string} The ordinal in Hebrew words
17
27
  * @throws {TypeError} If value is not a valid numeric type
18
28
  * @throws {Error} If value is not a positive integer
19
- *
20
29
  * @example
21
30
  * toOrdinal(1) // 'ראשון'
22
31
  * toOrdinal(21) // 'עשרים וראשון'
@@ -24,12 +33,10 @@ export function toCardinal(value: number | string | bigint, options?: {
24
33
  export function toOrdinal(value: number | string | bigint): string;
25
34
  /**
26
35
  * Converts a numeric value to Hebrew New Israeli Shekel currency words.
27
- *
28
36
  * @param {number | string | bigint} value - The numeric value to convert
29
37
  * @returns {string} The currency in Hebrew words
30
38
  * @throws {TypeError} If value is not a valid numeric type
31
39
  * @throws {Error} If value is not a valid number format
32
- *
33
40
  * @example
34
41
  * toCurrency(1) // 'שקל אחד'
35
42
  * toCurrency(2.50) // 'שניים שקלים חמישים אגורות'