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,121 +1,222 @@
1
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js'
2
-
3
1
  /**
4
- * Filipino language converter.
2
+ * Filipino language converter - Functional Implementation
3
+ *
4
+ * Self-contained converter with precomputed lookup tables.
5
5
  *
6
- * Supports:
7
- * - "ng" connectors between words
8
- * - Implicit "one" omission
9
- * - Special linkers for certain tens (e.g., "limampung")
6
+ * Key features:
7
+ * - Linker "ng" after vowels: "isang daang" (100)
8
+ * - Linker " na " after consonants: "siyam na daang" (900)
9
+ * - Special tens with linker: "limampung anim" (56)
10
+ * - Per-digit decimal reading
10
11
  */
11
- export class Filipino extends GreedyScaleLanguage {
12
- negativeWord = 'negatibo'
13
- decimalSeparatorWord = 'punto'
14
- zeroWord = 'zero'
15
- usePerDigitDecimals = true // Read decimals digit-by-digit
16
-
17
- scaleWords = [
18
- [1000000000000n, 'trilyong'],
19
- [1000000000n, 'milyong'],
20
- [1000000n, 'milyong'],
21
- [1000n, 'libong'],
22
- [100n, 'daang'],
23
-
24
- // Tens
25
- [90n, 'siyamnapu'],
26
- [80n, 'walumpu'],
27
- [70n, 'pitumpu'],
28
- [60n, 'animnapu'],
29
- [50n, 'limampu'],
30
- [40n, 'apatnapu'],
31
- [30n, 'tatlumpu'],
32
- [20n, 'dalawampu'],
33
-
34
- // Teens (must come before 10 to be matched first)
35
- [19n, 'labinsiyam'],
36
- [18n, 'labingwalo'],
37
- [17n, 'labimpito'],
38
- [16n, 'labinanum'],
39
- [15n, 'labinlima'],
40
- [14n, 'labinapat'],
41
- [13n, 'labintatlo'],
42
- [12n, 'labindalawa'],
43
- [11n, 'labinisa'],
44
- [10n, 'sampu'],
45
-
46
- // Ones
47
- [9n, 'siyam'],
48
- [8n, 'walo'],
49
- [7n, 'pito'],
50
- [6n, 'anim'],
51
- [5n, 'lima'],
52
- [4n, 'apat'],
53
- [3n, 'tatlo'],
54
- [2n, 'dalawa'],
55
- [1n, 'isa'],
56
- [0n, 'zero']
57
- ]
58
-
59
- /** Converts integer part with explicit zero handling. */
60
- integerToWords (integerPart) {
61
- // Handle zero explicitly
62
- if (integerPart === 0n) {
63
- return this.zeroWord
64
- }
65
- return super.integerToWords(integerPart)
12
+
13
+ import { parseNumericValue } from '../utils/parse-numeric.js'
14
+
15
+ // ============================================================================
16
+ // Vocabulary
17
+ // ============================================================================
18
+
19
+ const ONES = ['', 'isa', 'dalawa', 'tatlo', 'apat', 'lima', 'anim', 'pito', 'walo', 'siyam']
20
+ const TEENS = ['sampu', 'labinisa', 'labindalawa', 'labintatlo', 'labinapat', 'labinlima', 'labinanum', 'labimpito', 'labingwalo', 'labinsiyam']
21
+ const TENS = ['', '', 'dalawampu', 'tatlumpu', 'apatnapu', 'limampu', 'animnapu', 'pitumpu', 'walumpu', 'siyamnapu']
22
+
23
+ // Scale words include linker (end with "ng")
24
+ const HUNDRED = 'daang'
25
+ const THOUSAND = 'libong'
26
+
27
+ const ZERO = 'zero'
28
+ const NEGATIVE = 'negatibo'
29
+ const DECIMAL_SEP = 'punto'
30
+
31
+ // Short scale with linker
32
+ const SCALE_WORDS = ['', THOUSAND, 'milyong', 'bilyong', 'trilyong']
33
+
34
+ // ============================================================================
35
+ // Helper Functions
36
+ // ============================================================================
37
+
38
+ const VOWELS = ['a', 'e', 'i', 'o', 'u']
39
+
40
+ function addLinker (word) {
41
+ const lastChar = word[word.length - 1]
42
+ if (VOWELS.includes(lastChar)) {
43
+ return word + 'ng'
66
44
  }
45
+ return word + ' na'
46
+ }
67
47
 
68
- /** Combines two word-sets with Filipino connector and linker rules. */
69
- combineWordSets (preceding, following) {
70
- const precedingWord = Object.keys(preceding)[0]
71
- const followingWord = Object.keys(following)[0]
72
- const precedingValue = Object.values(preceding)[0]
73
- const followingValue = Object.values(following)[0]
48
+ // ============================================================================
49
+ // Precomputed Lookup Table
50
+ // ============================================================================
74
51
 
75
- // Don't merge zero with anything - just return the non-zero value
76
- if (precedingValue === 0n) {
77
- return following
78
- }
79
- if (followingValue === 0n) {
80
- return preceding
81
- }
52
+ function buildSegment (n) {
53
+ if (n === 0) return ''
82
54
 
83
- // Implicit "one" - omit when adding with values < 100
84
- if (precedingValue === 1n && followingValue < 100n) {
85
- return following
86
- }
55
+ const ones = n % 10
56
+ const tensDigit = Math.floor(n / 10) % 10
57
+ const hundredsDigit = Math.floor(n / 100)
58
+
59
+ const parts = []
87
60
 
88
- // Multiply when following is a scale word AND following > preceding
89
- // Use "ng" connector for Filipino, but consonant-ending words use " na "
90
- if (followingValue > precedingValue && followingValue >= 100n) {
91
- // Words ending in consonants (not vowels) use " na " instead of "ng"
92
- const vowels = ['a', 'e', 'i', 'o', 'u']
93
- const lastChar = precedingWord[precedingWord.length - 1]
94
- if (!vowels.includes(lastChar)) {
95
- return {
96
- [`${precedingWord} na ${followingWord}`]: precedingValue * followingValue
97
- }
98
- }
99
- // Vowel-ending words add "ng"
100
- return {
101
- [`${precedingWord}ng ${followingWord}`]: precedingValue * followingValue
102
- }
61
+ // Hundreds: "isang daan", "dalawang daan", "siyam na daan"
62
+ if (hundredsDigit > 0) {
63
+ const hundredPrefix = addLinker(ONES[hundredsDigit])
64
+ parts.push(hundredPrefix + ' ' + HUNDRED)
65
+ }
66
+
67
+ // Tens and ones
68
+ const tensOnes = n % 100
69
+
70
+ if (tensOnes === 0) {
71
+ // Just hundreds
72
+ } else if (tensOnes < 10) {
73
+ // Single digit
74
+ parts.push(ONES[ones])
75
+ } else if (tensOnes < 20) {
76
+ // Teens (10-19)
77
+ parts.push(TEENS[ones])
78
+ } else if (ones === 0) {
79
+ // Even tens (20, 30, 40, etc.)
80
+ parts.push(TENS[tensDigit])
81
+ } else {
82
+ // Tens + ones
83
+ // limampu (50) gets special linker: "limampung anim" (56)
84
+ if (tensDigit === 5) {
85
+ parts.push(TENS[tensDigit] + 'ng ' + ONES[ones])
86
+ } else {
87
+ parts.push(TENS[tensDigit] + ' ' + ONES[ones])
103
88
  }
89
+ }
104
90
 
105
- // Special Filipino rule: certain tens words take "-ng" linker when followed by ones
106
- // Only limampu (50) confirmed to use this pattern
107
- if (precedingValue >= 10n && precedingValue < 100n && followingValue >= 1n && followingValue < 10n) {
108
- const tensWithNg = ['limampu']
109
- if (tensWithNg.includes(precedingWord)) {
110
- return {
111
- [`${precedingWord}ng ${followingWord}`]: precedingValue + followingValue
112
- }
113
- }
91
+ return parts.join(' ')
92
+ }
93
+
94
+ const SEGMENTS = new Array(1000)
95
+ for (let i = 0; i < 1000; i++) {
96
+ SEGMENTS[i] = buildSegment(i)
97
+ }
98
+
99
+ /**
100
+ * Builds segment with linker added to last word (for use before scale words).
101
+ */
102
+ function buildSegmentWithLinker (n) {
103
+ const segmentWord = SEGMENTS[n]
104
+ if (!segmentWord) return ''
105
+
106
+ // Find the last space to get the last word
107
+ const lastSpaceIdx = segmentWord.lastIndexOf(' ')
108
+ if (lastSpaceIdx === -1) {
109
+ // Single word
110
+ const lastChar = segmentWord[segmentWord.length - 1]
111
+ if (lastChar === 'g' && segmentWord.endsWith('ng')) {
112
+ return segmentWord // Already has linker
114
113
  }
114
+ return addLinker(segmentWord)
115
+ }
116
+
117
+ // Multi-word: add linker to last word
118
+ const prefix = segmentWord.slice(0, lastSpaceIdx + 1)
119
+ const lastWord = segmentWord.slice(lastSpaceIdx + 1)
115
120
 
116
- // Default: space for addition
117
- return {
118
- [`${precedingWord} ${followingWord}`]: precedingValue + followingValue
121
+ if (lastWord.endsWith('ng')) {
122
+ return segmentWord // Already has linker
123
+ }
124
+ return prefix + addLinker(lastWord)
125
+ }
126
+
127
+ // Precompute segments with linker for scale word usage
128
+ const SEGMENTS_WITH_LINKER = new Array(1000)
129
+ for (let i = 0; i < 1000; i++) {
130
+ SEGMENTS_WITH_LINKER[i] = buildSegmentWithLinker(i)
131
+ }
132
+
133
+ // ============================================================================
134
+ // Conversion Functions
135
+ // ============================================================================
136
+
137
+ function integerToWords (n) {
138
+ if (n === 0n) return ZERO
139
+
140
+ if (n < 1000n) {
141
+ return SEGMENTS[Number(n)]
142
+ }
143
+
144
+ return buildLargeNumberWords(n)
145
+ }
146
+
147
+ /**
148
+ * Builds words for large numbers using BigInt division.
149
+ * @param {bigint} n - Number >= 1000
150
+ * @returns {string} Filipino words
151
+ */
152
+ function buildLargeNumberWords (n) {
153
+ // Extract segments using BigInt division (faster than string slicing)
154
+ const segmentValues = []
155
+ let temp = n
156
+ while (temp > 0n) {
157
+ segmentValues.push(Number(temp % 1000n))
158
+ temp = temp / 1000n
159
+ }
160
+
161
+ // Build result string directly
162
+ let result = ''
163
+
164
+ for (let i = segmentValues.length - 1; i >= 0; i--) {
165
+ const segment = segmentValues[i]
166
+ if (segment === 0) continue
167
+
168
+ const scaleWord = SCALE_WORDS[i] || ''
169
+
170
+ if (result) result += ' '
171
+
172
+ if (i === 0) {
173
+ result += SEGMENTS[segment]
174
+ } else {
175
+ // Add linker to segment before scale word
176
+ const segmentWord = SEGMENTS_WITH_LINKER[segment]
177
+ result += segmentWord + ' ' + scaleWord
119
178
  }
120
179
  }
180
+
181
+ return result
121
182
  }
183
+
184
+ function decimalPartToWords (decimalPart) {
185
+ // Per-digit decimal reading
186
+ const digits = []
187
+ for (const char of decimalPart) {
188
+ const d = parseInt(char, 10)
189
+ digits.push(d === 0 ? ZERO : ONES[d])
190
+ }
191
+ return digits.join(' ')
192
+ }
193
+
194
+ /**
195
+ * Converts a numeric value to Filipino words.
196
+ *
197
+ * @param {number | string | bigint} value - The numeric value to convert
198
+ * @returns {string} The number in Filipino words
199
+ */
200
+ function toWords (value) {
201
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
202
+
203
+ let result = ''
204
+
205
+ if (isNegative) {
206
+ result = NEGATIVE + ' '
207
+ }
208
+
209
+ result += integerToWords(integerPart)
210
+
211
+ if (decimalPart) {
212
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
213
+ }
214
+
215
+ return result
216
+ }
217
+
218
+ // ============================================================================
219
+ // Exports
220
+ // ============================================================================
221
+
222
+ export { toWords }
@@ -1,11 +1,11 @@
1
1
  /**
2
- * French (Belgium) language converter.
2
+ * Converts a numeric value to Belgian French words.
3
3
  *
4
- * Supports:
5
- * - Belgian regional variants: "septante" (70) and "nonante" (90)
6
- * - Simplified tens naming (no complex arithmetic)
7
- * - Inherits all other French grammar rules
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @param {Object} [options] - Optional configuration
6
+ * @param {boolean} [options.withHyphenSeparator=false] - Use hyphens between words
7
+ * @returns {string} The number in Belgian French words
8
8
  */
9
- export class FrenchBelgium extends French {
10
- }
11
- import { French } from './fr.js';
9
+ export function toWords(value: number | string | bigint, options?: {
10
+ withHyphenSeparator?: boolean | undefined;
11
+ }): string;
@@ -1,25 +1,300 @@
1
- import { French } from './fr.js'
1
+ /**
2
+ * French (Belgium) language converter - Functional Implementation
3
+ *
4
+ * Self-contained converter with precomputed lookup tables.
5
+ *
6
+ * Belgian French differences from standard French:
7
+ * - septante (70) instead of soixante-dix
8
+ * - nonante (90) instead of quatre-vingt-dix
9
+ * - Keeps quatre-vingts (80) like standard French
10
+ * - Uses "septante et un" (71), "nonante et un" (91)
11
+ */
12
+
13
+ import { parseNumericValue } from '../utils/parse-numeric.js'
14
+ import { validateOptions } from '../utils/validate-options.js'
15
+
16
+ // ============================================================================
17
+ // Vocabulary
18
+ // ============================================================================
19
+
20
+ const ONES = ['', 'un', 'deux', 'trois', 'quatre', 'cinq', 'six', 'sept', 'huit', 'neuf']
21
+ const TEENS = ['dix', 'onze', 'douze', 'treize', 'quatorze', 'quinze', 'seize', 'dix-sept', 'dix-huit', 'dix-neuf']
22
+ const TENS = ['', '', 'vingt', 'trente', 'quarante', 'cinquante', 'soixante', 'septante', 'quatre-vingt', 'nonante']
23
+
24
+ // Scale words (long scale with -ard forms)
25
+ const SCALES = ['million', 'billion', 'trillion', 'quadrillion']
26
+ const SCALES_ARD = ['milliard', 'billiard', 'trilliard', 'quadrilliard']
27
+
28
+ const THOUSAND = 'mille'
29
+ const HUNDRED = 'cent'
30
+ const ZERO = 'zéro'
31
+ const NEGATIVE = 'moins'
32
+ const DECIMAL_SEP = 'virgule'
33
+
34
+ // ============================================================================
35
+ // Precomputed Lookup Tables
36
+ // ============================================================================
37
+
38
+ function buildSegment (n) {
39
+ if (n === 0) return { word: '', endsWithCents: false, endsWithVingts: false }
40
+
41
+ const tensOnes = n % 100
42
+ const hundreds = Math.floor(n / 100)
43
+
44
+ const parts = []
45
+ let endsWithCents = false
46
+ let endsWithVingts = false
47
+
48
+ // Hundreds
49
+ if (hundreds > 0) {
50
+ if (hundreds === 1) {
51
+ parts.push(HUNDRED)
52
+ } else {
53
+ if (tensOnes === 0) {
54
+ parts.push(ONES[hundreds] + ' ' + HUNDRED + 's')
55
+ endsWithCents = true
56
+ } else {
57
+ parts.push(ONES[hundreds] + ' ' + HUNDRED)
58
+ }
59
+ }
60
+ }
61
+
62
+ // Tens and ones - Belgian pattern
63
+ if (tensOnes === 0) {
64
+ // Just hundreds
65
+ } else if (tensOnes < 10) {
66
+ parts.push(ONES[tensOnes])
67
+ } else if (tensOnes < 17) {
68
+ parts.push(TEENS[tensOnes - 10])
69
+ } else if (tensOnes < 20) {
70
+ parts.push(TEENS[tensOnes - 10])
71
+ } else if (tensOnes < 70) {
72
+ // 20-69: standard pattern
73
+ const t = Math.floor(tensOnes / 10)
74
+ const o = tensOnes % 10
75
+ if (o === 0) {
76
+ parts.push(TENS[t])
77
+ } else if (o === 1) {
78
+ parts.push(TENS[t] + ' et ' + ONES[1])
79
+ } else {
80
+ parts.push(TENS[t] + '-' + ONES[o])
81
+ }
82
+ } else if (tensOnes < 80) {
83
+ // 70-79: septante pattern (Belgian)
84
+ const o = tensOnes % 10
85
+ if (o === 0) {
86
+ parts.push('septante')
87
+ } else if (o === 1) {
88
+ parts.push('septante et ' + ONES[1])
89
+ } else {
90
+ parts.push('septante-' + ONES[o])
91
+ }
92
+ } else if (tensOnes === 80) {
93
+ // 80: quatre-vingts (same as standard)
94
+ parts.push('quatre-vingts')
95
+ endsWithVingts = true
96
+ } else if (tensOnes < 90) {
97
+ // 81-89: quatre-vingt-X (same as standard)
98
+ const remainder = tensOnes - 80
99
+ parts.push('quatre-vingt-' + ONES[remainder])
100
+ } else {
101
+ // 90-99: nonante pattern (Belgian)
102
+ const o = tensOnes % 10
103
+ if (o === 0) {
104
+ parts.push('nonante')
105
+ } else if (o === 1) {
106
+ parts.push('nonante et ' + ONES[1])
107
+ } else {
108
+ parts.push('nonante-' + ONES[o])
109
+ }
110
+ }
111
+
112
+ return { word: parts.join(' '), endsWithCents, endsWithVingts }
113
+ }
114
+
115
+ // Precompute all 1000 segment words
116
+ const SEGMENTS = new Array(1000)
117
+ const SEGMENTS_ENDS_CENTS = new Array(1000)
118
+ const SEGMENTS_ENDS_VINGTS = new Array(1000)
119
+
120
+ for (let i = 0; i < 1000; i++) {
121
+ const result = buildSegment(i)
122
+ SEGMENTS[i] = result.word
123
+ SEGMENTS_ENDS_CENTS[i] = result.endsWithCents
124
+ SEGMENTS_ENDS_VINGTS[i] = result.endsWithVingts
125
+ }
126
+
127
+ // ============================================================================
128
+ // Helper Functions
129
+ // ============================================================================
130
+
131
+ function getScaleWord (scaleIndex, segment) {
132
+ if (scaleIndex === 1) return THOUSAND
133
+
134
+ if (scaleIndex % 2 === 0) {
135
+ const arrayIndex = (scaleIndex / 2) - 1
136
+ const baseWord = SCALES[arrayIndex]
137
+ if (!baseWord) return ''
138
+ return segment > 1n ? baseWord + 's' : baseWord
139
+ } else {
140
+ const arrayIndex = ((scaleIndex - 1) / 2) - 1
141
+ const ardWord = SCALES_ARD[arrayIndex]
142
+ if (!ardWord) return THOUSAND
143
+ return segment > 1n ? ardWord + 's' : ardWord
144
+ }
145
+ }
146
+
147
+ // ============================================================================
148
+ // Conversion Functions
149
+ // ============================================================================
150
+
151
+ function integerToWords (n, withHyphen = false) {
152
+ if (n === 0n) return ZERO
153
+
154
+ if (n < 1000n) {
155
+ const word = SEGMENTS[Number(n)]
156
+ return withHyphen ? word.replace(/ /g, '-') : word
157
+ }
158
+
159
+ if (n < 1_000_000n) {
160
+ const thousands = Number(n / 1000n)
161
+ const remainder = Number(n % 1000n)
162
+
163
+ let result
164
+ if (thousands === 1) {
165
+ result = THOUSAND
166
+ } else {
167
+ let thousandsWord = SEGMENTS[thousands]
168
+ if (SEGMENTS_ENDS_CENTS[thousands] || SEGMENTS_ENDS_VINGTS[thousands]) {
169
+ thousandsWord = thousandsWord.slice(0, -1)
170
+ }
171
+ result = thousandsWord + (withHyphen ? '-' : ' ') + THOUSAND
172
+ }
173
+
174
+ if (remainder > 0) {
175
+ result += (withHyphen ? '-' : ' ') + SEGMENTS[remainder]
176
+ }
177
+
178
+ if (withHyphen) {
179
+ result = result.replace(/ /g, '-')
180
+ }
181
+
182
+ return result
183
+ }
184
+
185
+ return buildLargeNumberWords(n, withHyphen)
186
+ }
187
+
188
+ function buildLargeNumberWords (n, withHyphen) {
189
+ const numStr = n.toString()
190
+ const len = numStr.length
191
+
192
+ const segments = []
193
+ const segmentSize = 3
194
+
195
+ const remainderLen = len % segmentSize
196
+ let pos = 0
197
+ if (remainderLen > 0) {
198
+ segments.push(Number(numStr.slice(0, remainderLen)))
199
+ pos = remainderLen
200
+ }
201
+ while (pos < len) {
202
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
203
+ pos += segmentSize
204
+ }
205
+
206
+ const parts = []
207
+ let scaleIndex = segments.length - 1
208
+
209
+ for (let i = 0; i < segments.length; i++) {
210
+ const segment = segments[i]
211
+
212
+ if (segment !== 0) {
213
+ const scaleWord = scaleIndex > 0 ? getScaleWord(scaleIndex, BigInt(segment)) : ''
214
+
215
+ if (scaleIndex === 0) {
216
+ parts.push(SEGMENTS[segment])
217
+ } else if (scaleIndex === 1) {
218
+ if (segment === 1) {
219
+ parts.push(THOUSAND)
220
+ } else {
221
+ let segWords = SEGMENTS[segment]
222
+ if (SEGMENTS_ENDS_CENTS[segment] || SEGMENTS_ENDS_VINGTS[segment]) {
223
+ segWords = segWords.slice(0, -1)
224
+ }
225
+ parts.push(segWords)
226
+ parts.push(scaleWord)
227
+ }
228
+ } else {
229
+ parts.push(SEGMENTS[segment])
230
+ parts.push(scaleWord)
231
+ }
232
+ }
233
+
234
+ scaleIndex--
235
+ }
236
+
237
+ const sep = withHyphen ? '-' : ' '
238
+ let result = parts.join(sep)
239
+
240
+ if (withHyphen) {
241
+ result = result.replace(/ /g, '-')
242
+ }
243
+
244
+ return result
245
+ }
246
+
247
+ function decimalPartToWords (decimalPart, withHyphen) {
248
+ let result = ''
249
+ const sep = withHyphen ? '-' : ' '
250
+
251
+ let i = 0
252
+ while (i < decimalPart.length && decimalPart[i] === '0') {
253
+ if (result) result += sep
254
+ result += ZERO
255
+ i++
256
+ }
257
+
258
+ const remainder = decimalPart.slice(i)
259
+ if (remainder) {
260
+ if (result) result += sep
261
+ result += integerToWords(BigInt(remainder), withHyphen)
262
+ }
263
+
264
+ return result
265
+ }
2
266
 
3
267
  /**
4
- * French (Belgium) language converter.
268
+ * Converts a numeric value to Belgian French words.
5
269
  *
6
- * Supports:
7
- * - Belgian regional variants: "septante" (70) and "nonante" (90)
8
- * - Simplified tens naming (no complex arithmetic)
9
- * - Inherits all other French grammar rules
270
+ * @param {number | string | bigint} value - The numeric value to convert
271
+ * @param {Object} [options] - Optional configuration
272
+ * @param {boolean} [options.withHyphenSeparator=false] - Use hyphens between words
273
+ * @returns {string} The number in Belgian French words
10
274
  */
11
- export class FrenchBelgium extends French {
12
- constructor (options = {}) {
13
- super(options)
14
-
15
- // Insert 90n ('nonante') after 80n and 70n ('septante') after 60n
16
- const tuples = [...this.scaleWords]
17
- // Find index of 80n and insert 90n after it
18
- const idx80 = tuples.findIndex(tuple => tuple[0] === 80n)
19
- if (idx80 !== -1) tuples.splice(idx80, 0, [90n, 'nonante'])
20
- // Find index of 60n and insert 70n after it
21
- const idx60 = tuples.findIndex(tuple => tuple[0] === 60n)
22
- if (idx60 !== -1) tuples.splice(idx60, 0, [70n, 'septante'])
23
- this.scaleWords = tuples
275
+ function toWords (value, options) {
276
+ options = validateOptions(options)
277
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
278
+ const withHyphen = options.withHyphenSeparator || false
279
+
280
+ let result = ''
281
+ const sep = withHyphen ? '-' : ' '
282
+
283
+ if (isNegative) {
284
+ result = NEGATIVE + sep
285
+ }
286
+
287
+ result += integerToWords(integerPart, withHyphen)
288
+
289
+ if (decimalPart) {
290
+ result += sep + DECIMAL_SEP + sep + decimalPartToWords(decimalPart, withHyphen)
24
291
  }
292
+
293
+ return result
25
294
  }
295
+
296
+ // ============================================================================
297
+ // Exports
298
+ // ============================================================================
299
+
300
+ export { toWords }