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,90 +1,304 @@
1
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js'
2
-
3
1
  /**
4
- * Swedish language converter.
2
+ * Swedish language converter - Functional Implementation
3
+ *
4
+ * A performance-optimized number-to-words converter using precomputed lookup tables.
5
5
  *
6
- * Supports:
7
- * - Hyphenation for compound tens (tjugo-tre)
8
- * - "och" (and) after hundreds
9
- * - Space-separated larger composites
6
+ * Key features:
7
+ * - Hyphenation for tens-ones (tjugo-ett)
8
+ * - "och" after hundreds before small numbers
9
+ * - Omit "ett" before hundra and tusen
10
+ * - Use "en" (not "ett") before million+ scales
11
+ * - Long scale naming with -ard forms
12
+ */
13
+
14
+ import { parseNumericValue } from '../utils/parse-numeric.js'
15
+
16
+ // ============================================================================
17
+ // Vocabulary (module-level constants)
18
+ // ============================================================================
19
+
20
+ const ONES = ['', 'ett', 'två', 'tre', 'fyra', 'fem', 'sex', 'sju', 'åtta', 'nio']
21
+
22
+ const TEENS = ['tio', 'elva', 'tolv', 'tretton', 'fjorton', 'femton', 'sexton', 'sjutton', 'arton', 'nitton']
23
+ const TENS = ['', '', 'tjugo', 'trettio', 'fyrtio', 'femtio', 'sextio', 'sjuttio', 'åttio', 'nittio']
24
+
25
+ const HUNDRED = 'hundra'
26
+
27
+ const ZERO = 'noll'
28
+ const NEGATIVE = 'minus'
29
+ const DECIMAL_SEP = 'komma'
30
+
31
+ // Scale words (long scale with -ard forms)
32
+ const SCALES = ['tusen', 'miljon', 'miljard', 'biljon', 'biljard', 'triljon', 'triljard', 'kvadriljon']
33
+
34
+ // ============================================================================
35
+ // Precomputed Lookup Tables (built once at module load)
36
+ // ============================================================================
37
+
38
+ /**
39
+ * Builds segment word for 0-999.
40
+ * Returns object with word and metadata for "och" logic.
10
41
  */
11
- export class Swedish extends GreedyScaleLanguage {
12
- negativeWord = 'minus'
13
- decimalSeparatorWord = 'komma'
14
- zeroWord = 'noll'
15
- wordSeparator = ' '
16
-
17
- scaleWords = [
18
- [1_000_000_000_000_000_000_000_000_000n, 'kvadriljon'],
19
- [1_000_000_000_000_000_000_000_000n, 'triljon'],
20
- [1_000_000_000_000_000_000_000n, 'biljon'],
21
- [1_000_000_000_000_000_000n, 'miljard'],
22
- [1_000_000_000_000n, 'biljon'],
23
- [1_000_000_000n, 'miljard'],
24
- [1_000_000n, 'miljon'],
25
- [1000n, 'tusen'],
26
- [100n, 'hundra'],
27
- [90n, 'nittio'],
28
- [80n, 'åttio'],
29
- [70n, 'sjuttio'],
30
- [60n, 'sextio'],
31
- [50n, 'femtio'],
32
- [40n, 'fyrtio'],
33
- [30n, 'trettio'],
34
- [20n, 'tjugo'],
35
- [19n, 'nitton'],
36
- [18n, 'arton'],
37
- [17n, 'sjutton'],
38
- [16n, 'sexton'],
39
- [15n, 'femton'],
40
- [14n, 'fjorton'],
41
- [13n, 'tretton'],
42
- [12n, 'tolv'],
43
- [11n, 'elva'],
44
- [10n, 'tio'],
45
- [9n, 'nio'],
46
- [8n, 'åtta'],
47
- [7n, 'sju'],
48
- [6n, 'sex'],
49
- [5n, 'fem'],
50
- [4n, 'fyra'],
51
- [3n, 'tre'],
52
- [2n, 'två'],
53
- [1n, 'ett'],
54
- [0n, 'noll']
55
- ]
56
-
57
- /** Combines two word-sets according to Swedish grammar rules. */
58
- combineWordSets (preceding, following) {
59
- const precedingWord = Object.keys(preceding)[0]
60
- const followingWord = Object.keys(following)[0]
61
- const precedingValue = Object.values(preceding)[0]
62
- const followingValue = Object.values(following)[0]
63
-
64
- if (precedingValue === 1n && followingValue < 100n) {
65
- return following
42
+ function buildSegment (n) {
43
+ if (n === 0) return { word: '', hasHundred: false, lessThan100: false }
44
+
45
+ const ones = n % 10
46
+ const tens = Math.floor(n / 10) % 10
47
+ const hundreds = Math.floor(n / 100)
48
+
49
+ const parts = []
50
+ let hasHundred = false
51
+
52
+ // Hundreds - omit "ett" before hundra
53
+ if (hundreds > 0) {
54
+ hasHundred = true
55
+ if (hundreds === 1) {
56
+ parts.push(HUNDRED)
57
+ } else {
58
+ parts.push(ONES[hundreds] + ' ' + HUNDRED)
66
59
  }
60
+ }
67
61
 
68
- // Omit 'ett' before 'hundra' and 'tusen'
69
- if (precedingValue === 1n && (followingValue === 100n || followingValue === 1000n)) {
70
- return following
62
+ // Tens and ones with hyphenation
63
+ let tensOnesWord = ''
64
+ if (tens === 1) {
65
+ tensOnesWord = TEENS[ones]
66
+ } else if (tens >= 2) {
67
+ if (ones > 0) {
68
+ tensOnesWord = TENS[tens] + '-' + ONES[ones]
69
+ } else {
70
+ tensOnesWord = TENS[tens]
71
71
  }
72
+ } else if (ones > 0) {
73
+ tensOnesWord = ONES[ones]
74
+ }
75
+
76
+ // Combine with "och" after hundreds if there's a remainder
77
+ if (hasHundred && tensOnesWord) {
78
+ return { word: parts[0] + ' och ' + tensOnesWord, hasHundred: true, lessThan100: false }
79
+ } else if (hasHundred) {
80
+ return { word: parts[0], hasHundred: true, lessThan100: false }
81
+ } else {
82
+ return { word: tensOnesWord, hasHundred: false, lessThan100: true }
83
+ }
84
+ }
85
+
86
+ // Precompute all 1000 segment words (0-999)
87
+ const SEGMENTS = new Array(1000)
88
+ const SEGMENTS_HAS_HUNDRED = new Array(1000)
89
+ const SEGMENTS_LESS_THAN_100 = new Array(1000)
72
90
 
73
- if (precedingValue < 100n && precedingValue > followingValue) {
74
- return { [`${precedingWord}-${followingWord}`]: precedingValue + followingValue }
91
+ for (let i = 0; i < 1000; i++) {
92
+ const result = buildSegment(i)
93
+ SEGMENTS[i] = result.word
94
+ SEGMENTS_HAS_HUNDRED[i] = result.hasHundred
95
+ SEGMENTS_LESS_THAN_100[i] = result.lessThan100
96
+ }
97
+
98
+ // ============================================================================
99
+ // Conversion Functions
100
+ // ============================================================================
101
+
102
+ /**
103
+ * Converts a non-negative integer to Swedish words.
104
+ *
105
+ * @param {bigint} n - Non-negative integer to convert
106
+ * @returns {string} Swedish words
107
+ */
108
+ function integerToWords (n) {
109
+ if (n === 0n) return ZERO
110
+
111
+ // Fast path: numbers < 1000 (direct lookup)
112
+ if (n < 1000n) {
113
+ return SEGMENTS[Number(n)]
114
+ }
115
+
116
+ // Fast path: numbers < 1,000,000 (thousands)
117
+ if (n < 1_000_000n) {
118
+ const thousands = Number(n / 1000n)
119
+ const remainder = Number(n % 1000n)
120
+
121
+ // Omit "ett" before tusen
122
+ let result = thousands === 1 ? SCALES[0] : SEGMENTS[thousands] + ' ' + SCALES[0]
123
+
124
+ if (remainder > 0) {
125
+ const remainderWord = SEGMENTS[remainder]
126
+ // Insert "och" if remainder < 100 (doesn't have hundred)
127
+ if (SEGMENTS_LESS_THAN_100[remainder]) {
128
+ result += ' och ' + remainderWord
129
+ } else {
130
+ result += ' ' + remainderWord
131
+ }
75
132
  }
76
133
 
77
- if (precedingValue >= 100n && followingValue < 100n) {
78
- return { [`${precedingWord} och ${followingWord}`]: precedingValue + followingValue }
134
+ return result
135
+ }
136
+
137
+ // For numbers >= 1,000,000, use scale decomposition
138
+ return buildLargeNumberWords(n)
139
+ }
140
+
141
+ /**
142
+ * Builds words for numbers >= 1,000,000.
143
+ *
144
+ * @param {bigint} n - Number >= 1,000,000
145
+ * @returns {string} Swedish words
146
+ */
147
+ function buildLargeNumberWords (n) {
148
+ const numStr = n.toString()
149
+ const len = numStr.length
150
+
151
+ // Build segments of 3 digits from right to left
152
+ const segments = []
153
+ const segmentSize = 3
154
+
155
+ const remainderLen = len % segmentSize
156
+ let pos = 0
157
+ if (remainderLen > 0) {
158
+ segments.push(Number(numStr.slice(0, remainderLen)))
159
+ pos = remainderLen
160
+ }
161
+ while (pos < len) {
162
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
163
+ pos += segmentSize
164
+ }
165
+
166
+ // Convert segments to words
167
+ const parts = []
168
+ let scaleIndex = segments.length - 1
169
+
170
+ for (let i = 0; i < segments.length; i++) {
171
+ const segment = segments[i]
172
+
173
+ if (segment !== 0) {
174
+ if (scaleIndex === 0) {
175
+ // Units segment
176
+ parts.push({
177
+ word: SEGMENTS[segment],
178
+ hasHundred: SEGMENTS_HAS_HUNDRED[segment],
179
+ isScale: false
180
+ })
181
+ } else {
182
+ // Segment with scale word
183
+ const scaleWord = SCALES[scaleIndex - 1]
184
+
185
+ let segmentWord
186
+ if (segment === 1) {
187
+ // Omit "ett" before tusen, use "en" before million+
188
+ if (scaleIndex === 1) {
189
+ segmentWord = '' // Just "tusen"
190
+ } else {
191
+ segmentWord = 'en' // "en miljon"
192
+ }
193
+ } else {
194
+ segmentWord = SEGMENTS[segment]
195
+ }
196
+
197
+ if (segmentWord) {
198
+ parts.push({ word: segmentWord, hasHundred: false, isScale: false })
199
+ }
200
+ parts.push({ word: scaleWord, hasHundred: false, isScale: true })
201
+ }
79
202
  }
80
203
 
81
- if (followingValue > precedingValue) {
82
- if (precedingValue === 1n && followingValue >= 1_000_000n) {
83
- return { [`en ${followingWord}`]: precedingValue * followingValue }
204
+ scaleIndex--
205
+ }
206
+
207
+ // Join with Swedish "och" rules
208
+ return joinSwedishParts(parts)
209
+ }
210
+
211
+ /**
212
+ * Joins parts with Swedish "och" rules.
213
+ * Insert "och" before final segment if it follows a scale word and doesn't have "hundra".
214
+ *
215
+ * @param {Array} parts - Parts with metadata
216
+ * @returns {string} Joined string
217
+ */
218
+ function joinSwedishParts (parts) {
219
+ if (parts.length === 0) return ZERO
220
+ if (parts.length === 1) return parts[0].word
221
+
222
+ const result = []
223
+
224
+ for (let i = 0; i < parts.length; i++) {
225
+ const part = parts[i]
226
+ const isLast = i === parts.length - 1
227
+
228
+ if (isLast && parts.length > 1) {
229
+ const prevPart = parts[i - 1]
230
+ // Insert "och" if previous was scale and this doesn't have hundred
231
+ if (prevPart.isScale && !part.hasHundred) {
232
+ result.push('och')
84
233
  }
85
- return { [`${precedingWord} ${followingWord}`]: precedingValue * followingValue }
86
234
  }
87
235
 
88
- return { [`${precedingWord} ${followingWord}`]: precedingValue + followingValue }
236
+ result.push(part.word)
89
237
  }
238
+
239
+ return result.join(' ')
90
240
  }
241
+
242
+ /**
243
+ * Converts decimal digits to Swedish words.
244
+ *
245
+ * @param {string} decimalPart - Decimal digits (without the point)
246
+ * @returns {string} Swedish words for decimal part
247
+ */
248
+ function decimalPartToWords (decimalPart) {
249
+ let result = ''
250
+
251
+ // Handle leading zeros
252
+ let i = 0
253
+ while (i < decimalPart.length && decimalPart[i] === '0') {
254
+ if (result) result += ' '
255
+ result += ZERO
256
+ i++
257
+ }
258
+
259
+ // Convert remainder as a single number
260
+ const remainder = decimalPart.slice(i)
261
+ if (remainder) {
262
+ if (result) result += ' '
263
+ result += integerToWords(BigInt(remainder))
264
+ }
265
+
266
+ return result
267
+ }
268
+
269
+ /**
270
+ * Converts a numeric value to Swedish words.
271
+ *
272
+ * @param {number | string | bigint} value - The numeric value to convert
273
+ * @returns {string} The number in Swedish words
274
+ * @throws {TypeError} If value is not a valid numeric type
275
+ * @throws {Error} If value is not a valid number format
276
+ *
277
+ * @example
278
+ * toWords(42) // 'fyrtio-två'
279
+ * toWords(101) // 'hundra och ett'
280
+ * toWords(1000000) // 'en miljon'
281
+ */
282
+ function toWords (value) {
283
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
284
+
285
+ let result = ''
286
+
287
+ if (isNegative) {
288
+ result = NEGATIVE + ' '
289
+ }
290
+
291
+ result += integerToWords(integerPart)
292
+
293
+ if (decimalPart) {
294
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
295
+ }
296
+
297
+ return result
298
+ }
299
+
300
+ // ============================================================================
301
+ // Public API
302
+ // ============================================================================
303
+
304
+ export { toWords }
@@ -1,39 +1,7 @@
1
1
  /**
2
- * Swahili language converter.
2
+ * Converts a numeric value to Swahili words.
3
3
  *
4
- * Supports:
5
- * - Swahili number words (moja, mbili, tatu)
6
- * - Space-separated components
7
- * - Simple decimal point notation
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Swahili words
8
6
  */
9
- export class Swahili extends AbstractLanguage {
10
- onesWords: {
11
- 0: string;
12
- 1: string;
13
- 2: string;
14
- 3: string;
15
- 4: string;
16
- 5: string;
17
- 6: string;
18
- 7: string;
19
- 8: string;
20
- 9: string;
21
- };
22
- tensWords: {
23
- 10: string;
24
- 20: string;
25
- 30: string;
26
- 40: string;
27
- 50: string;
28
- 60: string;
29
- 70: string;
30
- 80: string;
31
- 90: string;
32
- };
33
- scaleWords: string[];
34
- wordsUnder100(n: any): any;
35
- wordsUnder1000(n: any): any;
36
- splitBy3(number: any): number[];
37
- integerToWords(integerPart: any): string;
38
- }
39
- import { AbstractLanguage } from '../classes/abstract-language.js';
7
+ export function toWords(value: number | string | bigint): string;
@@ -1,126 +1,153 @@
1
- import { AbstractLanguage } from '../classes/abstract-language.js'
2
-
3
1
  /**
4
- * Swahili language converter.
2
+ * Swahili language converter - Functional Implementation
3
+ *
4
+ * Self-contained converter with precomputed lookup tables.
5
5
  *
6
- * Supports:
7
- * - Swahili number words (moja, mbili, tatu)
8
- * - Space-separated components
9
- * - Simple decimal point notation
6
+ * Key features:
7
+ * - "na" connector for compound numbers
8
+ * - Reversed hundreds: "mia moja" (one hundred)
9
+ * - Scale words: elfu, milioni, bilioni
10
10
  */
11
- export class Swahili extends AbstractLanguage {
12
- negativeWord = 'minus'
13
- decimalSeparatorWord = 'nukta'
14
- zeroWord = 'sifuri'
15
-
16
- onesWords = {
17
- 0: 'sifuri',
18
- 1: 'moja',
19
- 2: 'mbili',
20
- 3: 'tatu',
21
- 4: 'nne',
22
- 5: 'tano',
23
- 6: 'sita',
24
- 7: 'saba',
25
- 8: 'nane',
26
- 9: 'tisa'
27
- }
28
11
 
29
- tensWords = {
30
- 10: 'kumi',
31
- 20: 'ishirini',
32
- 30: 'thelathini',
33
- 40: 'arobaini',
34
- 50: 'hamsini',
35
- 60: 'sitini',
36
- 70: 'sabini',
37
- 80: 'themanini',
38
- 90: 'tisini'
12
+ import { parseNumericValue } from '../utils/parse-numeric.js'
13
+
14
+ // ============================================================================
15
+ // Vocabulary
16
+ // ============================================================================
17
+
18
+ const ONES = ['sifuri', 'moja', 'mbili', 'tatu', 'nne', 'tano', 'sita', 'saba', 'nane', 'tisa']
19
+ const TENS = { 10: 'kumi', 20: 'ishirini', 30: 'thelathini', 40: 'arobaini', 50: 'hamsini', 60: 'sitini', 70: 'sabini', 80: 'themanini', 90: 'tisini' }
20
+
21
+ const SCALE_WORDS = ['', 'elfu', 'milioni', 'bilioni', 'trilioni', 'kwadrilioni', 'kwintilioni']
22
+
23
+ const ZERO = 'sifuri'
24
+ const NEGATIVE = 'minus'
25
+ const DECIMAL_SEP = 'nukta'
26
+
27
+ // ============================================================================
28
+ // Conversion Functions
29
+ // ============================================================================
30
+
31
+ function wordsUnder100 (n) {
32
+ if (n < 10) return ONES[n]
33
+ if (n === 10) return TENS[10]
34
+ if (n < 20) {
35
+ // 11-19: 'kumi na <digit>'
36
+ return TENS[10] + ' na ' + ONES[n - 10]
39
37
  }
38
+ const tens = Math.trunc(n / 10) * 10
39
+ const ones = n % 10
40
+ if (ones === 0) return TENS[tens]
41
+ return TENS[tens] + ' na ' + ONES[ones]
42
+ }
40
43
 
41
- scaleWords = [
42
- '',
43
- 'elfu',
44
- 'milioni',
45
- 'bilioni',
46
- 'trilioni'
47
- ]
48
-
49
- wordsUnder100 (n) {
50
- if (n < 10) return this.onesWords[n]
51
- if (n === 10) return this.tensWords[10]
52
- if (n < 20) {
53
- // 11-19: 'kumi na <digit>'
54
- return this.tensWords[10] + ' na ' + this.onesWords[n - 10]
44
+ function wordsUnder1000 (n) {
45
+ if (n < 100) return wordsUnder100(n)
46
+ if (n === 100) return 'mia moja'
47
+ const hundreds = Math.trunc(n / 100)
48
+ const rest = n % 100
49
+ const parts = []
50
+
51
+ // Hundreds: 'mia <digit>'
52
+ parts.push('mia ' + ONES[hundreds])
53
+ if (rest > 0) {
54
+ if (rest < 10) {
55
+ parts.push('na ' + ONES[rest])
56
+ } else {
57
+ parts.push(wordsUnder100(rest))
55
58
  }
56
- const tens = Math.trunc(n / 10) * 10
57
- const ones = n % 10
58
- if (ones === 0) return this.tensWords[tens]
59
- return this.tensWords[tens] + ' na ' + this.onesWords[ones]
60
59
  }
61
60
 
62
- wordsUnder1000 (n) {
63
- if (n < 100) return this.wordsUnder100(n)
64
- if (n === 100) return 'mia moja'
65
- const hundreds = Math.trunc(n / 100)
66
- const rest = n % 100
67
- const parts = []
68
-
69
- // Hundreds: 'mia <digit>'
70
- parts.push('mia ' + this.onesWords[hundreds])
71
- if (rest > 0) {
72
- if (rest < 10) {
73
- parts.push('na ' + this.onesWords[rest])
61
+ return parts.join(' ')
62
+ }
63
+
64
+ function extractSegments (n) {
65
+ const segments = []
66
+ let temp = n
67
+ while (temp > 0n) {
68
+ segments.push(Number(temp % 1000n))
69
+ temp = temp / 1000n
70
+ }
71
+ return segments
72
+ }
73
+
74
+ function integerToWords (n) {
75
+ if (n === 0n) return ZERO
76
+
77
+ // segments stored least-significant first: [ones, thousands, millions, ...]
78
+ const segments = extractSegments(n)
79
+ const parts = []
80
+
81
+ // Iterate from highest scale to lowest
82
+ for (let scaleIndex = segments.length - 1; scaleIndex >= 0; scaleIndex--) {
83
+ const val = segments[scaleIndex]
84
+ if (val === 0) continue
85
+
86
+ if (scaleIndex === 0) {
87
+ // Units segment
88
+ if (val < 10 && parts.length > 0) {
89
+ parts.push('na ' + ONES[val])
90
+ } else if (val === 100 && parts.length > 0) {
91
+ // In compound numbers (e.g., 1100 -> 'elfu moja mia'), use 'mia' not 'mia moja'
92
+ parts.push('mia')
74
93
  } else {
75
- parts.push(this.wordsUnder100(rest))
94
+ parts.push(wordsUnder1000(val))
76
95
  }
96
+ } else {
97
+ // Scale segments: 'elfu moja', 'milioni mbili'
98
+ const unit = (val === 1) ? 'moja' : wordsUnder1000(val)
99
+ parts.push(SCALE_WORDS[scaleIndex] + ' ' + unit)
77
100
  }
101
+ }
78
102
 
79
- return parts.join(' ')
103
+ return parts.join(' ').trim()
104
+ }
105
+
106
+ function decimalPartToWords (decimalPart) {
107
+ let result = ''
108
+ let i = 0
109
+
110
+ while (i < decimalPart.length && decimalPart[i] === '0') {
111
+ if (result) result += ' '
112
+ result += ZERO
113
+ i++
80
114
  }
81
115
 
82
- splitBy3 (number) {
83
- const s = number.toString()
84
- if (s.length <= 3) return [Number(s)]
85
- const groups = []
86
- const last3 = s.slice(-3)
87
- groups.unshift(Number(last3))
88
- let remaining = s.slice(0, -3)
89
- while (remaining.length > 0) {
90
- const group = remaining.slice(-3)
91
- groups.unshift(Number(group))
92
- remaining = remaining.slice(0, -3)
93
- }
94
- return groups
116
+ const remainder = decimalPart.slice(i)
117
+ if (remainder) {
118
+ if (result) result += ' '
119
+ result += integerToWords(BigInt(remainder))
95
120
  }
96
121
 
97
- integerToWords (integerPart) {
98
- if (integerPart === 0n) return this.zeroWord
99
-
100
- const groups = this.splitBy3(integerPart)
101
- const parts = []
102
-
103
- for (let i = 0; i < groups.length; i++) {
104
- const val = groups[i]
105
- if (val === 0) continue
106
- const scaleIndex = groups.length - i - 1
107
- // scale word
108
- if (scaleIndex === 0) {
109
- if (val < 10 && parts.length > 0) {
110
- parts.push('na ' + this.onesWords[val])
111
- } else if (val === 100 && parts.length > 0) {
112
- // In compound numbers (e.g., 1100 -> 'elfu moja mia'), use 'mia' not 'mia moja'
113
- parts.push('mia')
114
- } else {
115
- parts.push(this.wordsUnder1000(val))
116
- }
117
- } else {
118
- // e.g., 'elfu moja', 'milioni mbili'
119
- const unit = (val === 1) ? 'moja' : this.wordsUnder1000(val)
120
- parts.push(this.scaleWords[scaleIndex] + ' ' + unit)
121
- }
122
- }
122
+ return result
123
+ }
123
124
 
124
- return parts.join(' ').trim()
125
+ /**
126
+ * Converts a numeric value to Swahili words.
127
+ *
128
+ * @param {number | string | bigint} value - The numeric value to convert
129
+ * @returns {string} The number in Swahili words
130
+ */
131
+ function toWords (value) {
132
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
133
+
134
+ let result = ''
135
+
136
+ if (isNegative) {
137
+ result = NEGATIVE + ' '
125
138
  }
139
+
140
+ result += integerToWords(integerPart)
141
+
142
+ if (decimalPart) {
143
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
144
+ }
145
+
146
+ return result
126
147
  }
148
+
149
+ // ============================================================================
150
+ // Exports
151
+ // ============================================================================
152
+
153
+ export { toWords }