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,137 +1,247 @@
1
- import { AbstractLanguage } from '../classes/abstract-language.js'
2
-
3
1
  /**
4
- * Japanese language converter.
2
+ * Japanese language converter - Functional Implementation
3
+ *
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
- * Supports:
7
- * - Kanji numerals (一, 二, 三, etc.)
8
- * - Grouping by(10,000) instead of 1,000
9
- * - Special (one) omission rules (十, 百, 千 but 一万, 一億)
6
+ * Japanese-specific rules:
7
+ * - Myriad (万-based) grouping: 4 digits per segment instead of 3
8
+ * - omission: Omit "一" before 十, 百, 千 but NOT before and higher scales
9
+ * - Kanji numerals: 零一二三四五六七八九
10
+ * - No spaces between characters
10
11
  */
11
- export class Japanese extends AbstractLanguage {
12
- negativeWord = 'マイナス'
13
- decimalSeparatorWord = '点'
14
- zeroWord = '零'
15
- wordSeparator = '' // Japanese doesn't use spaces between characters
16
- usePerDigitDecimals = true // Enable digit-by-digit decimal conversion
17
-
18
- // Ones words used for group conversion (1-9)
19
- onesWords = ['一', '二', '三', '四', '五', '六', '七', '八', '九']
20
-
21
- // Scale words for grouping by 10^4
22
- scaleWords = [
23
- '', // 10^0 (ones)
24
- '万', // 10^4 (man)
25
- '億', // 10^8 (oku)
26
- '兆', // 10^12 (chō)
27
- '京', // 10^16 (kei)
28
- '垓', // 10^20 (gai)
29
- '秭', // 10^24 (jo/shi)
30
- '穣', // 10^28 (jō)
31
- '溝', // 10^32 (kō)
32
- '澗', // 10^36 (kan)
33
- '正', // 10^40 (sei)
34
- '載', // 10^44 (sai)
35
- '極', // 10^48 (goku)
36
- '恒河沙', // 10^52 (gōgasha)
37
- '阿僧祇', // 10^56 (asōgi)
38
- '那由他', // 10^60 (nayuta)
39
- '不可思議', // 10^64 (fukashigi)
40
- '無量大数' // 10^68 (muryōtaisū)
41
- ]
42
-
43
- /** Converts a segment of up to 4 digits to Japanese kanji with 一 omission rules. */
44
- segmentToWords (num) {
45
- if (num === 0n) return ''
46
-
47
- const thousands = num / 1000n
48
- const hundreds = (num % 1000n) / 100n
49
- const tens = (num % 100n) / 10n
50
- const ones = num % 10n
51
-
52
- let result = ''
53
-
54
- // Thousands (千)
55
- if (thousands > 0n) {
56
- // Always omit 一 before 千 when thousands === 1
57
- if (thousands === 1n) {
58
- result += '千'
59
- } else {
60
- result += this.onesWords[Number(thousands) - 1] + '千'
61
- }
62
- }
63
12
 
64
- // Hundreds (百)
65
- if (hundreds > 0n) {
66
- // Always omit 一 before 百 when hundreds === 1
67
- if (hundreds === 1n) {
68
- result += '百'
69
- } else {
70
- result += this.onesWords[Number(hundreds) - 1] + '百'
71
- }
13
+ import { parseNumericValue } from '../utils/parse-numeric.js'
14
+
15
+ // ============================================================================
16
+ // Vocabulary (module-level constants)
17
+ // ============================================================================
18
+
19
+ // Ones words (1-9), index 0 unused
20
+ const ONES = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九']
21
+
22
+ // Scale words for powers of 10,000 (万-based system)
23
+ // Index 0 = 万 (10^4), 1 = 億 (10^8), 2 = 兆 (10^12), etc.
24
+ const SCALES = [
25
+ '万', // 10^4 (man)
26
+ '億', // 10^8 (oku)
27
+ '兆', // 10^12 (chō)
28
+ '京', // 10^16 (kei)
29
+ '垓', // 10^20 (gai)
30
+ '秭', // 10^24 (jo/shi)
31
+ '穣', // 10^28 (jō)
32
+ '溝', // 10^32 (kō)
33
+ '澗', // 10^36 (kan)
34
+ '正', // 10^40 (sei)
35
+ '載', // 10^44 (sai)
36
+ '極', // 10^48 (goku)
37
+ '恒河沙', // 10^52 (gōgasha)
38
+ '阿僧祇', // 10^56 (asōgi)
39
+ '那由他', // 10^60 (nayuta)
40
+ '不可思議', // 10^64 (fukashigi)
41
+ '無量大数' // 10^68 (muryōtaisū)
42
+ ]
43
+
44
+ const ZERO = '零'
45
+ const NEGATIVE = 'マイナス'
46
+ const DECIMAL_SEP = '点'
47
+
48
+ // Internal scale words (within 4-digit segments)
49
+ const TEN = '十'
50
+ const HUNDRED = '百'
51
+ const THOUSAND = '千'
52
+
53
+ // ============================================================================
54
+ // Segment Building
55
+ // ============================================================================
56
+
57
+ /**
58
+ * Builds segment word for 0-9999 with 一 omission rules.
59
+ * - Omit 一 before 十, 百, 千
60
+ */
61
+ function buildSegment (n) {
62
+ if (n === 0) return ''
63
+
64
+ const ones = n % 10
65
+ const tens = Math.floor(n / 10) % 10
66
+ const hundreds = Math.floor(n / 100) % 10
67
+ const thousands = Math.floor(n / 1000)
68
+
69
+ let result = ''
70
+
71
+ // Thousands (千) - omit 一 when 1
72
+ if (thousands > 0) {
73
+ if (thousands === 1) {
74
+ result += THOUSAND
75
+ } else {
76
+ result += ONES[thousands] + THOUSAND
72
77
  }
78
+ }
73
79
 
74
- // Tens ()
75
- if (tens > 0n) {
76
- // Always omit 一 before 十 when tens === 1
77
- if (tens === 1n) {
78
- result += '十'
79
- } else {
80
- result += this.onesWords[Number(tens) - 1] + '十'
81
- }
80
+ // Hundreds () - omit 一 when 1
81
+ if (hundreds > 0) {
82
+ if (hundreds === 1) {
83
+ result += HUNDRED
84
+ } else {
85
+ result += ONES[hundreds] + HUNDRED
82
86
  }
87
+ }
83
88
 
84
- // Ones
85
- if (ones > 0n) {
86
- result += this.onesWords[Number(ones) - 1]
89
+ // Tens (十) - omit 一 when 1
90
+ if (tens > 0) {
91
+ if (tens === 1) {
92
+ result += TEN
93
+ } else {
94
+ result += ONES[tens] + TEN
87
95
  }
96
+ }
88
97
 
89
- return result
98
+ // Ones
99
+ if (ones > 0) {
100
+ result += ONES[ones]
90
101
  }
91
102
 
92
- /** Converts integer part using Japanese 万-based grouping system. */
93
- integerToWords (integerPart) {
94
- if (integerPart === 0n) {
95
- return this.zeroWord
96
- }
103
+ return result
104
+ }
97
105
 
98
- let temp = integerPart
99
- let scaleIndex = 0
100
- const groups = []
106
+ // ============================================================================
107
+ // Conversion Functions
108
+ // ============================================================================
101
109
 
102
- // Split into groups of 4 digits (万-based system)
103
- while (temp > 0n) {
104
- const group = temp % 10000n
105
- if (group > 0n) {
106
- groups.push({ value: group, scale: scaleIndex })
107
- }
108
- temp = temp / 10000n
109
- scaleIndex++
110
+ /**
111
+ * Converts a non-negative integer to Japanese words.
112
+ *
113
+ * @param {bigint} n - Non-negative integer to convert
114
+ * @returns {string} Japanese kanji words
115
+ */
116
+ function integerToWords (n) {
117
+ if (n === 0n) return ZERO
118
+
119
+ // Fast path: numbers < 10000
120
+ if (n < 10000n) {
121
+ return buildSegment(Number(n))
122
+ }
123
+
124
+ // Fast path: numbers < 100,000,000 (万 range)
125
+ if (n < 100_000_000n) {
126
+ const man = Number(n / 10000n)
127
+ const remainder = Number(n % 10000n)
128
+
129
+ // For 万 and above, we need 一 before the scale word when segment is 1
130
+ let result
131
+ if (man === 1) {
132
+ result = '一' + SCALES[0] // 一万
133
+ } else {
134
+ result = buildSegment(man) + SCALES[0]
135
+ }
136
+
137
+ if (remainder > 0) {
138
+ result += buildSegment(remainder)
110
139
  }
111
140
 
112
- // Reverse to process from highest to lowest
113
- groups.reverse()
141
+ return result
142
+ }
143
+
144
+ // For numbers >= 100,000,000, use scale decomposition
145
+ return buildLargeNumberWords(n)
146
+ }
114
147
 
115
- let result = ''
148
+ /**
149
+ * Builds words for numbers >= 100,000,000.
150
+ * Uses BigInt modulo for 4-digit (myriad) segment extraction.
151
+ *
152
+ * @param {bigint} n - Number >= 100,000,000
153
+ * @returns {string} Japanese kanji words
154
+ */
155
+ function buildLargeNumberWords (n) {
156
+ // Extract segments using BigInt modulo (faster than string slicing)
157
+ // Segments stored least-significant first (index 0 = units, 1 = 万, etc.)
158
+ const segments = []
159
+ let temp = n
160
+ while (temp > 0n) {
161
+ segments.push(Number(temp % 10000n))
162
+ temp = temp / 10000n
163
+ }
116
164
 
117
- for (let i = 0; i < groups.length; i++) {
118
- const { value, scale } = groups[i]
165
+ // Build result string directly (process from most-significant to least)
166
+ let result = ''
119
167
 
120
- const groupStr = this.segmentToWords(value)
168
+ for (let i = segments.length - 1; i >= 0; i--) {
169
+ const segment = segments[i]
170
+ if (segment === 0) continue
121
171
 
122
- // For scales >= 1 ( and above), always add the scale word
123
- if (scale >= 1) {
124
- // Special case: if group is 1 and scale >= 1, we need 一 before the scale
125
- if (value === 1n) {
126
- result += '一' + this.scaleWords[scale]
127
- } else {
128
- result += groupStr + this.scaleWords[scale]
129
- }
172
+ if (i > 0) {
173
+ // For scales >= 万, we need 一 before scale word when segment is 1
174
+ if (segment === 1) {
175
+ result += '一' + SCALES[i - 1]
130
176
  } else {
131
- result += groupStr
177
+ result += buildSegment(segment) + SCALES[i - 1]
132
178
  }
179
+ } else {
180
+ // Units segment (no scale word)
181
+ result += buildSegment(segment)
133
182
  }
183
+ }
134
184
 
135
- return result
185
+ return result || ZERO
186
+ }
187
+
188
+ /**
189
+ * Converts decimal digits to Japanese words (digit by digit).
190
+ *
191
+ * @param {string} decimalPart - Decimal digits (without the point)
192
+ * @returns {string} Japanese kanji words for decimal part
193
+ */
194
+ function decimalPartToWords (decimalPart) {
195
+ let result = ''
196
+
197
+ for (let i = 0; i < decimalPart.length; i++) {
198
+ const digit = parseInt(decimalPart[i], 10)
199
+ if (digit === 0) {
200
+ result += ZERO
201
+ } else {
202
+ result += ONES[digit]
203
+ }
204
+ }
205
+
206
+ return result
207
+ }
208
+
209
+ /**
210
+ * Converts a numeric value to Japanese words.
211
+ *
212
+ * This is the main public API. It accepts any valid numeric input
213
+ * (number, string, or bigint) and handles parsing internally.
214
+ *
215
+ * @param {number | string | bigint} value - The numeric value to convert
216
+ * @returns {string} The number in Japanese kanji words
217
+ * @throws {TypeError} If value is not a valid numeric type
218
+ * @throws {Error} If value is not a valid number format
219
+ *
220
+ * @example
221
+ * toWords(42) // '四十二'
222
+ * toWords(10000) // '一万'
223
+ * toWords(100000000) // '一億'
224
+ */
225
+ function toWords (value) {
226
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
227
+
228
+ let result = ''
229
+
230
+ if (isNegative) {
231
+ result = NEGATIVE
232
+ }
233
+
234
+ result += integerToWords(integerPart)
235
+
236
+ if (decimalPart) {
237
+ result += DECIMAL_SEP + decimalPartToWords(decimalPart)
136
238
  }
239
+
240
+ return result
137
241
  }
242
+
243
+ // ============================================================================
244
+ // Public API
245
+ // ============================================================================
246
+
247
+ export { toWords }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Converts a numeric value to Georgian words.
3
+ *
4
+ * This is the main public API. It accepts any valid numeric input
5
+ * (number, string, or bigint) and handles parsing internally.
6
+ *
7
+ * @param {number | string | bigint} value - The numeric value to convert
8
+ * @returns {string} The number in Georgian words
9
+ * @throws {TypeError} If value is not a valid numeric type
10
+ * @throws {Error} If value is not a valid number format
11
+ *
12
+ * @example
13
+ * toWords(21) // 'ოცდაერთი'
14
+ * toWords(100) // 'ასი'
15
+ * toWords(1000) // 'ათასი'
16
+ */
17
+ export function toWords(value: number | string | bigint): string;
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Georgian language converter - Functional Implementation
3
+ *
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
+ *
6
+ * Georgian-specific rules:
7
+ * - Vigesimal (base-20) system for 20-99
8
+ * - 40 = ორმოცი (2×20), 60 = სამოცი (3×20), 80 = ოთხმოცი (4×20)
9
+ * - 30/50/70/90 use "და" + "ათი": ოცდაათი (20+10), ორმოცდაათი (40+10)
10
+ * - Compound numbers use "და" (da = "and") connector
11
+ * - Hundreds: unit prefix + ას (ორასი = 200)
12
+ * - Short scale for large numbers (მილიონი, მილიარდი, ტრილიონი)
13
+ */
14
+
15
+ import { parseNumericValue } from '../utils/parse-numeric.js'
16
+
17
+ // ============================================================================
18
+ // Vocabulary (module-level constants)
19
+ // ============================================================================
20
+
21
+ // Numbers 0-9 (primitives)
22
+ const ONES = ['ნული', 'ერთი', 'ორი', 'სამი', 'ოთხი', 'ხუთი', 'ექვსი', 'შვიდი', 'რვა', 'ცხრა']
23
+
24
+ // Numbers 10-19
25
+ const TEENS = ['ათი', 'თერთმეტი', 'თორმეტი', 'ცამეტი', 'თოთხმეტი', 'თხუთმეტი', 'თექვსმეტი', 'ჩვიდმეტი', 'თვრამეტი', 'ცხრამეტი']
26
+
27
+ // Vigesimal bases: 20, 40, 60, 80 (with და connector forms)
28
+ const VIGESIMAL = ['', 'ოცი', 'ორმოცი', 'სამოცი', 'ოთხმოცი']
29
+ const VIGESIMAL_DA = ['', 'ოცდა', 'ორმოცდა', 'სამოცდა', 'ოთხმოცდა']
30
+
31
+ // Prefixes for hundreds (unit forms without final vowel)
32
+ const HUNDRED_PREFIXES = ['', '', 'ორ', 'სამ', 'ოთხ', 'ხუთ', 'ექვს', 'შვიდ', 'რვა', 'ცხრა']
33
+
34
+ const HUNDRED = 'ასი'
35
+ const HUNDRED_STEM = 'ას' // Without final vowel
36
+ const THOUSAND = 'ათასი'
37
+ const THOUSAND_STEM = 'ათას' // Without final vowel
38
+
39
+ // Scale words (short scale) - indexed by segment position
40
+ const SCALES = ['', '', 'მილიონი', 'მილიარდი', 'ტრილიონი', 'კვადრილიონი', 'კვინტილიონი', 'სექსტილიონი']
41
+
42
+ const ZERO = 'ნული'
43
+ const NEGATIVE = 'მინუს'
44
+ const DECIMAL_SEP = 'მთელი'
45
+
46
+ // ============================================================================
47
+ // Segment Building
48
+ // ============================================================================
49
+
50
+ /**
51
+ * Builds segment word for 0-99 using vigesimal system.
52
+ * @param {number} n - Number 0-99
53
+ * @returns {string} Georgian word
54
+ */
55
+ function buildTens (n) {
56
+ if (n < 10) return ONES[n]
57
+ if (n < 20) return TEENS[n - 10]
58
+
59
+ const vigesimalGroup = Math.floor(n / 20)
60
+ const remainder = n % 20
61
+
62
+ if (remainder === 0) {
63
+ return VIGESIMAL[vigesimalGroup]
64
+ }
65
+
66
+ // Use და connector: ოცდა + remainder
67
+ const base = VIGESIMAL_DA[vigesimalGroup]
68
+ if (remainder < 10) {
69
+ return base + ONES[remainder]
70
+ }
71
+ return base + TEENS[remainder - 10]
72
+ }
73
+
74
+ /**
75
+ * Builds segment word for 0-999.
76
+ * Returns object with full form and stem form (without final vowel).
77
+ * @param {number} n - Number 0-999
78
+ * @returns {{full: string, stem: string}} Georgian words
79
+ */
80
+ function buildSegment (n) {
81
+ if (n === 0) return { full: '', stem: '' }
82
+ if (n < 100) {
83
+ const word = buildTens(n)
84
+ // Remove final vowel for stem
85
+ const lastChar = word.slice(-1)
86
+ const stem = (lastChar === 'ი' || lastChar === 'ა') ? word.slice(0, -1) : word
87
+ return { full: word, stem }
88
+ }
89
+
90
+ const hundreds = Math.floor(n / 100)
91
+ const remainder = n % 100
92
+
93
+ // Build hundreds: ასი (100), ორასი (200), etc.
94
+ let hundredWord
95
+ if (hundreds === 1) {
96
+ hundredWord = remainder > 0 ? HUNDRED_STEM : HUNDRED
97
+ } else {
98
+ hundredWord = HUNDRED_PREFIXES[hundreds] + (remainder > 0 ? HUNDRED_STEM : HUNDRED)
99
+ }
100
+
101
+ if (remainder > 0) {
102
+ const remainderWord = buildTens(remainder)
103
+ const full = hundredWord + ' ' + remainderWord
104
+ // Stem removes final vowel from remainder
105
+ const lastChar = remainderWord.slice(-1)
106
+ const remainderStem = (lastChar === 'ი' || lastChar === 'ა') ? remainderWord.slice(0, -1) : remainderWord
107
+ return { full, stem: hundredWord + ' ' + remainderStem }
108
+ }
109
+
110
+ // Hundreds only - stem removes final ი
111
+ return { full: hundredWord, stem: hundredWord.slice(0, -1) }
112
+ }
113
+
114
+ // ============================================================================
115
+ // Conversion Functions
116
+ // ============================================================================
117
+
118
+ /**
119
+ * Converts a non-negative integer to Georgian words.
120
+ *
121
+ * @param {bigint} n - Non-negative integer to convert
122
+ * @returns {string} Georgian words
123
+ */
124
+ function integerToWords (n) {
125
+ if (n === 0n) return ZERO
126
+
127
+ // Fast path: numbers < 1000
128
+ if (n < 1000n) {
129
+ const { full } = buildSegment(Number(n))
130
+ return full
131
+ }
132
+
133
+ // Fast path: numbers < 1,000,000 (thousands)
134
+ if (n < 1_000_000n) {
135
+ const thousands = Number(n / 1000n)
136
+ const remainder = Number(n % 1000n)
137
+
138
+ let result
139
+ if (thousands === 1) {
140
+ // "ათასი" not "ერთი ათასი"
141
+ result = remainder > 0 ? THOUSAND_STEM : THOUSAND
142
+ } else {
143
+ // Use stem form before ათასი
144
+ const { stem: thousandsPart } = buildSegment(thousands)
145
+ result = thousandsPart + ' ' + (remainder > 0 ? THOUSAND_STEM : THOUSAND)
146
+ }
147
+
148
+ if (remainder > 0) {
149
+ const { full: remainderWord } = buildSegment(remainder)
150
+ result += ' ' + remainderWord
151
+ }
152
+
153
+ return result
154
+ }
155
+
156
+ // For numbers >= 1,000,000, use scale decomposition
157
+ return buildLargeNumberWords(n)
158
+ }
159
+
160
+ /**
161
+ * Builds words for numbers >= 1,000,000.
162
+ *
163
+ * @param {bigint} n - Number >= 1,000,000
164
+ * @returns {string} Georgian words
165
+ */
166
+ function buildLargeNumberWords (n) {
167
+ const numStr = n.toString()
168
+ const len = numStr.length
169
+
170
+ // Build segments of 3 digits from left to right
171
+ const segments = []
172
+ const segmentSize = 3
173
+
174
+ const remainderLen = len % segmentSize
175
+ let pos = 0
176
+ if (remainderLen > 0) {
177
+ segments.push(Number(numStr.slice(0, remainderLen)))
178
+ pos = remainderLen
179
+ }
180
+ while (pos < len) {
181
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
182
+ pos += segmentSize
183
+ }
184
+
185
+ // Convert segments to words
186
+ const parts = []
187
+ let scaleIndex = segments.length - 1
188
+
189
+ for (let i = 0; i < segments.length; i++) {
190
+ const segment = segments[i]
191
+
192
+ if (segment !== 0) {
193
+ if (scaleIndex === 0) {
194
+ // Units (no scale)
195
+ const { full } = buildSegment(segment)
196
+ parts.push(full)
197
+ } else if (scaleIndex === 1) {
198
+ // Thousands - check if there's a remainder
199
+ const hasRemainder = segments.slice(i + 1).some(s => s !== 0)
200
+ const thousandWord = hasRemainder ? THOUSAND_STEM : THOUSAND
201
+
202
+ if (segment === 1) {
203
+ parts.push(thousandWord)
204
+ } else {
205
+ const { stem } = buildSegment(segment)
206
+ parts.push(stem + ' ' + thousandWord)
207
+ }
208
+ } else {
209
+ // Million and above
210
+ const scaleWord = SCALES[scaleIndex] || SCALES[SCALES.length - 1]
211
+ if (segment === 1) {
212
+ parts.push('ერთი ' + scaleWord)
213
+ } else {
214
+ const { full } = buildSegment(segment)
215
+ parts.push(full + ' ' + scaleWord)
216
+ }
217
+ }
218
+ }
219
+
220
+ scaleIndex--
221
+ }
222
+
223
+ return parts.join(' ')
224
+ }
225
+
226
+ /**
227
+ * Converts decimal digits to Georgian words.
228
+ *
229
+ * @param {string} decimalPart - Decimal digits (without the point)
230
+ * @returns {string} Georgian words for decimal part
231
+ */
232
+ function decimalPartToWords (decimalPart) {
233
+ let result = ''
234
+
235
+ // Handle leading zeros
236
+ let i = 0
237
+ while (i < decimalPart.length && decimalPart[i] === '0') {
238
+ if (result) result += ' '
239
+ result += ZERO
240
+ i++
241
+ }
242
+
243
+ // Convert remainder as a single number
244
+ const remainder = decimalPart.slice(i)
245
+ if (remainder) {
246
+ if (result) result += ' '
247
+ result += integerToWords(BigInt(remainder))
248
+ }
249
+
250
+ return result
251
+ }
252
+
253
+ /**
254
+ * Converts a numeric value to Georgian words.
255
+ *
256
+ * This is the main public API. It accepts any valid numeric input
257
+ * (number, string, or bigint) and handles parsing internally.
258
+ *
259
+ * @param {number | string | bigint} value - The numeric value to convert
260
+ * @returns {string} The number in Georgian words
261
+ * @throws {TypeError} If value is not a valid numeric type
262
+ * @throws {Error} If value is not a valid number format
263
+ *
264
+ * @example
265
+ * toWords(21) // 'ოცდაერთი'
266
+ * toWords(100) // 'ასი'
267
+ * toWords(1000) // 'ათასი'
268
+ */
269
+ function toWords (value) {
270
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
271
+
272
+ let result = ''
273
+
274
+ if (isNegative) {
275
+ result = NEGATIVE + ' '
276
+ }
277
+
278
+ result += integerToWords(integerPart)
279
+
280
+ if (decimalPart) {
281
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
282
+ }
283
+
284
+ return result
285
+ }
286
+
287
+ // ============================================================================
288
+ // Public API
289
+ // ============================================================================
290
+
291
+ export { toWords }
@@ -1,11 +1,7 @@
1
1
  /**
2
- * Kannada language converter.
2
+ * Converts a numeric value to Kannada words.
3
3
  *
4
- * Supports:
5
- * - Indian numbering system (ಸಾವಿರ, ಲಕ್ಷ, ಕೋಟಿ)
6
- * - Kannada script
7
- * - Complete word forms for 0-99
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Kannada words
8
6
  */
9
- export class Kannada extends SouthAsianLanguage {
10
- }
11
- import { SouthAsianLanguage } from '../classes/south-asian-language.js';
7
+ export function toWords(value: number | string | bigint): string;