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.
- num2words2/README.md +69 -0
- num2words2/__init__.py +196 -0
- num2words2/_version.py +3 -0
- num2words2/base.py +377 -0
- num2words2/compat.py +29 -0
- num2words2/currency.py +60 -0
- num2words2/lang_AF.py +202 -0
- num2words2/lang_AM.py +193 -0
- num2words2/lang_AR.py +435 -0
- num2words2/lang_AS.py +119 -0
- num2words2/lang_AZ.py +208 -0
- num2words2/lang_BA.py +119 -0
- num2words2/lang_BE.py +356 -0
- num2words2/lang_BG.py +393 -0
- num2words2/lang_BN.py +242 -0
- num2words2/lang_BO.py +122 -0
- num2words2/lang_BR.py +135 -0
- num2words2/lang_BS.py +189 -0
- num2words2/lang_CA.py +476 -0
- num2words2/lang_CE.py +546 -0
- num2words2/lang_CS.py +260 -0
- num2words2/lang_CY.py +574 -0
- num2words2/lang_DA.py +170 -0
- num2words2/lang_DE.py +180 -0
- num2words2/lang_EL.py +338 -0
- num2words2/lang_EN.py +118 -0
- num2words2/lang_EN_IN.py +26 -0
- num2words2/lang_EN_NG.py +45 -0
- num2words2/lang_EO.py +130 -0
- num2words2/lang_ES.py +399 -0
- num2words2/lang_ES_CO.py +37 -0
- num2words2/lang_ES_CR.py +38 -0
- num2words2/lang_ES_GT.py +37 -0
- num2words2/lang_ES_NI.py +35 -0
- num2words2/lang_ES_VE.py +43 -0
- num2words2/lang_ET.py +393 -0
- num2words2/lang_EU.py +158 -0
- num2words2/lang_EUR.py +104 -0
- num2words2/lang_FA.py +187 -0
- num2words2/lang_FI.py +759 -0
- num2words2/lang_FO.py +136 -0
- num2words2/lang_FR.py +133 -0
- num2words2/lang_FR_BE.py +49 -0
- num2words2/lang_FR_CH.py +48 -0
- num2words2/lang_FR_DZ.py +35 -0
- num2words2/lang_GL.py +135 -0
- num2words2/lang_GU.py +172 -0
- num2words2/lang_HA.py +293 -0
- num2words2/lang_HAW.py +144 -0
- num2words2/lang_HE.py +314 -0
- num2words2/lang_HI.py +243 -0
- num2words2/lang_HR.py +304 -0
- num2words2/lang_HT.py +136 -0
- num2words2/lang_HU.py +189 -0
- num2words2/lang_HY.py +364 -0
- num2words2/lang_ID.py +242 -0
- num2words2/lang_IS.py +208 -0
- num2words2/lang_IT.py +265 -0
- num2words2/lang_JA.py +615 -0
- num2words2/lang_JW.py +136 -0
- num2words2/lang_KA.py +169 -0
- num2words2/lang_KK.py +136 -0
- num2words2/lang_KM.py +173 -0
- num2words2/lang_KN.py +182 -0
- num2words2/lang_KO.py +175 -0
- num2words2/lang_KZ.py +161 -0
- num2words2/lang_LA.py +135 -0
- num2words2/lang_LB.py +135 -0
- num2words2/lang_LN.py +136 -0
- num2words2/lang_LO.py +149 -0
- num2words2/lang_LT.py +283 -0
- num2words2/lang_LV.py +266 -0
- num2words2/lang_MG.py +143 -0
- num2words2/lang_MI.py +151 -0
- num2words2/lang_MK.py +136 -0
- num2words2/lang_ML.py +159 -0
- num2words2/lang_MN.py +319 -0
- num2words2/lang_MR.py +187 -0
- num2words2/lang_MS.py +293 -0
- num2words2/lang_MT.py +159 -0
- num2words2/lang_MY.py +152 -0
- num2words2/lang_NE.py +157 -0
- num2words2/lang_NL.py +204 -0
- num2words2/lang_NN.py +136 -0
- num2words2/lang_NO.py +143 -0
- num2words2/lang_OC.py +135 -0
- num2words2/lang_PA.py +159 -0
- num2words2/lang_PL.py +336 -0
- num2words2/lang_PS.py +136 -0
- num2words2/lang_PT.py +280 -0
- num2words2/lang_PT_BR.py +224 -0
- num2words2/lang_RO.py +183 -0
- num2words2/lang_RU.py +570 -0
- num2words2/lang_SA.py +136 -0
- num2words2/lang_SD.py +136 -0
- num2words2/lang_SI.py +152 -0
- num2words2/lang_SK.py +250 -0
- num2words2/lang_SL.py +306 -0
- num2words2/lang_SN.py +447 -0
- num2words2/lang_SO.py +136 -0
- num2words2/lang_SQ.py +252 -0
- num2words2/lang_SR.py +254 -0
- num2words2/lang_SU.py +136 -0
- num2words2/lang_SV.py +169 -0
- num2words2/lang_SW.py +219 -0
- num2words2/lang_TA.py +303 -0
- num2words2/lang_TE.py +169 -0
- num2words2/lang_TET.py +285 -0
- num2words2/lang_TG.py +149 -0
- num2words2/lang_TH.py +214 -0
- num2words2/lang_TK.py +136 -0
- num2words2/lang_TL.py +145 -0
- num2words2/lang_TR.py +882 -0
- num2words2/lang_TT.py +136 -0
- num2words2/lang_UK.py +1101 -0
- num2words2/lang_UR.py +168 -0
- num2words2/lang_UZ.py +136 -0
- num2words2/lang_VI.py +143 -0
- num2words2/lang_WO.py +136 -0
- num2words2/lang_YI.py +135 -0
- num2words2/lang_YO.py +136 -0
- num2words2/lang_ZH.py +296 -0
- num2words2/lang_ZH_CN.py +85 -0
- num2words2/lang_ZH_HK.py +48 -0
- num2words2/lang_ZH_TW.py +198 -0
- num2words2/utils.py +35 -0
- num2words2-0.1.0.dev1.data/scripts/num2words2 +98 -0
- num2words2-0.1.0.dev1.dist-info/METADATA +244 -0
- num2words2-0.1.0.dev1.dist-info/RECORD +133 -0
- num2words2-0.1.0.dev1.dist-info/WHEEL +5 -0
- num2words2-0.1.0.dev1.dist-info/entry_points.txt +2 -0
- num2words2-0.1.0.dev1.dist-info/licenses/COPYING +145 -0
- 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))) + ' সাল'
|