n2words 1.24.0 → 3.0.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 (280) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +183 -156
  3. package/dist/languages/am-Latn.js +3 -0
  4. package/dist/languages/am-Latn.js.map +1 -0
  5. package/dist/languages/am.js +3 -0
  6. package/dist/languages/am.js.map +1 -0
  7. package/dist/languages/ar.js +3 -2
  8. package/dist/languages/ar.js.map +1 -1
  9. package/dist/languages/az.js +3 -2
  10. package/dist/languages/az.js.map +1 -1
  11. package/dist/languages/bn.js +3 -2
  12. package/dist/languages/bn.js.map +1 -1
  13. package/dist/languages/cs.js +3 -2
  14. package/dist/languages/cs.js.map +1 -1
  15. package/dist/languages/da.js +3 -2
  16. package/dist/languages/da.js.map +1 -1
  17. package/dist/languages/de.js +3 -2
  18. package/dist/languages/de.js.map +1 -1
  19. package/dist/languages/el.js +3 -2
  20. package/dist/languages/el.js.map +1 -1
  21. package/dist/languages/en.js +3 -2
  22. package/dist/languages/en.js.map +1 -1
  23. package/dist/languages/es.js +3 -2
  24. package/dist/languages/es.js.map +1 -1
  25. package/dist/languages/fa.js +3 -2
  26. package/dist/languages/fa.js.map +1 -1
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -2
  30. package/dist/languages/fil.js.map +1 -1
  31. package/dist/languages/fr-BE.js +3 -2
  32. package/dist/languages/fr-BE.js.map +1 -1
  33. package/dist/languages/fr.js +3 -2
  34. package/dist/languages/fr.js.map +1 -1
  35. package/dist/languages/gu.js +3 -2
  36. package/dist/languages/gu.js.map +1 -1
  37. package/dist/languages/ha.js +3 -0
  38. package/dist/languages/ha.js.map +1 -0
  39. package/dist/languages/hbo.js +3 -0
  40. package/dist/languages/hbo.js.map +1 -0
  41. package/dist/languages/he.js +3 -2
  42. package/dist/languages/he.js.map +1 -1
  43. package/dist/languages/hi.js +3 -2
  44. package/dist/languages/hi.js.map +1 -1
  45. package/dist/languages/hr.js +3 -2
  46. package/dist/languages/hr.js.map +1 -1
  47. package/dist/languages/hu.js +3 -2
  48. package/dist/languages/hu.js.map +1 -1
  49. package/dist/languages/id.js +3 -2
  50. package/dist/languages/id.js.map +1 -1
  51. package/dist/languages/it.js +3 -2
  52. package/dist/languages/it.js.map +1 -1
  53. package/dist/languages/ja.js +3 -2
  54. package/dist/languages/ja.js.map +1 -1
  55. package/dist/languages/kn.js +3 -2
  56. package/dist/languages/kn.js.map +1 -1
  57. package/dist/languages/ko.js +3 -2
  58. package/dist/languages/ko.js.map +1 -1
  59. package/dist/languages/lt.js +3 -2
  60. package/dist/languages/lt.js.map +1 -1
  61. package/dist/languages/lv.js +3 -2
  62. package/dist/languages/lv.js.map +1 -1
  63. package/dist/languages/mr.js +3 -2
  64. package/dist/languages/mr.js.map +1 -1
  65. package/dist/languages/ms.js +3 -2
  66. package/dist/languages/ms.js.map +1 -1
  67. package/dist/languages/nb.js +3 -2
  68. package/dist/languages/nb.js.map +1 -1
  69. package/dist/languages/nl.js +3 -2
  70. package/dist/languages/nl.js.map +1 -1
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -2
  74. package/dist/languages/pl.js.map +1 -1
  75. package/dist/languages/pt.js +3 -2
  76. package/dist/languages/pt.js.map +1 -1
  77. package/dist/languages/ro.js +3 -2
  78. package/dist/languages/ro.js.map +1 -1
  79. package/dist/languages/ru.js +3 -2
  80. package/dist/languages/ru.js.map +1 -1
  81. package/dist/languages/sr-Cyrl.js +3 -0
  82. package/dist/languages/sr-Cyrl.js.map +1 -0
  83. package/dist/languages/sr-Latn.js +3 -2
  84. package/dist/languages/sr-Latn.js.map +1 -1
  85. package/dist/languages/sv.js +3 -2
  86. package/dist/languages/sv.js.map +1 -1
  87. package/dist/languages/sw.js +3 -2
  88. package/dist/languages/sw.js.map +1 -1
  89. package/dist/languages/ta.js +3 -2
  90. package/dist/languages/ta.js.map +1 -1
  91. package/dist/languages/te.js +3 -2
  92. package/dist/languages/te.js.map +1 -1
  93. package/dist/languages/th.js +3 -2
  94. package/dist/languages/th.js.map +1 -1
  95. package/dist/languages/tr.js +3 -2
  96. package/dist/languages/tr.js.map +1 -1
  97. package/dist/languages/uk.js +3 -2
  98. package/dist/languages/uk.js.map +1 -1
  99. package/dist/languages/ur.js +3 -2
  100. package/dist/languages/ur.js.map +1 -1
  101. package/dist/languages/vi.js +3 -2
  102. package/dist/languages/vi.js.map +1 -1
  103. package/dist/languages/zh-Hans.js +3 -2
  104. package/dist/languages/zh-Hans.js.map +1 -1
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +3 -2
  108. package/dist/n2words.js.map +1 -1
  109. package/lib/languages/am-Latn.d.ts +7 -0
  110. package/lib/languages/am-Latn.js +164 -0
  111. package/lib/languages/am.d.ts +7 -0
  112. package/lib/languages/am.js +164 -0
  113. package/lib/languages/ar.d.ts +17 -0
  114. package/lib/languages/ar.js +171 -209
  115. package/lib/languages/az.d.ts +7 -0
  116. package/lib/languages/az.js +167 -49
  117. package/lib/languages/bn.d.ts +7 -0
  118. package/lib/languages/bn.js +142 -123
  119. package/lib/languages/cs.d.ts +18 -0
  120. package/lib/languages/cs.js +303 -176
  121. package/lib/languages/da.d.ts +14 -0
  122. package/lib/languages/da.js +267 -139
  123. package/lib/languages/de.d.ts +17 -0
  124. package/lib/languages/de.js +310 -113
  125. package/lib/languages/el.d.ts +14 -0
  126. package/lib/languages/el.js +225 -98
  127. package/lib/languages/en.d.ts +17 -0
  128. package/lib/languages/en.js +235 -102
  129. package/lib/languages/es.d.ts +21 -0
  130. package/lib/languages/es.js +307 -125
  131. package/lib/languages/fa.d.ts +7 -0
  132. package/lib/languages/fa.js +115 -108
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +7 -0
  136. package/lib/languages/fil.js +199 -139
  137. package/lib/languages/fr-BE.d.ts +11 -0
  138. package/lib/languages/fr-BE.js +287 -48
  139. package/lib/languages/fr.d.ts +21 -0
  140. package/lib/languages/fr.js +343 -119
  141. package/lib/languages/gu.d.ts +7 -0
  142. package/lib/languages/gu.js +125 -144
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +13 -0
  146. package/lib/languages/hbo.js +300 -0
  147. package/lib/languages/he.d.ts +13 -0
  148. package/lib/languages/he.js +230 -283
  149. package/lib/languages/hi.d.ts +7 -0
  150. package/lib/languages/hi.js +142 -123
  151. package/lib/languages/hr.d.ts +11 -0
  152. package/lib/languages/hr.js +190 -129
  153. package/lib/languages/hu.d.ts +7 -0
  154. package/lib/languages/hu.js +194 -133
  155. package/lib/languages/id.d.ts +7 -0
  156. package/lib/languages/id.js +167 -140
  157. package/lib/languages/it.d.ts +19 -0
  158. package/lib/languages/it.js +337 -108
  159. package/lib/languages/ja.d.ts +17 -0
  160. package/lib/languages/ja.js +224 -155
  161. package/lib/languages/kn.d.ts +7 -0
  162. package/lib/languages/kn.js +128 -62
  163. package/lib/languages/ko.d.ts +14 -0
  164. package/lib/languages/ko.js +250 -70
  165. package/lib/languages/lt.d.ts +18 -0
  166. package/lib/languages/lt.js +287 -148
  167. package/lib/languages/lv.d.ts +18 -0
  168. package/lib/languages/lv.js +291 -123
  169. package/lib/languages/mr.d.ts +7 -0
  170. package/lib/languages/mr.js +125 -144
  171. package/lib/languages/ms.d.ts +7 -0
  172. package/lib/languages/ms.js +171 -112
  173. package/lib/languages/nb.d.ts +14 -0
  174. package/lib/languages/nb.js +275 -100
  175. package/lib/languages/nl.d.ts +26 -0
  176. package/lib/languages/nl.js +307 -174
  177. package/lib/languages/pa.d.ts +7 -0
  178. package/lib/languages/pa.js +163 -0
  179. package/lib/languages/pl.d.ts +22 -0
  180. package/lib/languages/pl.js +299 -158
  181. package/lib/languages/pt.d.ts +17 -0
  182. package/lib/languages/pt.js +279 -120
  183. package/lib/languages/ro.d.ts +18 -0
  184. package/lib/languages/ro.js +214 -337
  185. package/lib/languages/ru.d.ts +11 -0
  186. package/lib/languages/ru.js +219 -95
  187. package/lib/languages/sr-Cyrl.d.ts +11 -0
  188. package/lib/languages/sr-Cyrl.js +215 -0
  189. package/lib/languages/sr-Latn.d.ts +11 -0
  190. package/lib/languages/sr-Latn.js +190 -132
  191. package/lib/languages/sv.d.ts +14 -0
  192. package/lib/languages/sv.js +280 -103
  193. package/lib/languages/sw.d.ts +7 -0
  194. package/lib/languages/sw.js +135 -103
  195. package/lib/languages/ta.d.ts +7 -0
  196. package/lib/languages/ta.js +133 -205
  197. package/lib/languages/te.d.ts +7 -0
  198. package/lib/languages/te.js +148 -213
  199. package/lib/languages/th.d.ts +7 -0
  200. package/lib/languages/th.js +139 -101
  201. package/lib/languages/tr.d.ts +18 -0
  202. package/lib/languages/tr.js +246 -66
  203. package/lib/languages/uk.d.ts +11 -0
  204. package/lib/languages/uk.js +197 -101
  205. package/lib/languages/ur.d.ts +7 -0
  206. package/lib/languages/ur.js +160 -123
  207. package/lib/languages/vi.d.ts +17 -0
  208. package/lib/languages/vi.js +287 -164
  209. package/lib/languages/zh-Hans.d.ts +11 -0
  210. package/lib/languages/zh-Hans.js +159 -142
  211. package/lib/languages/zh-Hant.d.ts +11 -0
  212. package/lib/languages/zh-Hant.js +202 -0
  213. package/lib/n2words.d.ts +53 -0
  214. package/lib/n2words.js +91 -227
  215. package/lib/utils/is-plain-object.d.ts +13 -0
  216. package/lib/utils/is-plain-object.js +17 -0
  217. package/lib/utils/parse-numeric.d.ts +17 -0
  218. package/lib/utils/parse-numeric.js +108 -0
  219. package/lib/utils/validate-options.d.ts +8 -0
  220. package/lib/utils/validate-options.js +16 -0
  221. package/package.json +118 -67
  222. package/dist/languages/pa-Guru.js +0 -2
  223. package/dist/languages/pa-Guru.js.map +0 -1
  224. package/lib/classes/abstract-language.js +0 -261
  225. package/lib/classes/greedy-scale-language.js +0 -195
  226. package/lib/classes/slavic-language.js +0 -251
  227. package/lib/classes/south-asian-language.js +0 -161
  228. package/lib/classes/turkic-language.js +0 -63
  229. package/lib/languages/pa-Guru.js +0 -126
  230. package/typings/classes/abstract-language.d.ts +0 -144
  231. package/typings/classes/greedy-scale-language.d.ts +0 -148
  232. package/typings/classes/slavic-language.d.ts +0 -145
  233. package/typings/classes/south-asian-language.d.ts +0 -101
  234. package/typings/classes/turkic-language.d.ts +0 -42
  235. package/typings/languages/ar.d.ts +0 -93
  236. package/typings/languages/az.d.ts +0 -25
  237. package/typings/languages/bn.d.ts +0 -1
  238. package/typings/languages/cs.d.ts +0 -120
  239. package/typings/languages/da.d.ts +0 -53
  240. package/typings/languages/de.d.ts +0 -26
  241. package/typings/languages/el.d.ts +0 -11
  242. package/typings/languages/en.d.ts +0 -30
  243. package/typings/languages/es.d.ts +0 -43
  244. package/typings/languages/fa.d.ts +0 -81
  245. package/typings/languages/fil.d.ts +0 -12
  246. package/typings/languages/fr-BE.d.ts +0 -41
  247. package/typings/languages/fr.d.ts +0 -43
  248. package/typings/languages/gu.d.ts +0 -12
  249. package/typings/languages/he.d.ts +0 -197
  250. package/typings/languages/hi.d.ts +0 -1
  251. package/typings/languages/hr.d.ts +0 -110
  252. package/typings/languages/hu.d.ts +0 -37
  253. package/typings/languages/id.d.ts +0 -69
  254. package/typings/languages/it.d.ts +0 -51
  255. package/typings/languages/ja.d.ts +0 -58
  256. package/typings/languages/kn.d.ts +0 -11
  257. package/typings/languages/ko.d.ts +0 -25
  258. package/typings/languages/lt.d.ts +0 -110
  259. package/typings/languages/lv.d.ts +0 -99
  260. package/typings/languages/mr.d.ts +0 -12
  261. package/typings/languages/ms.d.ts +0 -37
  262. package/typings/languages/nb.d.ts +0 -27
  263. package/typings/languages/nl.d.ts +0 -65
  264. package/typings/languages/pa-Guru.d.ts +0 -1
  265. package/typings/languages/pl.d.ts +0 -116
  266. package/typings/languages/pt.d.ts +0 -39
  267. package/typings/languages/ro.d.ts +0 -229
  268. package/typings/languages/ru.d.ts +0 -108
  269. package/typings/languages/sr-Latn.d.ts +0 -98
  270. package/typings/languages/sv.d.ts +0 -30
  271. package/typings/languages/sw.d.ts +0 -1
  272. package/typings/languages/ta.d.ts +0 -1
  273. package/typings/languages/te.d.ts +0 -1
  274. package/typings/languages/th.d.ts +0 -1
  275. package/typings/languages/tr.d.ts +0 -46
  276. package/typings/languages/uk.d.ts +0 -117
  277. package/typings/languages/ur.d.ts +0 -1
  278. package/typings/languages/vi.d.ts +0 -116
  279. package/typings/languages/zh-Hans.d.ts +0 -57
  280. package/typings/n2words.d.ts +0 -177
@@ -1,243 +1,205 @@
1
- import AbstractLanguage from '../classes/abstract-language.js'
2
-
3
- /**
4
- * @typedef {Object} ArabicOptions
5
- * @property {string} [negativeWord='ناقص'] Word for negative numbers (minus).
6
- * @property {boolean} [feminine=false] Use feminine forms for numbers.
7
- */
8
-
9
1
  /**
10
- * Arabic language converter.
11
- *
12
- * Converts numbers to Arabic words with full grammatical support:
13
- * - Gender agreement (masculine/feminine forms)
14
- * - Complex pluralization rules (singular, dual, plural forms)
15
- * - Special handling for hundreds, thousands, millions, etc.
16
- * - Right-to-left text orientation
17
- * - Traditional Arabic number naming conventions
18
- *
19
- * Key Features:
20
- * - Gender-aware number forms (واحد masculine vs واحدة feminine)
21
- * - Dual forms: اثنان/اثنتان (two masculine/feminine)
22
- * - Complex rule system for numbers 3-10 (requiring feminine when referring to countables)
23
- * - Group-based algorithm: splits number by powers of 1000 (ones, thousands, millions, billions)
24
- * - Tanween (nunation) for indefinite numbers
25
- * - Sophisticated pluralization with singular, dual, and plural forms
2
+ * Arabic language converter - Functional Implementation
26
3
  *
27
- * Algorithm:
28
- * 1. Break number into groups of 3 digits (right to left)
29
- * 2. For each non-zero group, convert to words using gender and plural rules
30
- * 3. Append the appropriate magnitude word (ألف/مليون/مليار) with proper plural form
31
- * 4. Join all groups with spaces
4
+ * Self-contained converter with gender agreement and complex pluralization.
32
5
  *
33
- * Features:
34
- * - Support for gender-aware number forms
35
- * - Proper handling of Arabic dual forms (اثنان/اثنتان)
36
- * - Complex group processing for large numbers
37
- * - Right-to-left text orientation
6
+ * Key features:
7
+ * - Gender agreement (masculine/feminine forms)
8
+ * - Complex pluralization (singular/dual/plural)
38
9
  * - Traditional Arabic number naming conventions
10
+ * - "و" (and) conjunction between segments
39
11
  */
40
- export class Arabic extends AbstractLanguage {
41
- decimalSeparatorWord = 'فاصلة'
42
- zeroWord = 'صفر'
43
- arabicTens = ['عشرون', 'ثلاثون', 'أربعون', 'خمسون', 'ستون', 'سبعون', 'ثمانون', 'تسعون']
44
- arabicHundreds = ['', 'مائة', 'مئتان', 'ثلاثمائة', 'أربعمائة', 'خمسمائة', 'ستمائة', 'سبعمائة', 'ثمانمائة', 'تسعمائة']
45
- arabicAppendedTwos = ['مئتا', 'ألفا', 'مليونا', 'مليارا', 'تريليونا', 'كوادريليونا', 'كوينتليونا', 'سكستيليونا']
46
- arabicTwos = ['مئتان', 'ألفان', 'مليونان', 'ملياران', 'تريليونان', 'كوادريليونان', 'كوينتليونان', 'سكستيليونان']
47
- arabicGroup = ['مائة', 'ألف', 'مليون', 'مليار', 'تريليون', 'كوادريليون', 'كوينتليون', 'سكستيليون']
48
- arabicAppendedGroup = ['', 'ألفاً', 'مليوناً', 'ملياراً', 'تريليوناً', 'كوادريليوناً', 'كوينتليوناً', 'سكستيليوناً']
49
- arabicPluralGroups = ['', 'آلاف', 'ملايين', 'مليارات', 'تريليونات', 'كوادريليونات', 'كوينتليونات', 'سكستيليونات']
50
- ones = {
51
- masculine: [
52
- 'واحد',
53
- 'اثنان',
54
- 'ثلاثة',
55
- 'أربعة',
56
- 'خمسة',
57
- 'ستة',
58
- 'سبعة',
59
- 'ثمانية',
60
- 'تسعة',
61
- 'عشرة',
62
- 'أحد عشر',
63
- 'اثنا عشر',
64
- 'ثلاثة عشر',
65
- 'أربعة عشر',
66
- 'خمسة عشر',
67
- 'ستة عشر',
68
- 'سبعة عشر',
69
- 'ثمانية عشر',
70
- 'تسعة عشر'
71
- ],
72
- feminine: [
73
- 'واحدة',
74
- 'اثنتان',
75
- 'ثلاث',
76
- 'أربع',
77
- 'خمس',
78
- 'ست',
79
- 'سبع',
80
- 'ثمان',
81
- 'تسع',
82
- 'عشر',
83
- 'إحدى عشرة',
84
- 'اثنتا عشرة',
85
- 'ثلاث عشرة',
86
- 'أربع عشرة',
87
- 'خمس عشرة',
88
- 'ست عشرة',
89
- 'سبع عشرة',
90
- 'ثماني عشرة',
91
- 'تسع عشرة'
92
- ]
93
- }
94
12
 
95
- /**
96
- * Initializes the Arabic converter with language-specific options.
97
- *
98
- * @param {ArabicOptions} [options={}] Configuration options.
99
- */
100
- constructor ({ negativeWord = 'ناقص', feminine = false } = {}) {
101
- super()
13
+ import { parseNumericValue } from '../utils/parse-numeric.js'
14
+ import { validateOptions } from '../utils/validate-options.js'
102
15
 
103
- this.feminine = feminine
104
- this.negativeWord = negativeWord
16
+ // ============================================================================
17
+ // Vocabulary
18
+ // ============================================================================
105
19
 
106
- this.selectedOnes = this.ones[this.feminine ? 'feminine' : 'masculine']
107
- }
20
+ const TENS = ['عشرون', 'ثلاثون', 'أربعون', 'خمسون', 'ستون', 'سبعون', 'ثمانون', 'تسعون']
21
+ const HUNDREDS = ['', 'مائة', 'مئتان', 'ثلاثمائة', 'أربعمائة', 'خمسمائة', 'ستمائة', 'سبعمائة', 'ثمانمائة', 'تسعمائة']
22
+
23
+ // Magnitude words with three forms: singular, appended (tanween), plural
24
+ const SCALE_WORDS = ['مائة', 'ألف', 'مليون', 'مليار', 'تريليون', 'كوادريليون', 'كوينتليون', 'سكستيليون']
25
+ const SCALE_APPENDED = ['', 'ألفاً', 'مليوناً', 'ملياراً', 'تريليوناً', 'كوادريليوناً', 'كوينتليوناً', 'سكستيليوناً']
26
+ const SCALE_PLURAL = ['', 'آلاف', 'ملايين', 'مليارات', 'تريليونات', 'كوادريليونات', 'كوينتليونات', 'سكستيليونات']
27
+
28
+ // Dual forms
29
+ const DUAL = ['مئتان', 'ألفان', 'مليونان', 'ملياران', 'تريليونان', 'كوادريليونان', 'كوينتليونان', 'سكستيليونان']
30
+ const DUAL_APPENDED = ['مئتا', 'ألفا', 'مليونا', 'مليارا', 'تريليونا', 'كوادريليونا', 'كوينتليونا', 'سكستيليونا']
31
+
32
+ // Gender-specific forms (1-19)
33
+ const ONES_MASC = ['واحد', 'اثنان', 'ثلاثة', 'أربعة', 'خمسة', 'ستة', 'سبعة', 'ثمانية', 'تسعة', 'عشرة', 'أحد عشر', 'اثنا عشر', 'ثلاثة عشر', 'أربعة عشر', 'خمسة عشر', 'ستة عشر', 'سبعة عشر', 'ثمانية عشر', 'تسعة عشر']
34
+ const ONES_FEM = ['واحدة', 'اثنتان', 'ثلاث', 'أربع', 'خمس', 'ست', 'سبع', 'ثمان', 'تسع', 'عشر', 'إحدى عشرة', 'اثنتا عشرة', 'ثلاث عشرة', 'أربع عشرة', 'خمس عشرة', 'ست عشرة', 'سبع عشرة', 'ثماني عشرة', 'تسع عشرة']
35
+
36
+ const ZERO = 'صفر'
37
+ const NEGATIVE = 'ناقص'
38
+ const DECIMAL_SEP = 'فاصلة'
39
+ const AND = 'و'
108
40
 
109
- /**
110
- * Returns the feminine status of a given digit (1-9).
111
- *
112
- * @param {number} digit - The digit to check (1-9).
113
- * @returns {string} The word form of the digit based on feminine status.
114
- */
115
- digitFeminineStatus (digit) {
116
- return this.selectedOnes[digit - 1]
41
+ // ============================================================================
42
+ // Conversion Functions
43
+ // ============================================================================
44
+
45
+ /**
46
+ * Convert a 3-digit group to words.
47
+ * Returns a clean string with no leading/trailing spaces.
48
+ * Arabic "و" (and) is attached to following word: "مائة وخمسة" not "مائة و خمسة"
49
+ */
50
+ function segmentToWords (groupNumber, groupLevel, fullNumber, ones) {
51
+ const tensValue = groupNumber % 100
52
+ const hundredsDigit = Math.trunc(groupNumber / 100)
53
+ let result = ''
54
+
55
+ // Process hundreds
56
+ if (hundredsDigit > 0) {
57
+ if (tensValue === 0 && hundredsDigit === 2) {
58
+ result = DUAL[0]
59
+ } else {
60
+ const hundredsWord = HUNDREDS[hundredsDigit]
61
+ if (hundredsWord) {
62
+ result = hundredsWord
63
+ if (tensValue !== 0) {
64
+ result += ' ' + AND // "مائة و" - و attaches to next word
65
+ }
66
+ }
67
+ }
117
68
  }
118
69
 
119
- /**
120
- * Processes the Arabic group number and returns the corresponding Arabic representation.
121
- * @param {number} groupNumber - The number to process. (Range: 1-999)
122
- * @param {number} groupLevel - Group level to process. (See example)
123
- * @returns {string} The Arabic representation of the group number.
124
- * @example 12345678 is processed in blocks of 3: '678' (group 0), '345' (group 1), '12' (group 2).
125
- */
126
- processArabicGroup (groupNumber, groupLevel, fullNumber) {
127
- const tens = groupNumber % 100
128
- const hundredsRaw = groupNumber / 100
129
- const hundreds = Math.trunc(hundredsRaw)
130
- let returnValue = ''
131
-
132
- // Process hundreds
133
- if (hundreds > 0) {
134
- if (tens === 0 && hundreds === 2) {
135
- returnValue = this.arabicTwos[0]
136
- } else {
137
- const hundredsWord = this.arabicHundreds[hundreds]
138
- if (hundredsWord) {
139
- returnValue = hundredsWord
140
- if (tens !== 0) {
141
- returnValue += ' و'
142
- }
70
+ // Process tens and ones
71
+ if (tensValue > 0) {
72
+ if (tensValue < 20) {
73
+ if (tensValue === 2 && hundredsDigit === 0 && groupLevel > 0) {
74
+ const numValue = Number(fullNumber)
75
+ const pow = Math.trunc(Math.log10(numValue))
76
+ if (pow % 3 === 0 && fullNumber === BigInt(2 * Math.pow(10, pow))) {
77
+ result += (groupNumber === 2 ? DUAL[groupLevel] : DUAL_APPENDED[groupLevel])
78
+ } else {
79
+ result += DUAL[groupLevel]
143
80
  }
81
+ } else if (tensValue === 1 && groupLevel > 0) {
82
+ result += SCALE_WORDS[groupLevel]
83
+ } else {
84
+ result += ones[tensValue - 1]
144
85
  }
86
+ } else {
87
+ const onesDigit = tensValue % 10
88
+ const tensIndex = Math.trunc(tensValue / 10) - 2
89
+
90
+ if (onesDigit > 0) {
91
+ result += ones[onesDigit - 1] + ' ' + AND // "ستة و" attaches to tens
92
+ }
93
+ result += TENS[tensIndex]
145
94
  }
95
+ }
96
+
97
+ return result
98
+ }
99
+
100
+ function integerToWords (n, options) {
101
+ if (n === 0n) return ZERO
102
+
103
+ const gender = options.gender || 'masculine'
104
+ const ones = gender === 'feminine' ? ONES_FEM : ONES_MASC
105
+
106
+ let temp = n
107
+ let group = 0
108
+ const groups = []
146
109
 
147
- // Process tens and ones
148
- if (tens > 0) {
149
- if (tens < 20) { // 1 -> 19
150
- if (tens === 2 && hundreds === 0 && groupLevel > 0) {
151
- // Cache expensive log10 calculation
152
- const numValue = Number(fullNumber)
153
- const pow = Math.trunc(Math.log10(numValue))
154
- if (pow % 3 === 0 && fullNumber === BigInt(2 * Math.pow(10, pow))) {
155
- returnValue = groupNumber === 2 ? this.arabicTwos[groupLevel] : this.arabicAppendedTwos[groupLevel]
110
+ while (temp > 0n) {
111
+ const numberToProcess = Number(temp % 1000n)
112
+ temp = temp / 1000n
113
+
114
+ if (numberToProcess > 0) {
115
+ const groupDescription = segmentToWords(numberToProcess, group, n, ones)
116
+
117
+ if (groupDescription) {
118
+ let groupText = groupDescription
119
+
120
+ // Add scale word for groups > 0
121
+ if (group > 0 && numberToProcess > 2) {
122
+ const remainder = numberToProcess % 100
123
+ if (remainder === 1) {
124
+ groupText += ' ' + SCALE_WORDS[group]
125
+ } else if (numberToProcess >= 3 && numberToProcess <= 10) {
126
+ groupText += ' ' + SCALE_PLURAL[group]
156
127
  } else {
157
- returnValue = this.arabicTwos[groupLevel]
128
+ groupText += ' ' + (groups.length > 0 ? SCALE_APPENDED[group] : SCALE_WORDS[group])
158
129
  }
159
- } else if (tens === 1 && groupLevel > 0) {
160
- returnValue += this.arabicGroup[groupLevel]
161
- } else {
162
- returnValue += this.selectedOnes[tens - 1]
163
130
  }
164
- } else { // 20 -> 99
165
- const ones = tens % 10
166
- const tensIndex = Math.trunc(tens / 10) - 2
167
131
 
168
- if (ones > 0) {
169
- returnValue += this.selectedOnes[ones - 1]
170
- returnValue += ' و'
171
- }
172
- returnValue += this.arabicTens[tensIndex]
132
+ groups.unshift(groupText)
173
133
  }
174
134
  }
175
135
 
176
- return returnValue
136
+ group++
177
137
  }
178
138
 
179
- /**
180
- * Converts a number to its cardinal representation in Arabic.
181
- * It process by blocks of 3 digits.
182
- * @param {bigint} number - The number to convert.
183
- * @returns {string} The cardinal representation of the number in Arabic.
184
- */
185
- convertWholePart (number) {
186
- if (number === 0n) {
187
- return this.zeroWord
188
- }
189
- let temp = number
190
- let group = 0
191
- let result = ''
192
-
193
- // Process each group of 3 digits (right to left)
194
- while (temp > 0n) {
195
- const numberToProcess = Number(temp % 1000n)
196
- temp = temp / 1000n
197
-
198
- if (numberToProcess > 0) {
199
- const groupDescription = this.processArabicGroup(numberToProcess, group, number)
200
-
201
- if (groupDescription) {
202
- // Add group name for thousands, millions, etc.
203
- if (group > 0) {
204
- if (result) {
205
- result = ' و' + result
206
- }
207
-
208
- if (numberToProcess > 2) {
209
- const remainder = numberToProcess % 100
210
- if (remainder === 1) {
211
- result = this.arabicGroup[group] + ' ' + result
212
- } else if (numberToProcess >= 3 && numberToProcess <= 10) {
213
- result = this.arabicPluralGroups[group] + ' ' + result
214
- } else {
215
- result = (result ? this.arabicAppendedGroup[group] : this.arabicGroup[group]) + ' ' + result
216
- }
217
- }
218
- }
139
+ // Join groups with و (and) - space before و, attaches to next word
140
+ // Use simple join since segmentToWords returns clean strings
141
+ if (groups.length === 1) return groups[0]
219
142
 
220
- // Add group description (prepend for RTL)
221
- result = groupDescription + ' ' + result
222
- }
223
- }
143
+ // Build result: "group1 وgroup2 وgroup3"
144
+ let result = groups[0]
145
+ for (let i = 1; i < groups.length; i++) {
146
+ result += ' ' + AND + groups[i]
147
+ }
148
+ return result
149
+ }
224
150
 
225
- group++
226
- }
151
+ function decimalPartToWords (decimalPart, options) {
152
+ const parts = []
153
+ let i = 0
154
+
155
+ while (i < decimalPart.length && decimalPart[i] === '0') {
156
+ parts.push(ZERO)
157
+ i++
158
+ }
227
159
 
228
- return result.replace(/\s+/g, ' ').trim()
160
+ const remainder = decimalPart.slice(i)
161
+ if (remainder) {
162
+ parts.push(integerToWords(BigInt(remainder), options))
229
163
  }
164
+
165
+ return parts.join(' ')
230
166
  }
231
167
 
232
168
  /**
233
- * Converts a number to Arabic cardinal (written) form.
169
+ * Converts a numeric value to Arabic words.
234
170
  *
235
- * @param {number|string|bigint} value The number to convert.
236
- * @param {ArabicOptions} [options={}] Configuration options.
237
- * @returns {string} The number expressed in Arabic words.
238
- * @throws {TypeError} If value is NaN or invalid type.
239
- * @throws {Error} If value is an invalid number string.
171
+ * @param {number | string | bigint} value - The numeric value to convert
172
+ * @param {Object} [options] - Optional configuration
173
+ * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
174
+ * @param {string} [options.negativeWord] - Custom word for negative numbers
175
+ * @returns {string} The number in Arabic words
176
+ *
177
+ * @example
178
+ * toWords(1) // 'واحد'
179
+ * toWords(1, {gender: 'feminine'}) // 'واحدة'
240
180
  */
241
- export default function convertToWords (value, options = {}) {
242
- return new Arabic(options).convertToWords(value)
181
+ function toWords (value, options) {
182
+ options = validateOptions(options)
183
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
184
+
185
+ const parts = []
186
+
187
+ if (isNegative) {
188
+ parts.push(options.negativeWord || NEGATIVE)
189
+ }
190
+
191
+ parts.push(integerToWords(integerPart, options))
192
+
193
+ if (decimalPart) {
194
+ parts.push(DECIMAL_SEP)
195
+ parts.push(decimalPartToWords(decimalPart, options))
196
+ }
197
+
198
+ return parts.join(' ')
243
199
  }
200
+
201
+ // ============================================================================
202
+ // Exports
203
+ // ============================================================================
204
+
205
+ export { toWords }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts a numeric value to Azerbaijani words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Azerbaijani words
6
+ */
7
+ export function toWords(value: number | string | bigint): string;
@@ -1,58 +1,176 @@
1
- import TurkicLanguage from '../classes/turkic-language.js'
2
-
3
1
  /**
4
- * Azerbaijani language converter.
2
+ * Azerbaijani language converter - Functional Implementation
3
+ *
4
+ * Self-contained converter with precomputed lookup tables.
5
5
  *
6
- * Inherits from TurkicLanguage shared patterns:
7
- * - Space-separated number combinations
8
- * - Omits '1' before hundreds and thousands
9
- * - Supports flexible word spacing configuration
6
+ * Key features:
7
+ * - Turkic language patterns
8
+ * - Implicit "bir" (one) omission before hundreds and thousands
9
+ * - Short scale naming
10
10
  */
11
- export class Azerbaijani extends TurkicLanguage {
12
- negativeWord = 'mənfi'
13
- decimalSeparatorWord = 'nöqtə'
14
- zeroWord = 'sıfır'
15
- scaleWordPairs = [[1_000_000_000_000_000_000n, 'kentilyon'],
16
- [1_000_000_000_000_000n, 'katrilyon'],
17
- [1_000_000_000_000n, 'trilyon'],
18
- [1_000_000_000n, 'milyar'],
19
- [1_000_000n, 'milyon'],
20
- [1000n, 'min'],
21
- [100n, 'yüz'],
22
- [90n, 'doxsan'],
23
- [80n, 'səksən'],
24
- [70n, 'yetmiş'],
25
- [60n, 'altmış'],
26
- [50n, 'əlli'],
27
- [40n, 'qırx'],
28
- [30n, 'otuz'],
29
- [20n, 'iyirmi'],
30
- [10n, 'on'],
31
- [9n, 'doqquz'],
32
- [8n, 'səkkiz'],
33
- [7n, 'yeddi'],
34
- [6n, 'altı'],
35
- [5n, 'beş'],
36
- [4n, 'dörd'],
37
- [3n, 'üç'],
38
- [2n, 'iki'],
39
- [1n, 'bir'],
40
- [0n, 'sıfır']]
11
+
12
+ import { parseNumericValue } from '../utils/parse-numeric.js'
13
+
14
+ // ============================================================================
15
+ // Vocabulary
16
+ // ============================================================================
17
+
18
+ const ONES = ['', 'bir', 'iki', 'üç', 'dörd', 'beş', 'altı', 'yeddi', 'səkkiz', 'doqquz']
19
+ const TEENS = ['on', 'on bir', 'on iki', 'on üç', 'on dörd', 'on beş', 'on altı', 'on yeddi', 'on səkkiz', 'on doqquz']
20
+ const TENS = ['', '', 'iyirmi', 'otuz', 'qırx', 'əlli', 'altmış', 'yetmiş', 'səksən', 'doxsan']
21
+
22
+ const HUNDRED = 'yüz'
23
+ const THOUSAND = 'min'
24
+
25
+ const ZERO = 'sıfır'
26
+ const NEGATIVE = 'mənfi'
27
+ const DECIMAL_SEP = 'nöqtə'
28
+
29
+ // Short scale
30
+ const SCALE_WORDS = ['', THOUSAND, 'milyon', 'milyar', 'trilyon', 'katrilyon', 'kentilyon']
31
+
32
+ // ============================================================================
33
+ // Precomputed Lookup Table
34
+ // ============================================================================
35
+
36
+ function buildSegment (n) {
37
+ if (n === 0) return ''
38
+
39
+ const ones = n % 10
40
+ const tensDigit = Math.floor(n / 10) % 10
41
+ const hundredsDigit = Math.floor(n / 100)
42
+
43
+ const parts = []
44
+
45
+ if (hundredsDigit > 0) {
46
+ if (hundredsDigit === 1) {
47
+ parts.push(HUNDRED)
48
+ } else {
49
+ parts.push(ONES[hundredsDigit] + ' ' + HUNDRED)
50
+ }
51
+ }
52
+
53
+ if (tensDigit === 1) {
54
+ parts.push(TEENS[ones])
55
+ } else {
56
+ if (tensDigit > 1) {
57
+ parts.push(TENS[tensDigit])
58
+ }
59
+ if (ones > 0) {
60
+ parts.push(ONES[ones])
61
+ }
62
+ }
63
+
64
+ return parts.join(' ')
65
+ }
66
+
67
+ const SEGMENTS = new Array(1000)
68
+ for (let i = 0; i < 1000; i++) {
69
+ SEGMENTS[i] = buildSegment(i)
70
+ }
71
+
72
+ // ============================================================================
73
+ // Conversion Functions
74
+ // ============================================================================
75
+
76
+ function integerToWords (n) {
77
+ if (n === 0n) return ZERO
78
+
79
+ if (n < 1000n) {
80
+ return SEGMENTS[Number(n)]
81
+ }
82
+
83
+ return buildLargeNumberWords(n)
84
+ }
85
+
86
+ function buildLargeNumberWords (n) {
87
+ const numStr = n.toString()
88
+ const len = numStr.length
89
+
90
+ const segments = []
91
+ const segmentSize = 3
92
+
93
+ const remainderLen = len % segmentSize
94
+ let pos = 0
95
+ if (remainderLen > 0) {
96
+ segments.push(Number(numStr.slice(0, remainderLen)))
97
+ pos = remainderLen
98
+ }
99
+ while (pos < len) {
100
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
101
+ pos += segmentSize
102
+ }
103
+
104
+ const parts = []
105
+ let scaleIndex = segments.length - 1
106
+
107
+ for (let i = 0; i < segments.length; i++) {
108
+ const segment = segments[i]
109
+
110
+ if (segment !== 0) {
111
+ const scaleWord = SCALE_WORDS[scaleIndex] || ''
112
+
113
+ if (scaleIndex === 0) {
114
+ parts.push(SEGMENTS[segment])
115
+ } else if (scaleIndex === 1 && segment === 1) {
116
+ // Omit "bir" before thousand
117
+ parts.push(scaleWord)
118
+ } else {
119
+ parts.push(SEGMENTS[segment] + ' ' + scaleWord)
120
+ }
121
+ }
122
+
123
+ scaleIndex--
124
+ }
125
+
126
+ return parts.join(' ')
127
+ }
128
+
129
+ function decimalPartToWords (decimalPart) {
130
+ let result = ''
131
+ let i = 0
132
+
133
+ while (i < decimalPart.length && decimalPart[i] === '0') {
134
+ if (result) result += ' '
135
+ result += ZERO
136
+ i++
137
+ }
138
+
139
+ const remainder = decimalPart.slice(i)
140
+ if (remainder) {
141
+ if (result) result += ' '
142
+ result += integerToWords(BigInt(remainder))
143
+ }
144
+
145
+ return result
41
146
  }
42
147
 
43
148
  /**
44
- * Converts a number to Azerbaijani cardinal (written) form.
149
+ * Converts a numeric value to Azerbaijani words.
45
150
  *
46
- * @param {number|string|bigint} value The number to convert.
47
- * @param {Object} [options] Conversion options (see AZ class).
48
- * @returns {string} The number expressed in Azerbaijani words.
49
- * @throws {TypeError} If value is NaN or invalid type.
50
- * @throws {Error} If value is an invalid number string.
51
- *
52
- * @example
53
- * convertToWords(42, { lang: 'az' }); // 'qırx iki'
54
- * convertToWords(1000, { lang: 'az' }); // 'min'
151
+ * @param {number | string | bigint} value - The numeric value to convert
152
+ * @returns {string} The number in Azerbaijani words
55
153
  */
56
- export default function convertToWords (value, options = {}) {
57
- return new Azerbaijani(options).convertToWords(value)
154
+ function toWords (value) {
155
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
156
+
157
+ let result = ''
158
+
159
+ if (isNegative) {
160
+ result = NEGATIVE + ' '
161
+ }
162
+
163
+ result += integerToWords(integerPart)
164
+
165
+ if (decimalPart) {
166
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
167
+ }
168
+
169
+ return result
58
170
  }
171
+
172
+ // ============================================================================
173
+ // Exports
174
+ // ============================================================================
175
+
176
+ export { toWords }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts a numeric value to Bengali words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Bengali words
6
+ */
7
+ export function toWords(value: number | string | bigint): string;