n2words 2.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/CHANGELOG.md +64 -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/ka.js +3 -0
  56. package/dist/languages/ka.js.map +1 -0
  57. package/dist/languages/kn.js +3 -0
  58. package/dist/languages/kn.js.map +1 -0
  59. package/dist/languages/ko.js +3 -0
  60. package/dist/languages/ko.js.map +1 -0
  61. package/dist/languages/lt.js +3 -0
  62. package/dist/languages/lt.js.map +1 -0
  63. package/dist/languages/lv.js +3 -0
  64. package/dist/languages/lv.js.map +1 -0
  65. package/dist/languages/mr.js +3 -0
  66. package/dist/languages/mr.js.map +1 -0
  67. package/dist/languages/ms.js +3 -0
  68. package/dist/languages/ms.js.map +1 -0
  69. package/dist/languages/nb.js +3 -0
  70. package/dist/languages/nb.js.map +1 -0
  71. package/dist/languages/nl.js +3 -0
  72. package/dist/languages/nl.js.map +1 -0
  73. package/dist/languages/pa.js +3 -0
  74. package/dist/languages/pa.js.map +1 -0
  75. package/dist/languages/pl.js +3 -0
  76. package/dist/languages/pl.js.map +1 -0
  77. package/dist/languages/pt.js +3 -0
  78. package/dist/languages/pt.js.map +1 -0
  79. package/dist/languages/ro.js +3 -0
  80. package/dist/languages/ro.js.map +1 -0
  81. package/dist/languages/ru.js +3 -0
  82. package/dist/languages/ru.js.map +1 -0
  83. package/dist/languages/sr-Cyrl.js +3 -0
  84. package/dist/languages/sr-Cyrl.js.map +1 -0
  85. package/dist/languages/sr-Latn.js +3 -0
  86. package/dist/languages/sr-Latn.js.map +1 -0
  87. package/dist/languages/sv.js +3 -0
  88. package/dist/languages/sv.js.map +1 -0
  89. package/dist/languages/sw.js +3 -0
  90. package/dist/languages/sw.js.map +1 -0
  91. package/dist/languages/ta.js +3 -0
  92. package/dist/languages/ta.js.map +1 -0
  93. package/dist/languages/te.js +3 -0
  94. package/dist/languages/te.js.map +1 -0
  95. package/dist/languages/th.js +3 -0
  96. package/dist/languages/th.js.map +1 -0
  97. package/dist/languages/tr.js +3 -0
  98. package/dist/languages/tr.js.map +1 -0
  99. package/dist/languages/uk.js +3 -0
  100. package/dist/languages/uk.js.map +1 -0
  101. package/dist/languages/ur.js +3 -0
  102. package/dist/languages/ur.js.map +1 -0
  103. package/dist/languages/vi.js +3 -0
  104. package/dist/languages/vi.js.map +1 -0
  105. package/dist/languages/yo.js +3 -0
  106. package/dist/languages/yo.js.map +1 -0
  107. package/dist/languages/zh-Hans.js +3 -0
  108. package/dist/languages/zh-Hans.js.map +1 -0
  109. package/dist/languages/zh-Hant.js +3 -0
  110. package/dist/languages/zh-Hant.js.map +1 -0
  111. package/dist/n2words.js +2 -2
  112. package/dist/n2words.js.map +1 -1
  113. package/lib/languages/am-Latn.d.ts +7 -0
  114. package/lib/languages/am-Latn.js +159 -0
  115. package/lib/languages/am.d.ts +7 -0
  116. package/lib/languages/am.js +159 -0
  117. package/lib/languages/ar.d.ts +14 -27
  118. package/lib/languages/ar.js +175 -129
  119. package/lib/languages/az.d.ts +4 -9
  120. package/lib/languages/az.js +166 -37
  121. package/lib/languages/bn.d.ts +4 -8
  122. package/lib/languages/bn.js +159 -124
  123. package/lib/languages/cs.d.ts +15 -85
  124. package/lib/languages/cs.js +293 -114
  125. package/lib/languages/da.d.ts +11 -12
  126. package/lib/languages/da.js +269 -101
  127. package/lib/languages/de.d.ts +14 -11
  128. package/lib/languages/de.js +305 -86
  129. package/lib/languages/el.d.ts +11 -11
  130. package/lib/languages/el.js +224 -78
  131. package/lib/languages/en.d.ts +14 -13
  132. package/lib/languages/en.js +228 -72
  133. package/lib/languages/es.d.ts +18 -12
  134. package/lib/languages/es.js +297 -103
  135. package/lib/languages/fa.d.ts +4 -44
  136. package/lib/languages/fa.js +112 -122
  137. package/lib/languages/fi.d.ts +14 -0
  138. package/lib/languages/fi.js +238 -0
  139. package/lib/languages/fil.d.ts +4 -13
  140. package/lib/languages/fil.js +196 -106
  141. package/lib/languages/fr-BE.d.ts +8 -8
  142. package/lib/languages/fr-BE.js +285 -19
  143. package/lib/languages/fr.d.ts +18 -12
  144. package/lib/languages/fr.js +339 -89
  145. package/lib/languages/gu.d.ts +4 -8
  146. package/lib/languages/gu.js +143 -125
  147. package/lib/languages/ha.d.ts +7 -0
  148. package/lib/languages/ha.js +225 -0
  149. package/lib/languages/hbo.d.ts +10 -110
  150. package/lib/languages/hbo.js +245 -214
  151. package/lib/languages/he.d.ts +10 -77
  152. package/lib/languages/he.js +231 -172
  153. package/lib/languages/hi.d.ts +4 -8
  154. package/lib/languages/hi.js +163 -124
  155. package/lib/languages/hr.d.ts +8 -77
  156. package/lib/languages/hr.js +200 -89
  157. package/lib/languages/hu.d.ts +4 -19
  158. package/lib/languages/hu.js +198 -119
  159. package/lib/languages/id.d.ts +4 -34
  160. package/lib/languages/id.js +166 -129
  161. package/lib/languages/it.d.ts +16 -34
  162. package/lib/languages/it.js +307 -97
  163. package/lib/languages/ja.d.ts +14 -14
  164. package/lib/languages/ja.js +221 -111
  165. package/lib/languages/ka.d.ts +17 -0
  166. package/lib/languages/ka.js +291 -0
  167. package/lib/languages/kn.d.ts +4 -8
  168. package/lib/languages/kn.js +143 -35
  169. package/lib/languages/ko.d.ts +11 -11
  170. package/lib/languages/ko.js +250 -49
  171. package/lib/languages/lt.d.ts +15 -67
  172. package/lib/languages/lt.js +287 -122
  173. package/lib/languages/lv.d.ts +15 -67
  174. package/lib/languages/lv.js +288 -106
  175. package/lib/languages/mr.d.ts +4 -8
  176. package/lib/languages/mr.js +143 -125
  177. package/lib/languages/ms.d.ts +4 -28
  178. package/lib/languages/ms.js +166 -116
  179. package/lib/languages/nb.d.ts +11 -9
  180. package/lib/languages/nb.js +272 -87
  181. package/lib/languages/nl.d.ts +23 -13
  182. package/lib/languages/nl.js +299 -133
  183. package/lib/languages/pa.d.ts +4 -8
  184. package/lib/languages/pa.js +151 -124
  185. package/lib/languages/pl.d.ts +19 -77
  186. package/lib/languages/pl.js +294 -87
  187. package/lib/languages/pt.d.ts +14 -26
  188. package/lib/languages/pt.js +272 -92
  189. package/lib/languages/ro.d.ts +15 -155
  190. package/lib/languages/ro.js +219 -235
  191. package/lib/languages/ru.d.ts +8 -82
  192. package/lib/languages/ru.js +239 -90
  193. package/lib/languages/sr-Cyrl.d.ts +8 -77
  194. package/lib/languages/sr-Cyrl.js +197 -89
  195. package/lib/languages/sr-Latn.d.ts +8 -77
  196. package/lib/languages/sr-Latn.js +197 -89
  197. package/lib/languages/sv.d.ts +11 -11
  198. package/lib/languages/sv.js +278 -74
  199. package/lib/languages/sw.d.ts +4 -36
  200. package/lib/languages/sw.js +133 -106
  201. package/lib/languages/ta.d.ts +4 -17
  202. package/lib/languages/ta.js +143 -202
  203. package/lib/languages/te.d.ts +4 -19
  204. package/lib/languages/te.js +133 -196
  205. package/lib/languages/th.d.ts +4 -14
  206. package/lib/languages/th.js +135 -91
  207. package/lib/languages/tr.d.ts +15 -9
  208. package/lib/languages/tr.js +245 -49
  209. package/lib/languages/uk.d.ts +8 -82
  210. package/lib/languages/uk.js +206 -78
  211. package/lib/languages/ur.d.ts +4 -8
  212. package/lib/languages/ur.js +151 -124
  213. package/lib/languages/vi.d.ts +14 -69
  214. package/lib/languages/vi.js +278 -129
  215. package/lib/languages/yo.d.ts +7 -0
  216. package/lib/languages/yo.js +303 -0
  217. package/lib/languages/zh-Hans.d.ts +8 -18
  218. package/lib/languages/zh-Hans.js +163 -92
  219. package/lib/languages/zh-Hant.d.ts +8 -18
  220. package/lib/languages/zh-Hant.js +181 -90
  221. package/lib/n2words.d.ts +55 -209
  222. package/lib/n2words.js +115 -530
  223. package/lib/utils/is-plain-object.d.ts +13 -0
  224. package/lib/utils/is-plain-object.js +17 -0
  225. package/lib/utils/parse-numeric.d.ts +17 -0
  226. package/lib/utils/parse-numeric.js +108 -0
  227. package/lib/utils/validate-options.d.ts +8 -0
  228. package/lib/utils/validate-options.js +16 -0
  229. package/package.json +26 -14
  230. package/dist/ArabicConverter.js +0 -3
  231. package/dist/ArabicConverter.js.map +0 -1
  232. package/dist/AzerbaijaniConverter.js +0 -3
  233. package/dist/AzerbaijaniConverter.js.map +0 -1
  234. package/dist/BanglaConverter.js +0 -3
  235. package/dist/BanglaConverter.js.map +0 -1
  236. package/dist/BiblicalHebrewConverter.js +0 -3
  237. package/dist/BiblicalHebrewConverter.js.map +0 -1
  238. package/dist/CroatianConverter.js +0 -3
  239. package/dist/CroatianConverter.js.map +0 -1
  240. package/dist/CzechConverter.js +0 -3
  241. package/dist/CzechConverter.js.map +0 -1
  242. package/dist/DanishConverter.js +0 -3
  243. package/dist/DanishConverter.js.map +0 -1
  244. package/dist/DutchConverter.js +0 -3
  245. package/dist/DutchConverter.js.map +0 -1
  246. package/dist/EnglishConverter.js +0 -3
  247. package/dist/EnglishConverter.js.map +0 -1
  248. package/dist/FilipinoConverter.js +0 -3
  249. package/dist/FilipinoConverter.js.map +0 -1
  250. package/dist/FrenchBelgiumConverter.js +0 -3
  251. package/dist/FrenchBelgiumConverter.js.map +0 -1
  252. package/dist/FrenchConverter.js +0 -3
  253. package/dist/FrenchConverter.js.map +0 -1
  254. package/dist/GermanConverter.js +0 -3
  255. package/dist/GermanConverter.js.map +0 -1
  256. package/dist/GreekConverter.js +0 -3
  257. package/dist/GreekConverter.js.map +0 -1
  258. package/dist/GujaratiConverter.js +0 -3
  259. package/dist/GujaratiConverter.js.map +0 -1
  260. package/dist/HebrewConverter.js +0 -3
  261. package/dist/HebrewConverter.js.map +0 -1
  262. package/dist/HindiConverter.js +0 -3
  263. package/dist/HindiConverter.js.map +0 -1
  264. package/dist/HungarianConverter.js +0 -3
  265. package/dist/HungarianConverter.js.map +0 -1
  266. package/dist/IndonesianConverter.js +0 -3
  267. package/dist/IndonesianConverter.js.map +0 -1
  268. package/dist/ItalianConverter.js +0 -3
  269. package/dist/ItalianConverter.js.map +0 -1
  270. package/dist/JapaneseConverter.js +0 -3
  271. package/dist/JapaneseConverter.js.map +0 -1
  272. package/dist/KannadaConverter.js +0 -3
  273. package/dist/KannadaConverter.js.map +0 -1
  274. package/dist/KoreanConverter.js +0 -3
  275. package/dist/KoreanConverter.js.map +0 -1
  276. package/dist/LatvianConverter.js +0 -3
  277. package/dist/LatvianConverter.js.map +0 -1
  278. package/dist/LithuanianConverter.js +0 -3
  279. package/dist/LithuanianConverter.js.map +0 -1
  280. package/dist/MalayConverter.js +0 -3
  281. package/dist/MalayConverter.js.map +0 -1
  282. package/dist/MarathiConverter.js +0 -3
  283. package/dist/MarathiConverter.js.map +0 -1
  284. package/dist/NorwegianBokmalConverter.js +0 -3
  285. package/dist/NorwegianBokmalConverter.js.map +0 -1
  286. package/dist/PersianConverter.js +0 -3
  287. package/dist/PersianConverter.js.map +0 -1
  288. package/dist/PolishConverter.js +0 -3
  289. package/dist/PolishConverter.js.map +0 -1
  290. package/dist/PortugueseConverter.js +0 -3
  291. package/dist/PortugueseConverter.js.map +0 -1
  292. package/dist/PunjabiConverter.js +0 -3
  293. package/dist/PunjabiConverter.js.map +0 -1
  294. package/dist/RomanianConverter.js +0 -3
  295. package/dist/RomanianConverter.js.map +0 -1
  296. package/dist/RussianConverter.js +0 -3
  297. package/dist/RussianConverter.js.map +0 -1
  298. package/dist/SerbianCyrillicConverter.js +0 -3
  299. package/dist/SerbianCyrillicConverter.js.map +0 -1
  300. package/dist/SerbianLatinConverter.js +0 -3
  301. package/dist/SerbianLatinConverter.js.map +0 -1
  302. package/dist/SimplifiedChineseConverter.js +0 -3
  303. package/dist/SimplifiedChineseConverter.js.map +0 -1
  304. package/dist/SpanishConverter.js +0 -3
  305. package/dist/SpanishConverter.js.map +0 -1
  306. package/dist/SwahiliConverter.js +0 -3
  307. package/dist/SwahiliConverter.js.map +0 -1
  308. package/dist/SwedishConverter.js +0 -3
  309. package/dist/SwedishConverter.js.map +0 -1
  310. package/dist/TamilConverter.js +0 -3
  311. package/dist/TamilConverter.js.map +0 -1
  312. package/dist/TeluguConverter.js +0 -3
  313. package/dist/TeluguConverter.js.map +0 -1
  314. package/dist/ThaiConverter.js +0 -3
  315. package/dist/ThaiConverter.js.map +0 -1
  316. package/dist/TraditionalChineseConverter.js +0 -3
  317. package/dist/TraditionalChineseConverter.js.map +0 -1
  318. package/dist/TurkishConverter.js +0 -3
  319. package/dist/TurkishConverter.js.map +0 -1
  320. package/dist/UkrainianConverter.js +0 -3
  321. package/dist/UkrainianConverter.js.map +0 -1
  322. package/dist/UrduConverter.js +0 -3
  323. package/dist/UrduConverter.js.map +0 -1
  324. package/dist/VietnameseConverter.js +0 -3
  325. package/dist/VietnameseConverter.js.map +0 -1
  326. package/lib/classes/abstract-language.d.ts +0 -178
  327. package/lib/classes/abstract-language.js +0 -268
  328. package/lib/classes/greedy-scale-language.d.ts +0 -109
  329. package/lib/classes/greedy-scale-language.js +0 -201
  330. package/lib/classes/slavic-language.d.ts +0 -148
  331. package/lib/classes/slavic-language.js +0 -281
  332. package/lib/classes/south-asian-language.d.ts +0 -70
  333. package/lib/classes/south-asian-language.js +0 -154
  334. package/lib/classes/turkic-language.d.ts +0 -26
  335. 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,238 @@
1
+ /**
2
+ * Finnish language converter - Functional Implementation
3
+ *
4
+ * Self-contained module with its own input validation, ready for subpath exports.
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
+ // Segment Building
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
+ // ============================================================================
83
+ // Conversion Functions
84
+ // ============================================================================
85
+
86
+ /**
87
+ * Converts a non-negative integer to Finnish words.
88
+ *
89
+ * @param {bigint} n - Non-negative integer to convert
90
+ * @returns {string} Finnish words
91
+ */
92
+ function integerToWords (n) {
93
+ if (n === 0n) return ZERO
94
+
95
+ // Fast path: numbers < 1000 (direct lookup)
96
+ if (n < 1000n) {
97
+ return buildSegment(Number(n))
98
+ }
99
+
100
+ // Fast path: numbers < 1,000,000 (thousands)
101
+ if (n < 1_000_000n) {
102
+ const thousands = Number(n / 1000n)
103
+ const remainder = Number(n % 1000n)
104
+
105
+ // Omit "yksi" before tuhat
106
+ let result
107
+ if (thousands === 1) {
108
+ result = THOUSAND
109
+ } else {
110
+ result = buildSegment(thousands) + ' ' + THOUSAND
111
+ }
112
+
113
+ if (remainder > 0) {
114
+ result += ' ' + buildSegment(remainder)
115
+ }
116
+
117
+ return result
118
+ }
119
+
120
+ // For numbers >= 1,000,000, use scale decomposition
121
+ return buildLargeNumberWords(n)
122
+ }
123
+
124
+ /**
125
+ * Builds words for numbers >= 1,000,000.
126
+ *
127
+ * @param {bigint} n - Number >= 1,000,000
128
+ * @returns {string} Finnish words
129
+ */
130
+ function buildLargeNumberWords (n) {
131
+ const numStr = n.toString()
132
+ const len = numStr.length
133
+
134
+ // Build segments of 3 digits from right to left
135
+ const segments = []
136
+ const segmentSize = 3
137
+
138
+ const remainderLen = len % segmentSize
139
+ let pos = 0
140
+ if (remainderLen > 0) {
141
+ segments.push(Number(numStr.slice(0, remainderLen)))
142
+ pos = remainderLen
143
+ }
144
+ while (pos < len) {
145
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
146
+ pos += segmentSize
147
+ }
148
+
149
+ // Convert segments to words
150
+ const parts = []
151
+ let scaleIndex = segments.length - 1
152
+
153
+ for (let i = 0; i < segments.length; i++) {
154
+ const segment = segments[i]
155
+
156
+ if (segment !== 0) {
157
+ const segmentWord = buildSegment(segment)
158
+
159
+ if (scaleIndex === 0) {
160
+ // Units segment
161
+ parts.push(segmentWord)
162
+ } else if (scaleIndex === 1) {
163
+ // Thousands - omit "yksi" before tuhat
164
+ if (segment === 1) {
165
+ parts.push(THOUSAND)
166
+ } else {
167
+ parts.push(segmentWord + ' ' + THOUSAND)
168
+ }
169
+ } else {
170
+ // Millions+ - keep "yksi" before scale words
171
+ const scaleWord = SCALES[scaleIndex - 2]
172
+ parts.push(segmentWord + ' ' + scaleWord)
173
+ }
174
+ }
175
+
176
+ scaleIndex--
177
+ }
178
+
179
+ return parts.join(' ')
180
+ }
181
+
182
+ /**
183
+ * Converts decimal digits to Finnish words (per-digit).
184
+ *
185
+ * @param {string} decimalPart - Decimal digits (without the point)
186
+ * @returns {string} Finnish words for decimal part
187
+ */
188
+ function decimalPartToWords (decimalPart) {
189
+ const parts = []
190
+
191
+ for (const digit of decimalPart) {
192
+ const d = parseInt(digit, 10)
193
+ if (d === 0) {
194
+ parts.push(ZERO)
195
+ } else {
196
+ parts.push(ONES[d])
197
+ }
198
+ }
199
+
200
+ return parts.join(' ')
201
+ }
202
+
203
+ /**
204
+ * Converts a numeric value to Finnish words.
205
+ *
206
+ * @param {number | string | bigint} value - The numeric value to convert
207
+ * @returns {string} The number in Finnish words
208
+ * @throws {TypeError} If value is not a valid numeric type
209
+ * @throws {Error} If value is not a valid number format
210
+ *
211
+ * @example
212
+ * toWords(21) // 'kaksikymmentäyksi'
213
+ * toWords(1000) // 'tuhat'
214
+ * toWords('3.14') // 'kolme pilkku yksi neljä'
215
+ */
216
+ function toWords (value) {
217
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
218
+
219
+ let result = ''
220
+
221
+ if (isNegative) {
222
+ result = NEGATIVE + ' '
223
+ }
224
+
225
+ result += integerToWords(integerPart)
226
+
227
+ if (decimalPart) {
228
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
229
+ }
230
+
231
+ return result
232
+ }
233
+
234
+ // ============================================================================
235
+ // Public API
236
+ // ============================================================================
237
+
238
+ 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;