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,127 +1,134 @@
1
- import AbstractLanguage from '../classes/abstract-language.js'
2
-
3
1
  /**
4
- * Persian (Farsi) language converter.
5
- *
6
- * Converts numbers to Persian words using Persian-Arabic numerals:
7
- * - Right-to-left script orientation
8
- * - Base-10 decimal system with Persian number words
9
- * - Conjunction "و" (va/and) for compound numbers
10
- * - Traditional Persian number naming conventions
2
+ * Persian language converter - Functional Implementation
11
3
  *
12
- * Key Features:
13
- * - Named number lookup table (namedNumbers) for direct mapping 0-999
14
- * - Group-based algorithm for numbers >= 1000:
15
- * 1. Split into groups of 3 digits
16
- * 2. Convert each group to words using named table or recursion
17
- * 3. Append magnitude word (هزار/میلیون/میلیارد)
18
- * 4. Join with "و" (and) conjunction
19
- * - Special compound forms (دویست for 200, سیصد for 300)
20
- * - Proper Persian grammatical structure
21
- * - Support for large numbers (thousands, millions, billions, trillions)
4
+ * Self-contained converter using recursive decomposition.
22
5
  *
23
- * Features:
24
- * - Native Persian digits and words
25
- * - Special compound forms (e.g., دویست for 200, سیصد for 300)
26
- * - Support for large numbers (thousands, millions)
27
- * - Proper Persian grammatical structure
6
+ * Key features:
7
+ * - "و" (and) conjunction for compound numbers
8
+ * - Omit "یک" (one) before thousand
9
+ * - Pre-composed hundreds (دویست, سيصد, etc.)
28
10
  */
29
- export class Farsi extends AbstractLanguage {
30
- negativeWord = 'منفى'
31
- decimalSeparatorWord = 'ممیّز'
32
- zeroWord = 'صفر'
33
- namedNumbers = {
34
- 0: 'صفر',
35
- 1: 'یک',
36
- 2: 'دو',
37
- 3: 'سه',
38
- 4: 'چهار',
39
- 5: 'پنج',
40
- 6: 'شش',
41
- 7: 'هفت',
42
- 8: 'هشت',
43
- 9: 'نه',
44
- 10: 'ده',
45
- 11: 'یازده',
46
- 12: 'دوازده',
47
- 13: 'سیزده',
48
- 14: 'چهارده',
49
- 15: 'پانزده',
50
- 16: 'شانزده',
51
- 17: 'هفده',
52
- 18: 'هجده',
53
- 19: 'نوزده',
54
- 20: 'بیست',
55
- 30: 'سی',
56
- 40: 'چهل',
57
- 50: 'پنجاه',
58
- 60: 'شصت',
59
- 70: 'هفتاد',
60
- 80: 'هشتاد',
61
- 90: 'نود',
62
- 100: 'صد',
63
- 200: 'دویست',
64
- 300: 'سيصد',
65
- 400: 'چهار صد',
66
- 500: 'پانصد',
67
- 600: 'ششصد',
68
- 700: 'هفتصد',
69
- 800: 'هشتصد',
70
- 900: 'نهصد',
71
- 1000: 'هزار',
72
- 1_000_000: 'میلیون'
11
+
12
+ import { parseNumericValue } from '../utils/parse-numeric.js'
13
+
14
+ // ============================================================================
15
+ // Vocabulary
16
+ // ============================================================================
17
+
18
+ const ONES = { 1: 'یک', 2: 'دو', 3: 'سه', 4: 'چهار', 5: 'پنج', 6: 'شش', 7: 'هفت', 8: 'هشت', 9: 'نه' }
19
+ const TEENS = { 10: 'ده', 11: 'یازده', 12: 'دوازده', 13: 'سیزده', 14: 'چهارده', 15: 'پانزده', 16: 'شانزده', 17: 'هفده', 18: 'هجده', 19: 'نوزده' }
20
+ const TENS = { 20: 'بیست', 30: 'سی', 40: 'چهل', 50: 'پنجاه', 60: 'شصت', 70: 'هفتاد', 80: 'هشتاد', 90: 'نود' }
21
+ const HUNDREDS = { 100: 'صد', 200: 'دویست', 300: 'سيصد', 400: 'چهار صد', 500: 'پانصد', 600: 'ششصد', 700: 'هفتصد', 800: 'هشتصد', 900: 'نهصد' }
22
+
23
+ const THOUSAND = 'هزار'
24
+ const MILLION = 'میلیون'
25
+
26
+ const ZERO = 'صفر'
27
+ const NEGATIVE = 'منفى'
28
+ const DECIMAL_SEP = 'ممیّز'
29
+
30
+ // ============================================================================
31
+ // Conversion Functions
32
+ // ============================================================================
33
+
34
+ function integerToWords (n) {
35
+ if (n === 0n) return ZERO
36
+
37
+ // 1-9
38
+ if (n <= 9n) {
39
+ return ONES[Number(n)]
73
40
  }
74
41
 
75
- convertWholePart (number) {
76
- if (this.namedNumbers[number] && number !== 1_000_000n) {
77
- return this.namedNumbers[number]
78
- }
42
+ // 10-19
43
+ if (n <= 19n) {
44
+ return TEENS[Number(n)]
45
+ }
79
46
 
80
- if (number > 20n && number < 100n) {
81
- const xone = number % 10n
82
- const xten = number - xone
83
- return `${this.namedNumbers[xten]} و ${this.namedNumbers[xone]}`
47
+ // 20-99
48
+ if (n < 100n) {
49
+ const ones = n % 10n
50
+ const tens = n - ones
51
+ if (ones === 0n) {
52
+ return TENS[Number(tens)]
84
53
  }
54
+ return `${TENS[Number(tens)]} و ${ONES[Number(ones)]}`
55
+ }
85
56
 
86
- if (number > 100n && number < 1000n) {
87
- const xhundred = 100n * (number / 100n)
88
- const tail = this.convertWholePart(number - xhundred)
89
- return `${this.namedNumbers[xhundred]} و ${tail}`
57
+ // 100-999
58
+ if (n < 1000n) {
59
+ const hundreds = 100n * (n / 100n)
60
+ const remainder = n - hundreds
61
+ if (remainder === 0n) {
62
+ return HUNDREDS[Number(hundreds)]
90
63
  }
64
+ return `${HUNDREDS[Number(hundreds)]} و ${integerToWords(remainder)}`
65
+ }
91
66
 
92
- if (number > 1000n && number < 1_000_000n) {
93
- const thousandMultiplier = number / 1000n
94
- const namedThousandMultiplier =
95
- (thousandMultiplier === 1n
96
- ? ''
97
- : this.convertWholePart(thousandMultiplier)) +
98
- ' ' +
99
- this.namedNumbers[1000]
100
- const tailNumber = number - thousandMultiplier * 1000n
101
- const tail = tailNumber === 0n ? '' : ' ' + this.convertWholePart(tailNumber)
102
- return `${namedThousandMultiplier}${tail}`
103
- }
67
+ // 1000-999999
68
+ if (n < 1_000_000n) {
69
+ const thousandMultiplier = n / 1000n
70
+ // Persian omits "one" before thousand: 1000 is just "هزار", not "یک هزار"
71
+ const thousandPrefix = thousandMultiplier === 1n
72
+ ? ''
73
+ : integerToWords(thousandMultiplier) + ' '
74
+ const remainder = n % 1000n
75
+ const suffix = remainder === 0n ? '' : ' ' + integerToWords(remainder)
76
+ return `${thousandPrefix}${THOUSAND}${suffix}`
77
+ }
104
78
 
105
- if (number >= 1_000_000n) {
106
- const millionMultiplier = number / 1_000_000n
107
- const namedMillion =
108
- this.convertWholePart(millionMultiplier) + ' ' + this.namedNumbers[1_000_000]
109
- const tailNumber = number - millionMultiplier * 1_000_000n
110
- const tail = tailNumber === 0n ? '' : ' و ' + this.convertWholePart(tailNumber)
111
- return `${namedMillion}${tail}`
112
- }
79
+ // 1000000+
80
+ const millionMultiplier = n / 1_000_000n
81
+ const millionPrefix = integerToWords(millionMultiplier) + ' ' + MILLION
82
+ const remainder = n % 1_000_000n
83
+ const suffix = remainder === 0n ? '' : ' و ' + integerToWords(remainder)
84
+ return `${millionPrefix}${suffix}`
85
+ }
86
+
87
+ function decimalPartToWords (decimalPart) {
88
+ let result = ''
89
+ let i = 0
90
+
91
+ while (i < decimalPart.length && decimalPart[i] === '0') {
92
+ if (result) result += ' '
93
+ result += ZERO
94
+ i++
113
95
  }
96
+
97
+ const remainder = decimalPart.slice(i)
98
+ if (remainder) {
99
+ if (result) result += ' '
100
+ result += integerToWords(BigInt(remainder))
101
+ }
102
+
103
+ return result
114
104
  }
115
105
 
116
106
  /**
117
- * Converts a number to Persian cardinal (written) form.
107
+ * Converts a numeric value to Persian words.
118
108
  *
119
- * @param {number|string|bigint} value The number to convert.
120
- * @param {Object} [options={}] Configuration options.
121
- * @returns {string} The number expressed in Persian words.
122
- * @throws {TypeError} If value is NaN or invalid type.
123
- * @throws {Error} If value is an invalid number string.
109
+ * @param {number | string | bigint} value - The numeric value to convert
110
+ * @returns {string} The number in Persian words
124
111
  */
125
- export default function convertToWords (value, options = {}) {
126
- return new Farsi(options).convertToWords(value)
112
+ function toWords (value) {
113
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
114
+
115
+ let result = ''
116
+
117
+ if (isNegative) {
118
+ result = NEGATIVE + ' '
119
+ }
120
+
121
+ result += integerToWords(integerPart)
122
+
123
+ if (decimalPart) {
124
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
125
+ }
126
+
127
+ return result
127
128
  }
129
+
130
+ // ============================================================================
131
+ // Exports
132
+ // ============================================================================
133
+
134
+ export { toWords }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Converts a numeric value to Finnish words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Finnish words
6
+ * @throws {TypeError} If value is not a valid numeric type
7
+ * @throws {Error} If value is not a valid number format
8
+ *
9
+ * @example
10
+ * toWords(21) // 'kaksikymmentäyksi'
11
+ * toWords(1000) // 'tuhat'
12
+ * toWords('3.14') // 'kolme pilkku yksi neljä'
13
+ */
14
+ export function toWords(value: number | string | bigint): string;
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Finnish language converter - Functional Implementation
3
+ *
4
+ * A performance-optimized number-to-words converter using precomputed lookup tables.
5
+ *
6
+ * Key features:
7
+ * - Compound tens+ones without spaces: "kaksikymmentäyksi" (21)
8
+ * - Teens with "-toista" suffix
9
+ * - Omit "yksi" before sata/tuhat but keep before miljoona+
10
+ * - Long scale: miljoona, miljardi, biljoona
11
+ * - Per-digit decimal reading
12
+ */
13
+
14
+ import { parseNumericValue } from '../utils/parse-numeric.js'
15
+
16
+ // ============================================================================
17
+ // Vocabulary (module-level constants)
18
+ // ============================================================================
19
+
20
+ const ONES = ['', 'yksi', 'kaksi', 'kolme', 'neljä', 'viisi', 'kuusi', 'seitsemän', 'kahdeksan', 'yhdeksän']
21
+
22
+ const TEENS = ['kymmenen', 'yksitoista', 'kaksitoista', 'kolmetoista', 'neljätoista', 'viisitoista', 'kuusitoista', 'seitsemäntoista', 'kahdeksantoista', 'yhdeksäntoista']
23
+
24
+ // Tens use "kymmentä" suffix
25
+ const TENS = ['', '', 'kaksikymmentä', 'kolmekymmentä', 'neljäkymmentä', 'viisikymmentä', 'kuusikymmentä', 'seitsemänkymmentä', 'kahdeksankymmentä', 'yhdeksänkymmentä']
26
+
27
+ const HUNDRED = 'sata'
28
+ const THOUSAND = 'tuhat'
29
+
30
+ const ZERO = 'nolla'
31
+ const NEGATIVE = 'miinus'
32
+ const DECIMAL_SEP = 'pilkku'
33
+
34
+ // Long scale
35
+ const SCALES = ['miljoona', 'miljardi', 'biljoona', 'triljoona']
36
+
37
+ // ============================================================================
38
+ // Precomputed Lookup Tables (built once at module load)
39
+ // ============================================================================
40
+
41
+ /**
42
+ * Builds segment word for 0-999.
43
+ * Omits "yksi" before "sata" (hundred).
44
+ */
45
+ function buildSegment (n) {
46
+ if (n === 0) return ''
47
+
48
+ const ones = n % 10
49
+ const tens = Math.floor(n / 10) % 10
50
+ const hundreds = Math.floor(n / 100)
51
+
52
+ const parts = []
53
+
54
+ // Hundreds - omit "yksi" before sata
55
+ if (hundreds > 0) {
56
+ if (hundreds === 1) {
57
+ parts.push(HUNDRED)
58
+ } else {
59
+ parts.push(ONES[hundreds] + ' ' + HUNDRED)
60
+ }
61
+ }
62
+
63
+ // Tens and ones
64
+ const tensOnes = n % 100
65
+
66
+ if (tensOnes === 0) {
67
+ // Just hundreds
68
+ } else if (tensOnes < 10) {
69
+ parts.push(ONES[ones])
70
+ } else if (tensOnes < 20) {
71
+ parts.push(TEENS[ones])
72
+ } else if (ones === 0) {
73
+ parts.push(TENS[tens])
74
+ } else {
75
+ // Compound: "kaksikymmentäyksi" (no space)
76
+ parts.push(TENS[tens] + ONES[ones])
77
+ }
78
+
79
+ return parts.join(' ')
80
+ }
81
+
82
+ // Precompute all 1000 segment words (0-999)
83
+ const SEGMENTS = new Array(1000)
84
+
85
+ for (let i = 0; i < 1000; i++) {
86
+ SEGMENTS[i] = buildSegment(i)
87
+ }
88
+
89
+ // ============================================================================
90
+ // Conversion Functions
91
+ // ============================================================================
92
+
93
+ /**
94
+ * Converts a non-negative integer to Finnish words.
95
+ *
96
+ * @param {bigint} n - Non-negative integer to convert
97
+ * @returns {string} Finnish words
98
+ */
99
+ function integerToWords (n) {
100
+ if (n === 0n) return ZERO
101
+
102
+ // Fast path: numbers < 1000 (direct lookup)
103
+ if (n < 1000n) {
104
+ return SEGMENTS[Number(n)]
105
+ }
106
+
107
+ // Fast path: numbers < 1,000,000 (thousands)
108
+ if (n < 1_000_000n) {
109
+ const thousands = Number(n / 1000n)
110
+ const remainder = Number(n % 1000n)
111
+
112
+ // Omit "yksi" before tuhat
113
+ let result
114
+ if (thousands === 1) {
115
+ result = THOUSAND
116
+ } else {
117
+ result = SEGMENTS[thousands] + ' ' + THOUSAND
118
+ }
119
+
120
+ if (remainder > 0) {
121
+ result += ' ' + SEGMENTS[remainder]
122
+ }
123
+
124
+ return result
125
+ }
126
+
127
+ // For numbers >= 1,000,000, use scale decomposition
128
+ return buildLargeNumberWords(n)
129
+ }
130
+
131
+ /**
132
+ * Builds words for numbers >= 1,000,000.
133
+ *
134
+ * @param {bigint} n - Number >= 1,000,000
135
+ * @returns {string} Finnish words
136
+ */
137
+ function buildLargeNumberWords (n) {
138
+ const numStr = n.toString()
139
+ const len = numStr.length
140
+
141
+ // Build segments of 3 digits from right to left
142
+ const segments = []
143
+ const segmentSize = 3
144
+
145
+ const remainderLen = len % segmentSize
146
+ let pos = 0
147
+ if (remainderLen > 0) {
148
+ segments.push(Number(numStr.slice(0, remainderLen)))
149
+ pos = remainderLen
150
+ }
151
+ while (pos < len) {
152
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
153
+ pos += segmentSize
154
+ }
155
+
156
+ // Convert segments to words
157
+ const parts = []
158
+ let scaleIndex = segments.length - 1
159
+
160
+ for (let i = 0; i < segments.length; i++) {
161
+ const segment = segments[i]
162
+
163
+ if (segment !== 0) {
164
+ const segmentWord = SEGMENTS[segment]
165
+
166
+ if (scaleIndex === 0) {
167
+ // Units segment
168
+ parts.push(segmentWord)
169
+ } else if (scaleIndex === 1) {
170
+ // Thousands - omit "yksi" before tuhat
171
+ if (segment === 1) {
172
+ parts.push(THOUSAND)
173
+ } else {
174
+ parts.push(segmentWord + ' ' + THOUSAND)
175
+ }
176
+ } else {
177
+ // Millions+ - keep "yksi" before scale words
178
+ const scaleWord = SCALES[scaleIndex - 2]
179
+ parts.push(segmentWord + ' ' + scaleWord)
180
+ }
181
+ }
182
+
183
+ scaleIndex--
184
+ }
185
+
186
+ return parts.join(' ')
187
+ }
188
+
189
+ /**
190
+ * Converts decimal digits to Finnish words (per-digit).
191
+ *
192
+ * @param {string} decimalPart - Decimal digits (without the point)
193
+ * @returns {string} Finnish words for decimal part
194
+ */
195
+ function decimalPartToWords (decimalPart) {
196
+ const parts = []
197
+
198
+ for (const digit of decimalPart) {
199
+ const d = parseInt(digit, 10)
200
+ if (d === 0) {
201
+ parts.push(ZERO)
202
+ } else {
203
+ parts.push(ONES[d])
204
+ }
205
+ }
206
+
207
+ return parts.join(' ')
208
+ }
209
+
210
+ /**
211
+ * Converts a numeric value to Finnish words.
212
+ *
213
+ * @param {number | string | bigint} value - The numeric value to convert
214
+ * @returns {string} The number in Finnish words
215
+ * @throws {TypeError} If value is not a valid numeric type
216
+ * @throws {Error} If value is not a valid number format
217
+ *
218
+ * @example
219
+ * toWords(21) // 'kaksikymmentäyksi'
220
+ * toWords(1000) // 'tuhat'
221
+ * toWords('3.14') // 'kolme pilkku yksi neljä'
222
+ */
223
+ function toWords (value) {
224
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
225
+
226
+ let result = ''
227
+
228
+ if (isNegative) {
229
+ result = NEGATIVE + ' '
230
+ }
231
+
232
+ result += integerToWords(integerPart)
233
+
234
+ if (decimalPart) {
235
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
236
+ }
237
+
238
+ return result
239
+ }
240
+
241
+ // ============================================================================
242
+ // Public API
243
+ // ============================================================================
244
+
245
+ export { toWords }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts a numeric value to Filipino words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Filipino words
6
+ */
7
+ export function toWords(value: number | string | bigint): string;