n2words 2.0.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 (327) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +86 -188
  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 -0
  8. package/dist/languages/ar.js.map +1 -0
  9. package/dist/languages/az.js +3 -0
  10. package/dist/languages/az.js.map +1 -0
  11. package/dist/languages/bn.js +3 -0
  12. package/dist/languages/bn.js.map +1 -0
  13. package/dist/languages/cs.js +3 -0
  14. package/dist/languages/cs.js.map +1 -0
  15. package/dist/languages/da.js +3 -0
  16. package/dist/languages/da.js.map +1 -0
  17. package/dist/languages/de.js +3 -0
  18. package/dist/languages/de.js.map +1 -0
  19. package/dist/languages/el.js +3 -0
  20. package/dist/languages/el.js.map +1 -0
  21. package/dist/languages/en.js +3 -0
  22. package/dist/languages/en.js.map +1 -0
  23. package/dist/languages/es.js +3 -0
  24. package/dist/languages/es.js.map +1 -0
  25. package/dist/languages/fa.js +3 -0
  26. package/dist/languages/fa.js.map +1 -0
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -0
  30. package/dist/languages/fil.js.map +1 -0
  31. package/dist/languages/fr-BE.js +3 -0
  32. package/dist/languages/fr-BE.js.map +1 -0
  33. package/dist/languages/fr.js +3 -0
  34. package/dist/languages/fr.js.map +1 -0
  35. package/dist/languages/gu.js +3 -0
  36. package/dist/languages/gu.js.map +1 -0
  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 -0
  42. package/dist/languages/he.js.map +1 -0
  43. package/dist/languages/hi.js +3 -0
  44. package/dist/languages/hi.js.map +1 -0
  45. package/dist/languages/hr.js +3 -0
  46. package/dist/languages/hr.js.map +1 -0
  47. package/dist/languages/hu.js +3 -0
  48. package/dist/languages/hu.js.map +1 -0
  49. package/dist/languages/id.js +3 -0
  50. package/dist/languages/id.js.map +1 -0
  51. package/dist/languages/it.js +3 -0
  52. package/dist/languages/it.js.map +1 -0
  53. package/dist/languages/ja.js +3 -0
  54. package/dist/languages/ja.js.map +1 -0
  55. package/dist/languages/kn.js +3 -0
  56. package/dist/languages/kn.js.map +1 -0
  57. package/dist/languages/ko.js +3 -0
  58. package/dist/languages/ko.js.map +1 -0
  59. package/dist/languages/lt.js +3 -0
  60. package/dist/languages/lt.js.map +1 -0
  61. package/dist/languages/lv.js +3 -0
  62. package/dist/languages/lv.js.map +1 -0
  63. package/dist/languages/mr.js +3 -0
  64. package/dist/languages/mr.js.map +1 -0
  65. package/dist/languages/ms.js +3 -0
  66. package/dist/languages/ms.js.map +1 -0
  67. package/dist/languages/nb.js +3 -0
  68. package/dist/languages/nb.js.map +1 -0
  69. package/dist/languages/nl.js +3 -0
  70. package/dist/languages/nl.js.map +1 -0
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -0
  74. package/dist/languages/pl.js.map +1 -0
  75. package/dist/languages/pt.js +3 -0
  76. package/dist/languages/pt.js.map +1 -0
  77. package/dist/languages/ro.js +3 -0
  78. package/dist/languages/ro.js.map +1 -0
  79. package/dist/languages/ru.js +3 -0
  80. package/dist/languages/ru.js.map +1 -0
  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 -0
  84. package/dist/languages/sr-Latn.js.map +1 -0
  85. package/dist/languages/sv.js +3 -0
  86. package/dist/languages/sv.js.map +1 -0
  87. package/dist/languages/sw.js +3 -0
  88. package/dist/languages/sw.js.map +1 -0
  89. package/dist/languages/ta.js +3 -0
  90. package/dist/languages/ta.js.map +1 -0
  91. package/dist/languages/te.js +3 -0
  92. package/dist/languages/te.js.map +1 -0
  93. package/dist/languages/th.js +3 -0
  94. package/dist/languages/th.js.map +1 -0
  95. package/dist/languages/tr.js +3 -0
  96. package/dist/languages/tr.js.map +1 -0
  97. package/dist/languages/uk.js +3 -0
  98. package/dist/languages/uk.js.map +1 -0
  99. package/dist/languages/ur.js +3 -0
  100. package/dist/languages/ur.js.map +1 -0
  101. package/dist/languages/vi.js +3 -0
  102. package/dist/languages/vi.js.map +1 -0
  103. package/dist/languages/zh-Hans.js +3 -0
  104. package/dist/languages/zh-Hans.js.map +1 -0
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +2 -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 +14 -27
  114. package/lib/languages/ar.js +175 -129
  115. package/lib/languages/az.d.ts +4 -9
  116. package/lib/languages/az.js +171 -37
  117. package/lib/languages/bn.d.ts +4 -8
  118. package/lib/languages/bn.js +138 -124
  119. package/lib/languages/cs.d.ts +15 -85
  120. package/lib/languages/cs.js +310 -114
  121. package/lib/languages/da.d.ts +11 -12
  122. package/lib/languages/da.js +276 -101
  123. package/lib/languages/de.d.ts +14 -11
  124. package/lib/languages/de.js +317 -86
  125. package/lib/languages/el.d.ts +11 -11
  126. package/lib/languages/el.js +231 -78
  127. package/lib/languages/en.d.ts +14 -13
  128. package/lib/languages/en.js +242 -72
  129. package/lib/languages/es.d.ts +18 -12
  130. package/lib/languages/es.js +317 -103
  131. package/lib/languages/fa.d.ts +4 -44
  132. package/lib/languages/fa.js +112 -122
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +4 -13
  136. package/lib/languages/fil.js +207 -106
  137. package/lib/languages/fr-BE.d.ts +8 -8
  138. package/lib/languages/fr-BE.js +294 -19
  139. package/lib/languages/fr.d.ts +18 -12
  140. package/lib/languages/fr.js +352 -89
  141. package/lib/languages/gu.d.ts +4 -8
  142. package/lib/languages/gu.js +130 -125
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +10 -110
  146. package/lib/languages/hbo.js +263 -214
  147. package/lib/languages/he.d.ts +10 -77
  148. package/lib/languages/he.js +242 -172
  149. package/lib/languages/hi.d.ts +4 -8
  150. package/lib/languages/hi.js +138 -124
  151. package/lib/languages/hr.d.ts +8 -77
  152. package/lib/languages/hr.js +194 -89
  153. package/lib/languages/hu.d.ts +4 -19
  154. package/lib/languages/hu.js +198 -119
  155. package/lib/languages/id.d.ts +4 -34
  156. package/lib/languages/id.js +171 -129
  157. package/lib/languages/it.d.ts +16 -34
  158. package/lib/languages/it.js +339 -94
  159. package/lib/languages/ja.d.ts +14 -14
  160. package/lib/languages/ja.js +233 -111
  161. package/lib/languages/kn.d.ts +4 -8
  162. package/lib/languages/kn.js +130 -35
  163. package/lib/languages/ko.d.ts +11 -11
  164. package/lib/languages/ko.js +257 -49
  165. package/lib/languages/lt.d.ts +15 -67
  166. package/lib/languages/lt.js +296 -122
  167. package/lib/languages/lv.d.ts +15 -67
  168. package/lib/languages/lv.js +297 -106
  169. package/lib/languages/mr.d.ts +4 -8
  170. package/lib/languages/mr.js +130 -125
  171. package/lib/languages/ms.d.ts +4 -28
  172. package/lib/languages/ms.js +171 -116
  173. package/lib/languages/nb.d.ts +11 -9
  174. package/lib/languages/nb.js +282 -87
  175. package/lib/languages/nl.d.ts +23 -13
  176. package/lib/languages/nl.js +317 -133
  177. package/lib/languages/pa.d.ts +4 -8
  178. package/lib/languages/pa.js +156 -124
  179. package/lib/languages/pl.d.ts +19 -77
  180. package/lib/languages/pl.js +307 -87
  181. package/lib/languages/pt.d.ts +14 -26
  182. package/lib/languages/pt.js +286 -92
  183. package/lib/languages/ro.d.ts +15 -155
  184. package/lib/languages/ro.js +219 -235
  185. package/lib/languages/ru.d.ts +8 -82
  186. package/lib/languages/ru.js +222 -78
  187. package/lib/languages/sr-Cyrl.d.ts +8 -77
  188. package/lib/languages/sr-Cyrl.js +191 -89
  189. package/lib/languages/sr-Latn.d.ts +8 -77
  190. package/lib/languages/sr-Latn.js +191 -89
  191. package/lib/languages/sv.d.ts +11 -11
  192. package/lib/languages/sv.js +288 -74
  193. package/lib/languages/sw.d.ts +4 -36
  194. package/lib/languages/sw.js +133 -106
  195. package/lib/languages/ta.d.ts +4 -17
  196. package/lib/languages/ta.js +129 -201
  197. package/lib/languages/te.d.ts +4 -19
  198. package/lib/languages/te.js +141 -196
  199. package/lib/languages/th.d.ts +4 -14
  200. package/lib/languages/th.js +135 -91
  201. package/lib/languages/tr.d.ts +15 -9
  202. package/lib/languages/tr.js +256 -49
  203. package/lib/languages/uk.d.ts +8 -82
  204. package/lib/languages/uk.js +200 -78
  205. package/lib/languages/ur.d.ts +4 -8
  206. package/lib/languages/ur.js +156 -124
  207. package/lib/languages/vi.d.ts +14 -69
  208. package/lib/languages/vi.js +294 -125
  209. package/lib/languages/zh-Hans.d.ts +8 -18
  210. package/lib/languages/zh-Hans.js +163 -92
  211. package/lib/languages/zh-Hant.d.ts +8 -18
  212. package/lib/languages/zh-Hant.js +181 -90
  213. package/lib/n2words.d.ts +53 -209
  214. package/lib/n2words.js +111 -530
  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 +26 -14
  222. package/dist/ArabicConverter.js +0 -3
  223. package/dist/ArabicConverter.js.map +0 -1
  224. package/dist/AzerbaijaniConverter.js +0 -3
  225. package/dist/AzerbaijaniConverter.js.map +0 -1
  226. package/dist/BanglaConverter.js +0 -3
  227. package/dist/BanglaConverter.js.map +0 -1
  228. package/dist/BiblicalHebrewConverter.js +0 -3
  229. package/dist/BiblicalHebrewConverter.js.map +0 -1
  230. package/dist/CroatianConverter.js +0 -3
  231. package/dist/CroatianConverter.js.map +0 -1
  232. package/dist/CzechConverter.js +0 -3
  233. package/dist/CzechConverter.js.map +0 -1
  234. package/dist/DanishConverter.js +0 -3
  235. package/dist/DanishConverter.js.map +0 -1
  236. package/dist/DutchConverter.js +0 -3
  237. package/dist/DutchConverter.js.map +0 -1
  238. package/dist/EnglishConverter.js +0 -3
  239. package/dist/EnglishConverter.js.map +0 -1
  240. package/dist/FilipinoConverter.js +0 -3
  241. package/dist/FilipinoConverter.js.map +0 -1
  242. package/dist/FrenchBelgiumConverter.js +0 -3
  243. package/dist/FrenchBelgiumConverter.js.map +0 -1
  244. package/dist/FrenchConverter.js +0 -3
  245. package/dist/FrenchConverter.js.map +0 -1
  246. package/dist/GermanConverter.js +0 -3
  247. package/dist/GermanConverter.js.map +0 -1
  248. package/dist/GreekConverter.js +0 -3
  249. package/dist/GreekConverter.js.map +0 -1
  250. package/dist/GujaratiConverter.js +0 -3
  251. package/dist/GujaratiConverter.js.map +0 -1
  252. package/dist/HebrewConverter.js +0 -3
  253. package/dist/HebrewConverter.js.map +0 -1
  254. package/dist/HindiConverter.js +0 -3
  255. package/dist/HindiConverter.js.map +0 -1
  256. package/dist/HungarianConverter.js +0 -3
  257. package/dist/HungarianConverter.js.map +0 -1
  258. package/dist/IndonesianConverter.js +0 -3
  259. package/dist/IndonesianConverter.js.map +0 -1
  260. package/dist/ItalianConverter.js +0 -3
  261. package/dist/ItalianConverter.js.map +0 -1
  262. package/dist/JapaneseConverter.js +0 -3
  263. package/dist/JapaneseConverter.js.map +0 -1
  264. package/dist/KannadaConverter.js +0 -3
  265. package/dist/KannadaConverter.js.map +0 -1
  266. package/dist/KoreanConverter.js +0 -3
  267. package/dist/KoreanConverter.js.map +0 -1
  268. package/dist/LatvianConverter.js +0 -3
  269. package/dist/LatvianConverter.js.map +0 -1
  270. package/dist/LithuanianConverter.js +0 -3
  271. package/dist/LithuanianConverter.js.map +0 -1
  272. package/dist/MalayConverter.js +0 -3
  273. package/dist/MalayConverter.js.map +0 -1
  274. package/dist/MarathiConverter.js +0 -3
  275. package/dist/MarathiConverter.js.map +0 -1
  276. package/dist/NorwegianBokmalConverter.js +0 -3
  277. package/dist/NorwegianBokmalConverter.js.map +0 -1
  278. package/dist/PersianConverter.js +0 -3
  279. package/dist/PersianConverter.js.map +0 -1
  280. package/dist/PolishConverter.js +0 -3
  281. package/dist/PolishConverter.js.map +0 -1
  282. package/dist/PortugueseConverter.js +0 -3
  283. package/dist/PortugueseConverter.js.map +0 -1
  284. package/dist/PunjabiConverter.js +0 -3
  285. package/dist/PunjabiConverter.js.map +0 -1
  286. package/dist/RomanianConverter.js +0 -3
  287. package/dist/RomanianConverter.js.map +0 -1
  288. package/dist/RussianConverter.js +0 -3
  289. package/dist/RussianConverter.js.map +0 -1
  290. package/dist/SerbianCyrillicConverter.js +0 -3
  291. package/dist/SerbianCyrillicConverter.js.map +0 -1
  292. package/dist/SerbianLatinConverter.js +0 -3
  293. package/dist/SerbianLatinConverter.js.map +0 -1
  294. package/dist/SimplifiedChineseConverter.js +0 -3
  295. package/dist/SimplifiedChineseConverter.js.map +0 -1
  296. package/dist/SpanishConverter.js +0 -3
  297. package/dist/SpanishConverter.js.map +0 -1
  298. package/dist/SwahiliConverter.js +0 -3
  299. package/dist/SwahiliConverter.js.map +0 -1
  300. package/dist/SwedishConverter.js +0 -3
  301. package/dist/SwedishConverter.js.map +0 -1
  302. package/dist/TamilConverter.js +0 -3
  303. package/dist/TamilConverter.js.map +0 -1
  304. package/dist/TeluguConverter.js +0 -3
  305. package/dist/TeluguConverter.js.map +0 -1
  306. package/dist/ThaiConverter.js +0 -3
  307. package/dist/ThaiConverter.js.map +0 -1
  308. package/dist/TraditionalChineseConverter.js +0 -3
  309. package/dist/TraditionalChineseConverter.js.map +0 -1
  310. package/dist/TurkishConverter.js +0 -3
  311. package/dist/TurkishConverter.js.map +0 -1
  312. package/dist/UkrainianConverter.js +0 -3
  313. package/dist/UkrainianConverter.js.map +0 -1
  314. package/dist/UrduConverter.js +0 -3
  315. package/dist/UrduConverter.js.map +0 -1
  316. package/dist/VietnameseConverter.js +0 -3
  317. package/dist/VietnameseConverter.js.map +0 -1
  318. package/lib/classes/abstract-language.d.ts +0 -178
  319. package/lib/classes/abstract-language.js +0 -268
  320. package/lib/classes/greedy-scale-language.d.ts +0 -109
  321. package/lib/classes/greedy-scale-language.js +0 -201
  322. package/lib/classes/slavic-language.d.ts +0 -148
  323. package/lib/classes/slavic-language.js +0 -281
  324. package/lib/classes/south-asian-language.d.ts +0 -70
  325. package/lib/classes/south-asian-language.js +0 -154
  326. package/lib/classes/turkic-language.d.ts +0 -26
  327. package/lib/classes/turkic-language.js +0 -59
@@ -1,144 +1,134 @@
1
- import { AbstractLanguage } from '../classes/abstract-language.js'
2
-
3
1
  /**
4
- * Persian language converter.
2
+ * Persian language converter - Functional Implementation
3
+ *
4
+ * Self-contained converter using recursive decomposition.
5
5
  *
6
- * Supports:
6
+ * Key features:
7
7
  * - "و" (and) conjunction for compound numbers
8
- * - Recursive conversion for larger numbers
8
+ * - Omit "یک" (one) before thousand
9
+ * - Pre-composed hundreds (دویست, سيصد, etc.)
9
10
  */
10
- export class Persian extends AbstractLanguage {
11
- negativeWord = 'منفى'
12
- decimalSeparatorWord = 'ممیّز'
13
- zeroWord = 'صفر'
14
-
15
- /**
16
- * Words for digits 1-9.
17
- * @type {Object.<number, string>}
18
- */
19
- onesWords = {
20
- 1: 'یک',
21
- 2: 'دو',
22
- 3: 'سه',
23
- 4: 'چهار',
24
- 5: 'پنج',
25
- 6: 'شش',
26
- 7: 'هفت',
27
- 8: 'هشت',
28
- 9: 'نه'
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)]
29
40
  }
30
41
 
31
- /**
32
- * Words for teen numbers (10-19).
33
- * @type {Object.<number, string>}
34
- */
35
- teensWords = {
36
- 10: 'ده',
37
- 11: 'یازده',
38
- 12: 'دوازده',
39
- 13: 'سیزده',
40
- 14: 'چهارده',
41
- 15: 'پانزده',
42
- 16: 'شانزده',
43
- 17: 'هفده',
44
- 18: 'هجده',
45
- 19: 'نوزده'
42
+ // 10-19
43
+ if (n <= 19n) {
44
+ return TEENS[Number(n)]
46
45
  }
47
46
 
48
- /**
49
- * Words for multiples of ten (20-90).
50
- * @type {Object.<number, string>}
51
- */
52
- tensWords = {
53
- 20: 'بیست',
54
- 30: 'سی',
55
- 40: 'چهل',
56
- 50: 'پنجاه',
57
- 60: 'شصت',
58
- 70: 'هفتاد',
59
- 80: 'هشتاد',
60
- 90: 'نود'
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)]
53
+ }
54
+ return `${TENS[Number(tens)]} و ${ONES[Number(ones)]}`
61
55
  }
62
56
 
63
- /**
64
- * Words for hundreds (100-900).
65
- * @type {Object.<number, string>}
66
- */
67
- hundredsWords = {
68
- 100: 'صد',
69
- 200: 'دویست',
70
- 300: 'سيصد',
71
- 400: 'چهار صد',
72
- 500: 'پانصد',
73
- 600: 'ششصد',
74
- 700: 'هفتصد',
75
- 800: 'هشتصد',
76
- 900: 'نهصد'
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)]
63
+ }
64
+ return `${HUNDREDS[Number(hundreds)]} و ${integerToWords(remainder)}`
77
65
  }
78
66
 
79
- /**
80
- * Scale magnitude words.
81
- * @type {Object.<number, string>}
82
- */
83
- scaleWords = {
84
- 1000: 'هزار',
85
- 1_000_000: 'میلیون'
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}`
86
77
  }
87
78
 
88
- /** Converts integer part using categorized word tables. */
89
- integerToWords (integerPart) {
90
- // Zero
91
- if (integerPart === 0n) {
92
- return this.zeroWord
93
- }
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
+ }
94
86
 
95
- // 1-9
96
- if (integerPart <= 9n) {
97
- return this.onesWords[integerPart]
98
- }
87
+ function decimalPartToWords (decimalPart) {
88
+ let result = ''
89
+ let i = 0
99
90
 
100
- // 10-19
101
- if (integerPart <= 19n) {
102
- return this.teensWords[integerPart]
103
- }
91
+ while (i < decimalPart.length && decimalPart[i] === '0') {
92
+ if (result) result += ' '
93
+ result += ZERO
94
+ i++
95
+ }
104
96
 
105
- // 20-99
106
- if (integerPart < 100n) {
107
- const ones = integerPart % 10n
108
- const tens = integerPart - ones
109
- if (ones === 0n) {
110
- return this.tensWords[tens]
111
- }
112
- return `${this.tensWords[tens]} و ${this.onesWords[ones]}`
113
- }
97
+ const remainder = decimalPart.slice(i)
98
+ if (remainder) {
99
+ if (result) result += ' '
100
+ result += integerToWords(BigInt(remainder))
101
+ }
114
102
 
115
- // 100-999
116
- if (integerPart < 1000n) {
117
- const hundreds = 100n * (integerPart / 100n)
118
- const remainder = integerPart - hundreds
119
- if (remainder === 0n) {
120
- return this.hundredsWords[hundreds]
121
- }
122
- return `${this.hundredsWords[hundreds]} و ${this.integerToWords(remainder)}`
123
- }
103
+ return result
104
+ }
124
105
 
125
- // 1000-999999
126
- if (integerPart < 1_000_000n) {
127
- const thousandMultiplier = integerPart / 1000n
128
- // Persian omits "one" before thousand: 1000 is just "هزار", not "یک هزار"
129
- const thousandPrefix = thousandMultiplier === 1n
130
- ? ''
131
- : this.integerToWords(thousandMultiplier) + ' '
132
- const remainder = integerPart % 1000n
133
- const suffix = remainder === 0n ? '' : ' ' + this.integerToWords(remainder)
134
- return `${thousandPrefix}${this.scaleWords[1000]}${suffix}`
135
- }
106
+ /**
107
+ * Converts a numeric value to Persian words.
108
+ *
109
+ * @param {number | string | bigint} value - The numeric value to convert
110
+ * @returns {string} The number in Persian words
111
+ */
112
+ function toWords (value) {
113
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
114
+
115
+ let result = ''
116
+
117
+ if (isNegative) {
118
+ result = NEGATIVE + ' '
119
+ }
136
120
 
137
- // 1000000+
138
- const millionMultiplier = integerPart / 1_000_000n
139
- const millionPrefix = this.integerToWords(millionMultiplier) + ' ' + this.scaleWords[1_000_000]
140
- const remainder = integerPart % 1_000_000n
141
- const suffix = remainder === 0n ? '' : ' و ' + this.integerToWords(remainder)
142
- return `${millionPrefix}${suffix}`
121
+ result += integerToWords(integerPart)
122
+
123
+ if (decimalPart) {
124
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
143
125
  }
126
+
127
+ return result
144
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 }
@@ -1,16 +1,7 @@
1
1
  /**
2
- * Filipino language converter.
2
+ * Converts a numeric value to Filipino words.
3
3
  *
4
- * Supports:
5
- * - "ng" connectors between words
6
- * - Implicit "one" omission
7
- * - Special linkers for certain tens (e.g., "limampung")
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Filipino words
8
6
  */
9
- export class Filipino extends GreedyScaleLanguage {
10
- scaleWords: (string | bigint)[][];
11
- /** Converts integer part with explicit zero handling. */
12
- integerToWords(integerPart: any): string;
13
- /** Combines two word-sets with Filipino connector and linker rules. */
14
- combineWordSets(preceding: any, following: any): any;
15
- }
16
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js';
7
+ export function toWords(value: number | string | bigint): string;