num2words2 0.1.0.dev1__py3-none-any.whl

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 (133) hide show
  1. num2words2/README.md +69 -0
  2. num2words2/__init__.py +196 -0
  3. num2words2/_version.py +3 -0
  4. num2words2/base.py +377 -0
  5. num2words2/compat.py +29 -0
  6. num2words2/currency.py +60 -0
  7. num2words2/lang_AF.py +202 -0
  8. num2words2/lang_AM.py +193 -0
  9. num2words2/lang_AR.py +435 -0
  10. num2words2/lang_AS.py +119 -0
  11. num2words2/lang_AZ.py +208 -0
  12. num2words2/lang_BA.py +119 -0
  13. num2words2/lang_BE.py +356 -0
  14. num2words2/lang_BG.py +393 -0
  15. num2words2/lang_BN.py +242 -0
  16. num2words2/lang_BO.py +122 -0
  17. num2words2/lang_BR.py +135 -0
  18. num2words2/lang_BS.py +189 -0
  19. num2words2/lang_CA.py +476 -0
  20. num2words2/lang_CE.py +546 -0
  21. num2words2/lang_CS.py +260 -0
  22. num2words2/lang_CY.py +574 -0
  23. num2words2/lang_DA.py +170 -0
  24. num2words2/lang_DE.py +180 -0
  25. num2words2/lang_EL.py +338 -0
  26. num2words2/lang_EN.py +118 -0
  27. num2words2/lang_EN_IN.py +26 -0
  28. num2words2/lang_EN_NG.py +45 -0
  29. num2words2/lang_EO.py +130 -0
  30. num2words2/lang_ES.py +399 -0
  31. num2words2/lang_ES_CO.py +37 -0
  32. num2words2/lang_ES_CR.py +38 -0
  33. num2words2/lang_ES_GT.py +37 -0
  34. num2words2/lang_ES_NI.py +35 -0
  35. num2words2/lang_ES_VE.py +43 -0
  36. num2words2/lang_ET.py +393 -0
  37. num2words2/lang_EU.py +158 -0
  38. num2words2/lang_EUR.py +104 -0
  39. num2words2/lang_FA.py +187 -0
  40. num2words2/lang_FI.py +759 -0
  41. num2words2/lang_FO.py +136 -0
  42. num2words2/lang_FR.py +133 -0
  43. num2words2/lang_FR_BE.py +49 -0
  44. num2words2/lang_FR_CH.py +48 -0
  45. num2words2/lang_FR_DZ.py +35 -0
  46. num2words2/lang_GL.py +135 -0
  47. num2words2/lang_GU.py +172 -0
  48. num2words2/lang_HA.py +293 -0
  49. num2words2/lang_HAW.py +144 -0
  50. num2words2/lang_HE.py +314 -0
  51. num2words2/lang_HI.py +243 -0
  52. num2words2/lang_HR.py +304 -0
  53. num2words2/lang_HT.py +136 -0
  54. num2words2/lang_HU.py +189 -0
  55. num2words2/lang_HY.py +364 -0
  56. num2words2/lang_ID.py +242 -0
  57. num2words2/lang_IS.py +208 -0
  58. num2words2/lang_IT.py +265 -0
  59. num2words2/lang_JA.py +615 -0
  60. num2words2/lang_JW.py +136 -0
  61. num2words2/lang_KA.py +169 -0
  62. num2words2/lang_KK.py +136 -0
  63. num2words2/lang_KM.py +173 -0
  64. num2words2/lang_KN.py +182 -0
  65. num2words2/lang_KO.py +175 -0
  66. num2words2/lang_KZ.py +161 -0
  67. num2words2/lang_LA.py +135 -0
  68. num2words2/lang_LB.py +135 -0
  69. num2words2/lang_LN.py +136 -0
  70. num2words2/lang_LO.py +149 -0
  71. num2words2/lang_LT.py +283 -0
  72. num2words2/lang_LV.py +266 -0
  73. num2words2/lang_MG.py +143 -0
  74. num2words2/lang_MI.py +151 -0
  75. num2words2/lang_MK.py +136 -0
  76. num2words2/lang_ML.py +159 -0
  77. num2words2/lang_MN.py +319 -0
  78. num2words2/lang_MR.py +187 -0
  79. num2words2/lang_MS.py +293 -0
  80. num2words2/lang_MT.py +159 -0
  81. num2words2/lang_MY.py +152 -0
  82. num2words2/lang_NE.py +157 -0
  83. num2words2/lang_NL.py +204 -0
  84. num2words2/lang_NN.py +136 -0
  85. num2words2/lang_NO.py +143 -0
  86. num2words2/lang_OC.py +135 -0
  87. num2words2/lang_PA.py +159 -0
  88. num2words2/lang_PL.py +336 -0
  89. num2words2/lang_PS.py +136 -0
  90. num2words2/lang_PT.py +280 -0
  91. num2words2/lang_PT_BR.py +224 -0
  92. num2words2/lang_RO.py +183 -0
  93. num2words2/lang_RU.py +570 -0
  94. num2words2/lang_SA.py +136 -0
  95. num2words2/lang_SD.py +136 -0
  96. num2words2/lang_SI.py +152 -0
  97. num2words2/lang_SK.py +250 -0
  98. num2words2/lang_SL.py +306 -0
  99. num2words2/lang_SN.py +447 -0
  100. num2words2/lang_SO.py +136 -0
  101. num2words2/lang_SQ.py +252 -0
  102. num2words2/lang_SR.py +254 -0
  103. num2words2/lang_SU.py +136 -0
  104. num2words2/lang_SV.py +169 -0
  105. num2words2/lang_SW.py +219 -0
  106. num2words2/lang_TA.py +303 -0
  107. num2words2/lang_TE.py +169 -0
  108. num2words2/lang_TET.py +285 -0
  109. num2words2/lang_TG.py +149 -0
  110. num2words2/lang_TH.py +214 -0
  111. num2words2/lang_TK.py +136 -0
  112. num2words2/lang_TL.py +145 -0
  113. num2words2/lang_TR.py +882 -0
  114. num2words2/lang_TT.py +136 -0
  115. num2words2/lang_UK.py +1101 -0
  116. num2words2/lang_UR.py +168 -0
  117. num2words2/lang_UZ.py +136 -0
  118. num2words2/lang_VI.py +143 -0
  119. num2words2/lang_WO.py +136 -0
  120. num2words2/lang_YI.py +135 -0
  121. num2words2/lang_YO.py +136 -0
  122. num2words2/lang_ZH.py +296 -0
  123. num2words2/lang_ZH_CN.py +85 -0
  124. num2words2/lang_ZH_HK.py +48 -0
  125. num2words2/lang_ZH_TW.py +198 -0
  126. num2words2/utils.py +35 -0
  127. num2words2-0.1.0.dev1.data/scripts/num2words2 +98 -0
  128. num2words2-0.1.0.dev1.dist-info/METADATA +244 -0
  129. num2words2-0.1.0.dev1.dist-info/RECORD +133 -0
  130. num2words2-0.1.0.dev1.dist-info/WHEEL +5 -0
  131. num2words2-0.1.0.dev1.dist-info/entry_points.txt +2 -0
  132. num2words2-0.1.0.dev1.dist-info/licenses/COPYING +145 -0
  133. num2words2-0.1.0.dev1.dist-info/top_level.txt +1 -0
num2words2/lang_BG.py ADDED
@@ -0,0 +1,393 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
3
+ # Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
4
+
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this library; if not, write to the Free Software
15
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16
+ # MA 02110-1301 USA
17
+
18
+ from __future__ import unicode_literals
19
+
20
+ from .base import Num2Word_Base
21
+ from .currency import parse_currency_parts
22
+
23
+
24
+ class Num2Word_BG(Num2Word_Base):
25
+ CURRENCY_FORMS = {
26
+ 'BGN': (
27
+ ('лев', 'лева'), ('стотинка', 'стотинки')
28
+ ),
29
+ 'EUR': (
30
+ ('евро', 'евро'), ('цент', 'цента')
31
+ ),
32
+ 'USD': (
33
+ ('долар', 'долара'), ('цент', 'цента')
34
+ ),
35
+ 'GBP': (
36
+ ('паунд', 'паунда'), ('пени', 'пенса')
37
+ ),
38
+ 'JPY': (
39
+ ('йена', 'йени'), ('сен', 'сена')
40
+ ),
41
+ }
42
+
43
+ def __init__(self):
44
+ super(Num2Word_BG, self).__init__()
45
+
46
+ self.ones = {
47
+ 0: 'нула',
48
+ 1: 'едно',
49
+ 2: 'две',
50
+ 3: 'три',
51
+ 4: 'четири',
52
+ 5: 'пет',
53
+ 6: 'шест',
54
+ 7: 'седем',
55
+ 8: 'осем',
56
+ 9: 'девет'
57
+ }
58
+
59
+ self.ones_masculine = {
60
+ 1: 'един',
61
+ 2: 'два'
62
+ }
63
+
64
+ self.ones_feminine = {
65
+ 1: 'една',
66
+ 2: 'две'
67
+ }
68
+
69
+ self.tens = {
70
+ 10: 'десет',
71
+ 11: 'единадесет',
72
+ 12: 'дванадесет',
73
+ 13: 'тринадесет',
74
+ 14: 'четиринадесет',
75
+ 15: 'петнадесет',
76
+ 16: 'шестнадесет',
77
+ 17: 'седемнадесет',
78
+ 18: 'осемнадесет',
79
+ 19: 'деветнадесет',
80
+ 20: 'двадесет',
81
+ 30: 'тридесет',
82
+ 40: 'четиридесет',
83
+ 50: 'петдесет',
84
+ 60: 'шестдесет',
85
+ 70: 'седемдесет',
86
+ 80: 'осемдесет',
87
+ 90: 'деветдесет'
88
+ }
89
+
90
+ self.scale = {
91
+ 100: ('сто', 'ста'),
92
+ 1000: ('хиляда', 'хиляди'),
93
+ 1000000: ('милион', 'милиона'),
94
+ 1000000000: ('милиард', 'милиарда'),
95
+ 1000000000000: ('трилион', 'трилиона')
96
+ }
97
+
98
+ self.ordinals = {
99
+ 1: 'първи',
100
+ 2: 'втори',
101
+ 3: 'трети',
102
+ 4: 'четвърти',
103
+ 5: 'пети',
104
+ 6: 'шести',
105
+ 7: 'седми',
106
+ 8: 'осми',
107
+ 9: 'девети',
108
+ 10: 'десети',
109
+ 11: 'единадесети',
110
+ 12: 'дванадесети',
111
+ 13: 'тринадесети',
112
+ 14: 'четиринадесети',
113
+ 15: 'петнадесети',
114
+ 16: 'шестнадесети',
115
+ 17: 'седемнадесети',
116
+ 18: 'осемнадесети',
117
+ 19: 'деветнадесети',
118
+ 20: 'двадесети',
119
+ 30: 'тридесети',
120
+ 40: 'четиридесети',
121
+ 50: 'петдесети',
122
+ 60: 'шестдесети',
123
+ 70: 'седемдесети',
124
+ 80: 'осемдесети',
125
+ 90: 'деветдесети',
126
+ 100: 'стотен',
127
+ 1000: 'хиляден'
128
+ }
129
+
130
+ self.negword = "минус "
131
+ self.pointword = "точка"
132
+
133
+ def _setup(self):
134
+ super(Num2Word_BG, self)._setup()
135
+
136
+ def _int_to_word(self, n, masculine=False, feminine=False):
137
+ """
138
+ Converts a number to words in Bulgarian.
139
+ Args:
140
+ n: integer to convert
141
+ masculine: whether to use masculine form for ones
142
+ feminine: whether to use feminine form for ones
143
+ """
144
+ if n == 0:
145
+ return self.ones[0]
146
+
147
+ parts = []
148
+
149
+ # Handle billions
150
+ if n >= 1000000000:
151
+ billions = n // 1000000000
152
+ if billions == 1:
153
+ parts.append('един милиард')
154
+ elif billions == 2:
155
+ parts.append('два милиарда')
156
+ else:
157
+ parts.append(self._int_to_word(billions) + ' милиарда')
158
+ n %= 1000000000
159
+
160
+ # Handle millions
161
+ if n >= 1000000:
162
+ millions = n // 1000000
163
+ if millions == 1:
164
+ parts.append('един милион')
165
+ elif millions == 2:
166
+ parts.append('два милиона')
167
+ else:
168
+ parts.append(self._int_to_word(millions) + ' милиона')
169
+ n %= 1000000
170
+
171
+ # Handle thousands
172
+ if n >= 1000:
173
+ thousands = n // 1000
174
+ if thousands == 1:
175
+ parts.append('хиляда')
176
+ elif thousands == 2:
177
+ parts.append('две хиляди')
178
+ else:
179
+ parts.append(self._int_to_word(thousands) + ' хиляди')
180
+ n %= 1000
181
+
182
+ # Handle hundreds
183
+ if n >= 100:
184
+ hundreds = n // 100
185
+ if hundreds == 1:
186
+ parts.append('сто')
187
+ elif hundreds == 2:
188
+ parts.append('двеста')
189
+ elif hundreds == 3:
190
+ parts.append('триста')
191
+ else:
192
+ parts.append(self.ones[hundreds] + 'стотин')
193
+ n %= 100
194
+
195
+ # Handle tens and ones
196
+ if n >= 20:
197
+ tens = (n // 10) * 10
198
+ parts.append(self.tens[tens])
199
+ n %= 10
200
+ if n > 0:
201
+ parts.append('и')
202
+ if feminine and n in self.ones_feminine:
203
+ parts.append(self.ones_feminine[n])
204
+ elif masculine and n in self.ones_masculine:
205
+ parts.append(self.ones_masculine[n])
206
+ else:
207
+ parts.append(self.ones[n])
208
+ elif n >= 10:
209
+ parts.append(self.tens[n])
210
+ elif n > 0:
211
+ if feminine and n in self.ones_feminine:
212
+ parts.append(self.ones_feminine[n])
213
+ elif masculine and n in self.ones_masculine:
214
+ parts.append(self.ones_masculine[n])
215
+ else:
216
+ parts.append(self.ones[n])
217
+
218
+ return ' '.join(parts)
219
+
220
+ def _int_to_cardinal(self, n):
221
+ if n == 0:
222
+ return self.ones[0]
223
+
224
+ if n < 0:
225
+ return self.negword + self._int_to_word(-n)
226
+
227
+ return self._int_to_word(n, masculine=True)
228
+
229
+ def _int_to_ordinal(self, n):
230
+ if n in self.ordinals:
231
+ return self.ordinals[n]
232
+
233
+ # For complex numbers, form ordinal by adding -ти/-ен to cardinal
234
+ cardinal = self._int_to_cardinal(n)
235
+
236
+ # Handle different endings
237
+ if cardinal.endswith('един'):
238
+ return cardinal[:-4] + 'първи'
239
+ elif cardinal.endswith('два'):
240
+ return cardinal[:-3] + 'втори'
241
+ elif cardinal.endswith('три'):
242
+ return cardinal[:-3] + 'трети'
243
+ elif cardinal.endswith('т'):
244
+ return cardinal + 'и'
245
+ else:
246
+ return cardinal + 'ти'
247
+
248
+ def to_cardinal(self, n):
249
+ try:
250
+ if isinstance(n, str):
251
+ n = float(n)
252
+
253
+ # Handle floats with decimal places
254
+ if isinstance(n, float) and n != int(n):
255
+ pre, post = self.float2tuple(n)
256
+
257
+ # Handle negative decimals
258
+ if n < 0:
259
+ result = self.negword
260
+ pre = abs(pre)
261
+ else:
262
+ result = ""
263
+
264
+ # Integer part
265
+ result += self._int_to_cardinal(pre)
266
+
267
+ # Decimal part
268
+ if self.precision > 0:
269
+ result += " " + self.pointword
270
+
271
+ # Convert post to string with proper padding
272
+ post_str = str(post)
273
+ post_str = '0' * (self.precision - len(post_str)) + post_str
274
+
275
+ # Say each digit individually
276
+ for digit in post_str:
277
+ result += " " + self.ones[int(digit)]
278
+
279
+ return result.strip()
280
+
281
+ # For integers
282
+ if n < 0:
283
+ return self.negword + self.to_cardinal(-n)
284
+
285
+ return self._int_to_cardinal(int(n))
286
+ except BaseException:
287
+ return self._int_to_cardinal(int(n))
288
+
289
+ def to_ordinal(self, n):
290
+ try:
291
+ if isinstance(n, str):
292
+ n = int(n)
293
+
294
+ return self._int_to_ordinal(n)
295
+ except BaseException:
296
+ return self._int_to_ordinal(int(n))
297
+
298
+ def to_ordinal_num(self, n):
299
+ try:
300
+ if isinstance(n, str):
301
+ n = int(n)
302
+
303
+ # Bulgarian ordinal suffixes
304
+ if n % 100 in [11, 12, 13, 14, 15, 16, 17, 18, 19]:
305
+ return str(n) + '-ти'
306
+ elif n % 10 == 1:
307
+ return str(n) + '-ви'
308
+ elif n % 10 == 2:
309
+ return str(n) + '-ри'
310
+ elif n % 10 in [7, 8]:
311
+ return str(n) + '-ми'
312
+ else:
313
+ return str(n) + '-ти'
314
+ except BaseException:
315
+ return str(n) + '-ти'
316
+
317
+ def to_currency(self, val, currency='BGN', cents=True, separator=' и',
318
+ adjective=False):
319
+ """Convert a number to currency words."""
320
+ # Track if input was originally an integer
321
+ is_integer_input = isinstance(val, int)
322
+
323
+ # Check if value has fractional cents
324
+ if not is_integer_input:
325
+ from decimal import Decimal
326
+ decimal_val = Decimal(str(val))
327
+ has_fractional_cents = (decimal_val * 100) % 1 != 0
328
+ else:
329
+ has_fractional_cents = False
330
+
331
+ left, right, is_negative = parse_currency_parts(val, is_int_with_cents=False,
332
+ keep_precision=has_fractional_cents)
333
+
334
+ try:
335
+ cr1, cr2 = self.CURRENCY_FORMS[currency]
336
+ except KeyError:
337
+ raise NotImplementedError(
338
+ 'Currency "%s" not implemented for "%s"' % (
339
+ currency, self.__class__.__name__))
340
+
341
+ minus_str = "минус " if is_negative else ""
342
+ money_str = self._int_to_cardinal(left)
343
+
344
+ # Determine currency form based on the number
345
+ if left == 1:
346
+ currency_str = cr1[0] # singular
347
+ else:
348
+ currency_str = cr1[1] # plural
349
+
350
+ # For integers, don't show cents
351
+ if is_integer_input:
352
+ return "%s%s %s" % (minus_str, money_str, currency_str)
353
+
354
+ # For floats, always show cents (even if zero)
355
+ if cents:
356
+ # Handle fractional cents
357
+ from decimal import Decimal
358
+ if isinstance(right, Decimal):
359
+ # Convert fractional cents (e.g., 65.3 cents)
360
+ cents_str = self.to_cardinal(float(right)) if right > 0 else self.ones[0]
361
+ else:
362
+ cents_str = self._int_to_cardinal(right) if right > 0 else self.ones[0]
363
+ else:
364
+ cents_str = str(float(right) if isinstance(right, Decimal) else right)
365
+
366
+ # Determine cents form
367
+ if right == 1:
368
+ cents_currency = cr2[0] # singular
369
+ else:
370
+ cents_currency = cr2[1] # plural
371
+
372
+ return "%s%s %s%s %s %s" % (
373
+ minus_str, money_str, currency_str,
374
+ separator, cents_str, cents_currency)
375
+
376
+ def to_year(self, n):
377
+ if n < 1000:
378
+ return self._int_to_cardinal(n)
379
+ elif n < 2000:
380
+ # Years like 1999 -> "хиляда деветстотин деветдесет и девет"
381
+ thousands = n // 1000
382
+ remainder = n % 1000
383
+ if thousands == 1:
384
+ result = 'хиляда'
385
+ else:
386
+ result = self._int_to_cardinal(thousands) + ' хиляди'
387
+
388
+ if remainder > 0:
389
+ result += ' ' + self._int_to_cardinal(remainder)
390
+ return result
391
+ else:
392
+ # Years like 2023 -> "две хиляди двадесет и три"
393
+ return self._int_to_cardinal(n)
num2words2/lang_BN.py ADDED
@@ -0,0 +1,242 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Author: Mehedi Hasan Khondoker
3
+ # Email: mehedihasankhondoker [at] gmail.com
4
+ # Copyright (c) 2024, Mehedi Hasan Khondoker. All Rights Reserved.
5
+
6
+ # This library is build for Bangladesh format Number to Word conversion.
7
+ # You are welcome as contributor to the library.
8
+
9
+ # This library is free software; you can redistribute it and/or
10
+ # modify it under the terms of the GNU Lesser General Public
11
+ # License as published by the Free Software Foundation; either
12
+ # version 2.1 of the License, or (at your option) any later version.
13
+ # This library is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
+ # Lesser General Public License for more details.
17
+
18
+
19
+ from decimal import Decimal
20
+
21
+ RANKING = ['', 'প্রথম', 'দ্বিতীয়', 'তৃতীয়', 'চতুর্থ', 'পঞ্চম', 'ষষ্ঠ',
22
+ 'সপ্তম', 'অষ্টম', 'নবম', 'দশম'] # pragma: no cover
23
+
24
+ AKOK = ['', 'এক', 'দুই', 'তিন', 'চার', 'পাঁচ', 'ছয়',
25
+ 'সাত', 'আট', 'নয়'] # pragma: no cover
26
+
27
+ DOSOK = [
28
+ 'দশ', 'এগারো', 'বারো', 'তেরো', 'চৌদ্দ', 'পনের',
29
+ 'ষোল', 'সতের', 'আঠারো', 'উনিশ',
30
+ 'বিশ', 'একুশ', 'বাইশ', 'তেইশ', 'চব্বিশ', 'পঁচিশ',
31
+ 'ছাব্বিশ', 'সাতাশ', 'আটাশ', 'উনত্রিশ',
32
+ 'ত্রিশ', 'একত্রিশ', 'বত্রিশ', 'তেত্রিশ', 'চৌত্রিশ', 'পঁইত্রিশ',
33
+ 'ছত্রিশ', 'সাতত্রিশ', 'আটত্রিশ', 'উনচল্লিশ', 'চল্লিশ',
34
+ 'একচল্লিশ', 'বিয়াল্লিশ', 'তেতাল্লিশ', 'চৌচল্লিশ',
35
+ 'পঁয়তাল্লিশ', 'ছেচল্লিশ', 'সাতচল্লিশ', 'আটচল্লিশ', 'উনপঞ্চাশ',
36
+ 'পঞ্চাশ', 'একান্ন', 'বাহান্ন', 'তিপ্পান্ন', 'চুয়ান্ন', 'পঞ্চান্ন',
37
+ 'ছাপ্পান্ন', 'সাতান্ন', 'আটান্ন', 'উনষাট', 'ষাট',
38
+ 'একষট্টি', 'বাষট্টি', 'তেষট্টি', 'চৌষট্টি', 'পঁয়ষট্টি',
39
+ 'ছিষট্টি', 'সাতষট্টি', 'আটষট্টি', 'উনসত্তর', 'সত্তর',
40
+ 'একাত্তর ', 'বাহাত্তর', 'তিয়াত্তর', 'চুয়াত্তর', 'পঁচাত্তর',
41
+ 'ছিয়াত্তর', 'সাতাত্তর', 'আটাত্তর', 'উনআশি', 'আশি',
42
+ 'একাশি', 'বিরাশি', 'তিরাশি', 'চুরাশি', 'পঁচাশি',
43
+ 'ছিয়াশি', 'সাতাশি', 'আটাশি', 'উননব্বই', 'নব্বই',
44
+ 'একানব্বই', 'বিরানব্বই', 'তিরানব্বই', 'চুরানব্বই', 'পঁচানব্বই',
45
+ 'ছিয়ানব্বই', 'সাতানব্বই', 'আটানব্বই', 'নিরানব্বই'
46
+ ] # pragma: no cover
47
+ HAZAR = ' হাজার ' # pragma: no cover
48
+ LAKH = ' লাখ ' # pragma: no cover
49
+ KOTI = ' কোটি ' # pragma: no cover
50
+ MAX_NUMBER = 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 # noqa: E501 # pragma: no cover
51
+
52
+
53
+ class NumberTooLargeError(Exception):
54
+ """Custom exception for numbers that are too large."""
55
+
56
+
57
+ class Num2Word_BN:
58
+
59
+ def __init__(self):
60
+ self.negword = "ঋণাত্মক" # Bengali word for "negative"
61
+
62
+ @staticmethod
63
+ def str_to_number(number):
64
+ return abs(Decimal(str(number))) # pragma: no cover
65
+
66
+ @staticmethod
67
+ def parse_number(number: Decimal):
68
+ dosomik = str(number - int(number)).split('.')[1:]
69
+ dosomik_str = ''.join(dosomik) if dosomik else 0
70
+ return int(number), int(dosomik_str)
71
+
72
+ @staticmethod
73
+ def parse_paisa(number: Decimal):
74
+ # 1-99 for paisa count so two digits are valid.
75
+ paisa = str(number - int(number)).split('.')[1:]
76
+ paisa_str = ''.join(paisa) if paisa else 0
77
+
78
+ # this is need, when we parse to decimal it removes trailing 0 .
79
+ if paisa_str:
80
+ paisa_str = str(int(paisa_str) * 100)[:2]
81
+ return int(number), int(paisa_str)
82
+
83
+ def _is_smaller_than_max_number(self, number):
84
+ if MAX_NUMBER >= number:
85
+ return True
86
+ raise NumberTooLargeError(f'Too Large number maximum '
87
+ f'value={MAX_NUMBER}')
88
+
89
+ def _dosomik_to_bengali_word(self, number):
90
+ word = ''
91
+ for i in str(number):
92
+ word += ' ' + AKOK[int(i)]
93
+ return word
94
+
95
+ def _number_to_bengali_word(self, number):
96
+ if number == 0:
97
+ return 'শূন্য'
98
+
99
+ words = ''
100
+
101
+ if number >= 10 ** 7:
102
+ words += self._number_to_bengali_word(number // 10 ** 7) + KOTI
103
+ number %= 10 ** 7
104
+
105
+ if number >= 10 ** 5:
106
+ words += self._number_to_bengali_word(number // 10 ** 5) + LAKH
107
+ number %= 10 ** 5
108
+
109
+ if number >= 1000:
110
+ words += self._number_to_bengali_word(number // 1000) + HAZAR
111
+ number %= 1000
112
+
113
+ if number >= 100:
114
+ words += AKOK[number // 100] + 'শত '
115
+ number %= 100
116
+
117
+ if 10 <= number <= 99:
118
+ words += DOSOK[number - 10] + ' '
119
+ number = 0
120
+
121
+ if 0 < number < 10:
122
+ words += AKOK[number] + ' '
123
+
124
+ return words.strip()
125
+
126
+ def to_currency(self, val, currency='BDT', cents=True, separator=',',
127
+ adjective=False):
128
+ """
129
+ This function represent a number to word in bangla taka and paisa.
130
+ example:
131
+ 1 = এক টাকা,
132
+ 101 = একশত এক টাকা,
133
+ 9999.15 = নয় হাজার নয়শত নিরানব্বই টাকা পনের পয়সা
134
+ and so on.
135
+ """
136
+ # Check for too large numbers first
137
+ if abs(val) >= MAX_NUMBER:
138
+ raise NumberTooLargeError(
139
+ f"Number is too large. Max: {MAX_NUMBER}"
140
+ )
141
+
142
+ # Check if value has fractional cents
143
+ decimal_val = Decimal(str(val))
144
+ has_fractional_cents = (decimal_val * 100) % 1 != 0
145
+
146
+ # Handle integers specially - just add currency name without paisa
147
+ if isinstance(val, int):
148
+ number = self.str_to_number(val)
149
+ self._is_smaller_than_max_number(number)
150
+ return f'{self._number_to_bengali_word(int(number))} টাকা'.strip()
151
+
152
+ # For floats, include paisa
153
+ dosomik_word = None
154
+ number = self.str_to_number(val)
155
+ number, decimal_part = self.parse_paisa(number)
156
+ self._is_smaller_than_max_number(number)
157
+
158
+ # Always show paisa for floats, even if it's zero
159
+ if decimal_part == 0:
160
+ dosomik_word = ' শূন্য পয়সা' # "zero paisa" in Bengali
161
+ elif decimal_part > 0:
162
+ if has_fractional_cents:
163
+ # Handle fractional paisa using to_cardinal for fractional decimal values
164
+ fractional_paisa = float(decimal_part)
165
+ dosomik_word = f' {self.to_cardinal(fractional_paisa)} পয়সা'
166
+ else:
167
+ dosomik_word = f' {self._number_to_bengali_word(decimal_part)} পয়সা'
168
+
169
+ words = f'{self._number_to_bengali_word(number)} টাকা'
170
+
171
+ if dosomik_word:
172
+ return (words + dosomik_word).strip()
173
+ return words.strip()
174
+
175
+ def to_cardinal(self, number):
176
+ """
177
+ This function represent a number to word in bangla.
178
+ example:
179
+ 1 = এক,
180
+ 101 = একশত এক,
181
+ 9999 = নয় হাজার নয়শত নিরানব্বই
182
+ and so on.
183
+ """
184
+
185
+ # Check for negative before conversion
186
+ is_negative = False
187
+ if isinstance(number, (int, float, Decimal)) and number < 0:
188
+ is_negative = True
189
+ elif isinstance(number, str) and number.strip().startswith('-'):
190
+ is_negative = True
191
+
192
+ dosomik_word = None
193
+ number = self.str_to_number(number)
194
+ number, decimal_part = self.parse_number(number)
195
+ self._is_smaller_than_max_number(number)
196
+
197
+ if decimal_part > 0:
198
+ dosomik_word = f' দশমিক{self._dosomik_to_bengali_word(decimal_part)}' # noqa: E501
199
+
200
+ words = self._number_to_bengali_word(number)
201
+
202
+ if dosomik_word:
203
+ result = (words + dosomik_word).strip()
204
+ else:
205
+ result = words.strip()
206
+
207
+ if is_negative:
208
+ result = self.negword + ' ' + result
209
+ return result
210
+
211
+ def to_ordinal(self, number):
212
+ return self.to_cardinal(number)
213
+
214
+ def to_ordinal_num(self, number):
215
+ """
216
+ This function represent a number to ranking in bangla.
217
+ example:
218
+ 1 = প্রথম,
219
+ 2 = দ্বিতীয়,
220
+ 1001 = এক হাজার একতম
221
+ and so on.
222
+ """
223
+ self._is_smaller_than_max_number(number)
224
+
225
+ if number in range(1, 11):
226
+ return RANKING[number]
227
+ else:
228
+ rank = self.to_cardinal(int(abs(number)))
229
+ if rank.endswith('ত'):
230
+ return rank + 'ম'
231
+ return rank + 'তম'
232
+
233
+ def to_year(self, number):
234
+ """
235
+ This function represent a number to year in bangla.
236
+ example:
237
+ 2002 = দুই হাজার দুই সাল,
238
+ 2024 = দুই হাজার চব্বিশ সাল
239
+ and so on.
240
+ """
241
+ self._is_smaller_than_max_number(number)
242
+ return self.to_cardinal(int(abs(number))) + ' সাল'