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,121 +1,211 @@
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 module with its own input validation, ready for subpath exports.
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
+ // Segment Building
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)
87
58
 
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
- }
59
+ const parts = []
60
+
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
+ }
90
+
91
+ return parts.join(' ')
92
+ }
93
+
94
+ /**
95
+ * Builds segment with linker added to last word (for use before scale words).
96
+ */
97
+ function buildSegmentWithLinker (n) {
98
+ const segmentWord = buildSegment(n)
99
+ if (!segmentWord) return ''
104
100
 
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
- }
101
+ // Find the last space to get the last word
102
+ const lastSpaceIdx = segmentWord.lastIndexOf(' ')
103
+ if (lastSpaceIdx === -1) {
104
+ // Single word
105
+ const lastChar = segmentWord[segmentWord.length - 1]
106
+ if (lastChar === 'g' && segmentWord.endsWith('ng')) {
107
+ return segmentWord // Already has linker
114
108
  }
109
+ return addLinker(segmentWord)
110
+ }
111
+
112
+ // Multi-word: add linker to last word
113
+ const prefix = segmentWord.slice(0, lastSpaceIdx + 1)
114
+ const lastWord = segmentWord.slice(lastSpaceIdx + 1)
115
+
116
+ if (lastWord.endsWith('ng')) {
117
+ return segmentWord // Already has linker
118
+ }
119
+ return prefix + addLinker(lastWord)
120
+ }
121
+
122
+ // ============================================================================
123
+ // Conversion Functions
124
+ // ============================================================================
115
125
 
116
- // Default: space for addition
117
- return {
118
- [`${precedingWord} ${followingWord}`]: precedingValue + followingValue
126
+ function integerToWords (n) {
127
+ if (n === 0n) return ZERO
128
+
129
+ if (n < 1000n) {
130
+ return buildSegment(Number(n))
131
+ }
132
+
133
+ return buildLargeNumberWords(n)
134
+ }
135
+
136
+ /**
137
+ * Builds words for large numbers using BigInt division.
138
+ * @param {bigint} n - Number >= 1000
139
+ * @returns {string} Filipino words
140
+ */
141
+ function buildLargeNumberWords (n) {
142
+ // Extract segments using BigInt division (faster than string slicing)
143
+ const segmentValues = []
144
+ let temp = n
145
+ while (temp > 0n) {
146
+ segmentValues.push(Number(temp % 1000n))
147
+ temp = temp / 1000n
148
+ }
149
+
150
+ // Build result string directly
151
+ let result = ''
152
+
153
+ for (let i = segmentValues.length - 1; i >= 0; i--) {
154
+ const segment = segmentValues[i]
155
+ if (segment === 0) continue
156
+
157
+ const scaleWord = SCALE_WORDS[i] || ''
158
+
159
+ if (result) result += ' '
160
+
161
+ if (i === 0) {
162
+ result += buildSegment(segment)
163
+ } else {
164
+ // Add linker to segment before scale word
165
+ const segmentWord = buildSegmentWithLinker(segment)
166
+ result += segmentWord + ' ' + scaleWord
119
167
  }
120
168
  }
169
+
170
+ return result
121
171
  }
172
+
173
+ function decimalPartToWords (decimalPart) {
174
+ // Per-digit decimal reading
175
+ const digits = []
176
+ for (const char of decimalPart) {
177
+ const d = parseInt(char, 10)
178
+ digits.push(d === 0 ? ZERO : ONES[d])
179
+ }
180
+ return digits.join(' ')
181
+ }
182
+
183
+ /**
184
+ * Converts a numeric value to Filipino words.
185
+ *
186
+ * @param {number | string | bigint} value - The numeric value to convert
187
+ * @returns {string} The number in Filipino words
188
+ */
189
+ function toWords (value) {
190
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
191
+
192
+ let result = ''
193
+
194
+ if (isNegative) {
195
+ result = NEGATIVE + ' '
196
+ }
197
+
198
+ result += integerToWords(integerPart)
199
+
200
+ if (decimalPart) {
201
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
202
+ }
203
+
204
+ return result
205
+ }
206
+
207
+ // ============================================================================
208
+ // Exports
209
+ // ============================================================================
210
+
211
+ 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,291 @@
1
- import { French } from './fr.js'
1
+ /**
2
+ * French (Belgium) language converter - Functional Implementation
3
+ *
4
+ * Self-contained module with its own input validation, ready for subpath exports.
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
+ // Segment Building
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
+ // ============================================================================
116
+ // Helper Functions
117
+ // ============================================================================
118
+
119
+ function getScaleWord (scaleIndex, segment) {
120
+ if (scaleIndex === 1) return THOUSAND
121
+
122
+ if (scaleIndex % 2 === 0) {
123
+ const arrayIndex = (scaleIndex / 2) - 1
124
+ const baseWord = SCALES[arrayIndex]
125
+ if (!baseWord) return ''
126
+ return segment > 1n ? baseWord + 's' : baseWord
127
+ } else {
128
+ const arrayIndex = ((scaleIndex - 1) / 2) - 1
129
+ const ardWord = SCALES_ARD[arrayIndex]
130
+ if (!ardWord) return THOUSAND
131
+ return segment > 1n ? ardWord + 's' : ardWord
132
+ }
133
+ }
134
+
135
+ // ============================================================================
136
+ // Conversion Functions
137
+ // ============================================================================
138
+
139
+ function integerToWords (n, withHyphen = false) {
140
+ if (n === 0n) return ZERO
141
+
142
+ if (n < 1000n) {
143
+ const { word } = buildSegment(Number(n))
144
+ return withHyphen ? word.replace(/ /g, '-') : word
145
+ }
146
+
147
+ if (n < 1_000_000n) {
148
+ const thousands = Number(n / 1000n)
149
+ const remainder = Number(n % 1000n)
150
+
151
+ let result
152
+ if (thousands === 1) {
153
+ result = THOUSAND
154
+ } else {
155
+ const { word: thousandsWord, endsWithCents, endsWithVingts } = buildSegment(thousands)
156
+ let adjustedWord = thousandsWord
157
+ if (endsWithCents || endsWithVingts) {
158
+ adjustedWord = thousandsWord.slice(0, -1)
159
+ }
160
+ result = adjustedWord + (withHyphen ? '-' : ' ') + THOUSAND
161
+ }
162
+
163
+ if (remainder > 0) {
164
+ const { word: remainderWord } = buildSegment(remainder)
165
+ result += (withHyphen ? '-' : ' ') + remainderWord
166
+ }
167
+
168
+ if (withHyphen) {
169
+ result = result.replace(/ /g, '-')
170
+ }
171
+
172
+ return result
173
+ }
174
+
175
+ return buildLargeNumberWords(n, withHyphen)
176
+ }
177
+
178
+ function buildLargeNumberWords (n, withHyphen) {
179
+ const numStr = n.toString()
180
+ const len = numStr.length
181
+
182
+ const segments = []
183
+ const segmentSize = 3
184
+
185
+ const remainderLen = len % segmentSize
186
+ let pos = 0
187
+ if (remainderLen > 0) {
188
+ segments.push(Number(numStr.slice(0, remainderLen)))
189
+ pos = remainderLen
190
+ }
191
+ while (pos < len) {
192
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
193
+ pos += segmentSize
194
+ }
195
+
196
+ const parts = []
197
+ let scaleIndex = segments.length - 1
198
+
199
+ for (let i = 0; i < segments.length; i++) {
200
+ const segment = segments[i]
201
+
202
+ if (segment !== 0) {
203
+ const scaleWord = scaleIndex > 0 ? getScaleWord(scaleIndex, BigInt(segment)) : ''
204
+ const { word: segWords, endsWithCents, endsWithVingts } = buildSegment(segment)
205
+
206
+ if (scaleIndex === 0) {
207
+ parts.push(segWords)
208
+ } else if (scaleIndex === 1) {
209
+ if (segment === 1) {
210
+ parts.push(THOUSAND)
211
+ } else {
212
+ let adjustedWord = segWords
213
+ if (endsWithCents || endsWithVingts) {
214
+ adjustedWord = segWords.slice(0, -1)
215
+ }
216
+ parts.push(adjustedWord)
217
+ parts.push(scaleWord)
218
+ }
219
+ } else {
220
+ parts.push(segWords)
221
+ parts.push(scaleWord)
222
+ }
223
+ }
224
+
225
+ scaleIndex--
226
+ }
227
+
228
+ const sep = withHyphen ? '-' : ' '
229
+ let result = parts.join(sep)
230
+
231
+ if (withHyphen) {
232
+ result = result.replace(/ /g, '-')
233
+ }
234
+
235
+ return result
236
+ }
237
+
238
+ function decimalPartToWords (decimalPart, withHyphen) {
239
+ let result = ''
240
+ const sep = withHyphen ? '-' : ' '
241
+
242
+ let i = 0
243
+ while (i < decimalPart.length && decimalPart[i] === '0') {
244
+ if (result) result += sep
245
+ result += ZERO
246
+ i++
247
+ }
248
+
249
+ const remainder = decimalPart.slice(i)
250
+ if (remainder) {
251
+ if (result) result += sep
252
+ result += integerToWords(BigInt(remainder), withHyphen)
253
+ }
254
+
255
+ return result
256
+ }
2
257
 
3
258
  /**
4
- * French (Belgium) language converter.
259
+ * Converts a numeric value to Belgian French words.
5
260
  *
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
261
+ * @param {number | string | bigint} value - The numeric value to convert
262
+ * @param {Object} [options] - Optional configuration
263
+ * @param {boolean} [options.withHyphenSeparator=false] - Use hyphens between words
264
+ * @returns {string} The number in Belgian French words
10
265
  */
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
266
+ function toWords (value, options) {
267
+ options = validateOptions(options)
268
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
269
+ const withHyphen = options.withHyphenSeparator || false
270
+
271
+ let result = ''
272
+ const sep = withHyphen ? '-' : ' '
273
+
274
+ if (isNegative) {
275
+ result = NEGATIVE + sep
276
+ }
277
+
278
+ result += integerToWords(integerPart, withHyphen)
279
+
280
+ if (decimalPart) {
281
+ result += sep + DECIMAL_SEP + sep + decimalPartToWords(decimalPart, withHyphen)
24
282
  }
283
+
284
+ return result
25
285
  }
286
+
287
+ // ============================================================================
288
+ // Exports
289
+ // ============================================================================
290
+
291
+ export { toWords }