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_PS.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
|
|
22
|
+
|
|
23
|
+
# Pashto language support
|
|
24
|
+
class Num2Word_PS(Num2Word_Base):
|
|
25
|
+
CURRENCY_FORMS = {
|
|
26
|
+
'AFN': (('افغانۍ', 'افغانۍ'), ('پول', 'پول')),
|
|
27
|
+
'USD': (('dollar', 'dollars'), ('cent', 'cents')),
|
|
28
|
+
'EUR': (('euro', 'euros'), ('cent', 'cents'))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def setup(self):
|
|
32
|
+
self.negword = "minus "
|
|
33
|
+
self.pointword = "point"
|
|
34
|
+
self.ones = ['', 'یو', 'دوه', 'درې', 'څلور', 'پنځه', 'شپږ', 'اووه', 'اته', 'نهه']
|
|
35
|
+
self.tens = ['', 'لس', 'شل', 'دېرش', 'څلوېښت', 'پنځوس', 'شپېته', 'اویا', 'اتیا', 'نوي']
|
|
36
|
+
self.hundred = "سل"
|
|
37
|
+
self.thousand = "زره"
|
|
38
|
+
self.million = "میلیون"
|
|
39
|
+
|
|
40
|
+
def to_cardinal(self, number):
|
|
41
|
+
"""Convert a number to its word representation in Pashto."""
|
|
42
|
+
n = str(number).strip()
|
|
43
|
+
|
|
44
|
+
if n.startswith('-'):
|
|
45
|
+
n = n[1:]
|
|
46
|
+
ret = self.negword
|
|
47
|
+
else:
|
|
48
|
+
ret = ""
|
|
49
|
+
|
|
50
|
+
if '.' in n:
|
|
51
|
+
left, right = n.split('.', 1)
|
|
52
|
+
ret += self._int_to_word(int(left)) + " " + self.pointword + " "
|
|
53
|
+
for digit in right:
|
|
54
|
+
ret += self._int_to_word(int(digit)) + " "
|
|
55
|
+
return ret.strip()
|
|
56
|
+
else:
|
|
57
|
+
return (ret + self._int_to_word(int(n))).strip()
|
|
58
|
+
|
|
59
|
+
def _int_to_word(self, number):
|
|
60
|
+
"""Convert an integer to its word representation in Pashto."""
|
|
61
|
+
if number == 0:
|
|
62
|
+
return self.ones[0] if self.ones[0] else "zero"
|
|
63
|
+
|
|
64
|
+
if number < 0:
|
|
65
|
+
return self.negword + self._int_to_word(abs(number))
|
|
66
|
+
elif number < 10:
|
|
67
|
+
return self.ones[number]
|
|
68
|
+
elif number < 100:
|
|
69
|
+
tens_val = number // 10
|
|
70
|
+
ones_val = number % 10
|
|
71
|
+
if ones_val == 0:
|
|
72
|
+
return self.tens[tens_val]
|
|
73
|
+
else:
|
|
74
|
+
return self.tens[tens_val] + " " + self.ones[ones_val]
|
|
75
|
+
elif number < 1000:
|
|
76
|
+
hundreds_val = number // 100
|
|
77
|
+
remainder = number % 100
|
|
78
|
+
result = self.ones[hundreds_val] + " " + self.hundred
|
|
79
|
+
if remainder:
|
|
80
|
+
result += " " + self._int_to_word(remainder)
|
|
81
|
+
return result
|
|
82
|
+
elif number < 1000000:
|
|
83
|
+
thousands_val = number // 1000
|
|
84
|
+
remainder = number % 1000
|
|
85
|
+
result = self._int_to_word(thousands_val) + " " + self.thousand
|
|
86
|
+
if remainder:
|
|
87
|
+
result += " " + self._int_to_word(remainder)
|
|
88
|
+
return result
|
|
89
|
+
elif number < 1000000000:
|
|
90
|
+
millions_val = number // 1000000
|
|
91
|
+
remainder = number % 1000000
|
|
92
|
+
result = self._int_to_word(millions_val) + " " + self.million
|
|
93
|
+
if remainder:
|
|
94
|
+
result += " " + self._int_to_word(remainder)
|
|
95
|
+
return result
|
|
96
|
+
else:
|
|
97
|
+
return str(number) # Fallback for very large numbers
|
|
98
|
+
|
|
99
|
+
def to_ordinal(self, number):
|
|
100
|
+
"""Convert to ordinal in Pashto."""
|
|
101
|
+
cardinal = self.to_cardinal(number)
|
|
102
|
+
# Language-specific ordinal suffix
|
|
103
|
+
return cardinal + "-م"
|
|
104
|
+
|
|
105
|
+
def to_ordinal_num(self, number):
|
|
106
|
+
"""Convert to abbreviated ordinal."""
|
|
107
|
+
return str(number) + "."
|
|
108
|
+
|
|
109
|
+
def to_year(self, val, longval=True):
|
|
110
|
+
"""Convert to year in Pashto."""
|
|
111
|
+
return self.to_cardinal(val)
|
|
112
|
+
|
|
113
|
+
def to_currency(self, val, currency='AFN', cents=True, separator=' ', adjective=False):
|
|
114
|
+
"""Convert to currency in Pashto."""
|
|
115
|
+
is_negative = False
|
|
116
|
+
if val < 0:
|
|
117
|
+
is_negative = True
|
|
118
|
+
val = abs(val)
|
|
119
|
+
|
|
120
|
+
parts = str(val).split('.')
|
|
121
|
+
left = int(parts[0]) if parts[0] else 0
|
|
122
|
+
right = int(parts[1][:2].ljust(2, '0')) if len(parts) > 1 and parts[1] else 0
|
|
123
|
+
|
|
124
|
+
cr1, cr2 = self.CURRENCY_FORMS.get(currency, list(self.CURRENCY_FORMS.values())[0])
|
|
125
|
+
|
|
126
|
+
left_str = self._int_to_word(left)
|
|
127
|
+
result = left_str + " " + (cr1[1] if left != 1 else cr1[0])
|
|
128
|
+
|
|
129
|
+
if cents and right:
|
|
130
|
+
cents_str = self._int_to_word(right)
|
|
131
|
+
result += separator + cents_str + " " + (cr2[1] if right != 1 else cr2[0])
|
|
132
|
+
|
|
133
|
+
if is_negative:
|
|
134
|
+
result = self.negword + result
|
|
135
|
+
|
|
136
|
+
return result.strip()
|
num2words2/lang_PT.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
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 division, unicode_literals
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
from .lang_EUR import Num2Word_EUR
|
|
23
|
+
|
|
24
|
+
DOLLAR = ('dólar', 'dólares')
|
|
25
|
+
CENTS = ('cêntimo', 'cêntimos')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Num2Word_PT(Num2Word_EUR):
|
|
29
|
+
|
|
30
|
+
CURRENCY_FORMS = {
|
|
31
|
+
'AUD': (DOLLAR, CENTS),
|
|
32
|
+
'BRL': (('real', 'reais'), ('centavo', 'centavos')),
|
|
33
|
+
'CAD': (DOLLAR, CENTS),
|
|
34
|
+
'EUR': (('euro', 'euros'), CENTS),
|
|
35
|
+
'GBP': (('libra', 'libras'), ('péni', 'pence')),
|
|
36
|
+
'USD': (DOLLAR, CENTS),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
GIGA_SUFFIX = None
|
|
40
|
+
MEGA_SUFFIX = "ilião"
|
|
41
|
+
|
|
42
|
+
def setup(self):
|
|
43
|
+
super(Num2Word_PT, self).setup()
|
|
44
|
+
lows = ["quatr", "tr", "b", "m"]
|
|
45
|
+
self.high_numwords = self.gen_high_numwords([], [], lows)
|
|
46
|
+
self.negword = "menos "
|
|
47
|
+
self.pointword = "vírgula"
|
|
48
|
+
self.exclude_title = ["e", "vírgula", "menos"]
|
|
49
|
+
|
|
50
|
+
self.mid_numwords = [
|
|
51
|
+
(1000, "mil"), (100, "cem"), (90, "noventa"),
|
|
52
|
+
(80, "oitenta"), (70, "setenta"), (60, "sessenta"),
|
|
53
|
+
(50, "cinquenta"), (40, "quarenta"), (30, "trinta")
|
|
54
|
+
]
|
|
55
|
+
self.low_numwords = [
|
|
56
|
+
"vinte", "dezanove", "dezoito", "dezassete", "dezasseis",
|
|
57
|
+
"quinze", "catorze", "treze", "doze", "onze", "dez",
|
|
58
|
+
"nove", "oito", "sete", "seis", "cinco", "quatro", "três", "dois",
|
|
59
|
+
"um", "zero"
|
|
60
|
+
]
|
|
61
|
+
self.ords = [
|
|
62
|
+
{
|
|
63
|
+
0: "",
|
|
64
|
+
1: "primeiro",
|
|
65
|
+
2: "segundo",
|
|
66
|
+
3: "terceiro",
|
|
67
|
+
4: "quarto",
|
|
68
|
+
5: "quinto",
|
|
69
|
+
6: "sexto",
|
|
70
|
+
7: "sétimo",
|
|
71
|
+
8: "oitavo",
|
|
72
|
+
9: "nono",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
0: "",
|
|
76
|
+
1: "décimo",
|
|
77
|
+
2: "vigésimo",
|
|
78
|
+
3: "trigésimo",
|
|
79
|
+
4: "quadragésimo",
|
|
80
|
+
5: "quinquagésimo",
|
|
81
|
+
6: "sexagésimo",
|
|
82
|
+
7: "septuagésimo",
|
|
83
|
+
8: "octogésimo",
|
|
84
|
+
9: "nonagésimo",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
0: "",
|
|
88
|
+
1: "centésimo",
|
|
89
|
+
2: "ducentésimo",
|
|
90
|
+
3: "tricentésimo",
|
|
91
|
+
4: "quadrigentésimo",
|
|
92
|
+
5: "quingentésimo",
|
|
93
|
+
6: "seiscentésimo",
|
|
94
|
+
7: "septigentésimo",
|
|
95
|
+
8: "octigentésimo",
|
|
96
|
+
9: "nongentésimo",
|
|
97
|
+
},
|
|
98
|
+
]
|
|
99
|
+
self.thousand_separators = {
|
|
100
|
+
3: "milésimo",
|
|
101
|
+
6: "milionésimo",
|
|
102
|
+
9: "milésimo milionésimo",
|
|
103
|
+
12: "bilionésimo",
|
|
104
|
+
15: "milésimo bilionésimo"
|
|
105
|
+
}
|
|
106
|
+
self.hundreds = {
|
|
107
|
+
1: "cento",
|
|
108
|
+
2: "duzentos",
|
|
109
|
+
3: "trezentos",
|
|
110
|
+
4: "quatrocentos",
|
|
111
|
+
5: "quinhentos",
|
|
112
|
+
6: "seiscentos",
|
|
113
|
+
7: "setecentos",
|
|
114
|
+
8: "oitocentos",
|
|
115
|
+
9: "novecentos",
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
def merge(self, curr, next):
|
|
119
|
+
ctext, cnum, ntext, nnum = curr + next
|
|
120
|
+
|
|
121
|
+
if cnum == 1:
|
|
122
|
+
if nnum < 1000000:
|
|
123
|
+
return next
|
|
124
|
+
ctext = "um"
|
|
125
|
+
elif cnum == 100 and not nnum % 1000 == 0:
|
|
126
|
+
ctext = "cento"
|
|
127
|
+
|
|
128
|
+
if nnum < cnum:
|
|
129
|
+
if cnum < 100:
|
|
130
|
+
return ("%s e %s" % (ctext, ntext), cnum + nnum)
|
|
131
|
+
return ("%s e %s" % (ctext, ntext), cnum + nnum)
|
|
132
|
+
|
|
133
|
+
elif (not nnum % 1000000000) and cnum > 1:
|
|
134
|
+
ntext = ntext[:-4] + "liões"
|
|
135
|
+
elif (not nnum % 1000000) and cnum > 1:
|
|
136
|
+
ntext = ntext[:-4] + "lhões"
|
|
137
|
+
# correct "milião" to "milhão"
|
|
138
|
+
if ntext == 'milião':
|
|
139
|
+
ntext = 'milhão'
|
|
140
|
+
if nnum == 100:
|
|
141
|
+
ctext = self.hundreds[cnum]
|
|
142
|
+
ntext = ""
|
|
143
|
+
|
|
144
|
+
else:
|
|
145
|
+
ntext = " " + ntext
|
|
146
|
+
|
|
147
|
+
return (ctext + ntext, cnum * nnum)
|
|
148
|
+
|
|
149
|
+
def to_cardinal(self, value):
|
|
150
|
+
result = super(Num2Word_PT, self).to_cardinal(value)
|
|
151
|
+
|
|
152
|
+
# Transforms "mil e cento e catorze" into "mil cento e catorze"
|
|
153
|
+
# Transforms "cem milhões e duzentos mil e duzentos e dez" em "cem
|
|
154
|
+
# milhões duzentos mil duzentos e dez" but "cem milhões e duzentos
|
|
155
|
+
# mil e duzentos" in "cem milhões duzentos mil e duzentos" and not in
|
|
156
|
+
# "cem milhões duzentos mil duzentos"
|
|
157
|
+
for ext in (
|
|
158
|
+
'mil', 'milhão', 'milhões', 'mil milhões',
|
|
159
|
+
'bilião', 'biliões', 'mil biliões'):
|
|
160
|
+
if re.match('.*{} e \\w*entos? (?=.*e)'.format(ext), result):
|
|
161
|
+
result = result.replace(
|
|
162
|
+
'{} e'.format(ext), '{}'.format(ext)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return result
|
|
166
|
+
|
|
167
|
+
# for the ordinal conversion the code is similar to pt_BR code,
|
|
168
|
+
# although there are other rules that are probably more correct in
|
|
169
|
+
# Portugal. Concerning numbers from 2000th on, saying "dois
|
|
170
|
+
# milésimos" instead of "segundo milésimo" (the first number
|
|
171
|
+
# would be used in the cardinal form instead of the ordinal) is better.
|
|
172
|
+
# This was not implemented.
|
|
173
|
+
# source:
|
|
174
|
+
# https://ciberduvidas.iscte-iul.pt/consultorio/perguntas/a-forma-por-extenso-de-2000-e-de-outros-ordinais/16428
|
|
175
|
+
def to_ordinal(self, value):
|
|
176
|
+
# Before changing this function remember this is used by pt-BR
|
|
177
|
+
# so act accordingly
|
|
178
|
+
self.verify_ordinal(value)
|
|
179
|
+
|
|
180
|
+
result = []
|
|
181
|
+
value = str(value)
|
|
182
|
+
thousand_separator = ''
|
|
183
|
+
|
|
184
|
+
for idx, char in enumerate(value[::-1]):
|
|
185
|
+
if idx and idx % 3 == 0:
|
|
186
|
+
thousand_separator = self.thousand_separators[idx]
|
|
187
|
+
|
|
188
|
+
if char != '0' and thousand_separator:
|
|
189
|
+
# avoiding "segundo milionésimo milésimo" for 6000000,
|
|
190
|
+
# for instance
|
|
191
|
+
result.append(thousand_separator)
|
|
192
|
+
thousand_separator = ''
|
|
193
|
+
|
|
194
|
+
result.append(self.ords[idx % 3][int(char)])
|
|
195
|
+
|
|
196
|
+
result = ' '.join(result[::-1])
|
|
197
|
+
result = result.strip()
|
|
198
|
+
result = re.sub('\\s+', ' ', result)
|
|
199
|
+
|
|
200
|
+
if result.startswith('primeiro') and value != '1':
|
|
201
|
+
# avoiding "primeiro milésimo", "primeiro milionésimo" and so on
|
|
202
|
+
result = result[9:]
|
|
203
|
+
|
|
204
|
+
return result
|
|
205
|
+
|
|
206
|
+
def to_ordinal_num(self, value):
|
|
207
|
+
# Before changing this function remember this is used by pt-BR
|
|
208
|
+
# so act accordingly
|
|
209
|
+
self.verify_ordinal(value)
|
|
210
|
+
return "%sº" % (value)
|
|
211
|
+
|
|
212
|
+
def to_year(self, val, longval=True):
|
|
213
|
+
# Before changing this function remember this is used by pt-BR
|
|
214
|
+
# so act accordingly
|
|
215
|
+
if val < 0:
|
|
216
|
+
return self.to_cardinal(abs(val)) + ' antes de Cristo'
|
|
217
|
+
return self.to_cardinal(val)
|
|
218
|
+
|
|
219
|
+
def to_currency(self, val, currency='EUR', cents=True, separator=' e',
|
|
220
|
+
adjective=False):
|
|
221
|
+
# Track if input was originally an integer (not float)
|
|
222
|
+
is_integer = isinstance(val, int)
|
|
223
|
+
|
|
224
|
+
# Handle integers specially - just add currency name without cents
|
|
225
|
+
if is_integer:
|
|
226
|
+
try:
|
|
227
|
+
cr1, cr2 = self.CURRENCY_FORMS[currency]
|
|
228
|
+
except KeyError:
|
|
229
|
+
return self.to_cardinal(val)
|
|
230
|
+
|
|
231
|
+
# change negword for currency
|
|
232
|
+
backup_negword = self.negword
|
|
233
|
+
self.negword = self.negword[:-1]
|
|
234
|
+
|
|
235
|
+
minus_str = "%s " % self.negword.strip() if val < 0 else ""
|
|
236
|
+
abs_val = abs(int(val) if isinstance(val, float) else val)
|
|
237
|
+
money_str = self.to_cardinal(abs_val)
|
|
238
|
+
|
|
239
|
+
# restore negword
|
|
240
|
+
self.negword = backup_negword
|
|
241
|
+
|
|
242
|
+
# Proper pluralization for currency
|
|
243
|
+
if abs_val == 1:
|
|
244
|
+
currency_str = cr1[0] # singular
|
|
245
|
+
else:
|
|
246
|
+
currency_str = cr1[1] if len(cr1) > 1 else cr1[0] # plural
|
|
247
|
+
|
|
248
|
+
result = "%s%s %s" % (minus_str, money_str, currency_str)
|
|
249
|
+
|
|
250
|
+
# transforms "milhões euros" em "milhões de euros"
|
|
251
|
+
for ext in ('milhão', 'milhões', 'bilião', 'biliões', 'trilião', 'triliões'):
|
|
252
|
+
if re.match('.*{} (?={})'.format(ext, currency_str), result):
|
|
253
|
+
result = result.replace(
|
|
254
|
+
'{}'.format(ext), '{} de'.format(ext), 1
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return result.strip()
|
|
258
|
+
|
|
259
|
+
# For floats with non-zero decimal part, use the parent class implementation
|
|
260
|
+
backup_negword = self.negword
|
|
261
|
+
self.negword = self.negword[:-1]
|
|
262
|
+
result = super(Num2Word_PT, self).to_currency(
|
|
263
|
+
val, currency=currency, cents=cents, separator=separator,
|
|
264
|
+
adjective=adjective)
|
|
265
|
+
# undo the change on negword
|
|
266
|
+
self.negword = backup_negword
|
|
267
|
+
|
|
268
|
+
# transforms "milhões euros" em "milhões de euros"
|
|
269
|
+
cr1, _ = self.CURRENCY_FORMS[currency]
|
|
270
|
+
|
|
271
|
+
for ext in (
|
|
272
|
+
'milhão', 'milhões', 'bilião',
|
|
273
|
+
'biliões', 'trilião', 'triliões'):
|
|
274
|
+
if re.match('.*{} (?={})'.format(ext, cr1[1]), result):
|
|
275
|
+
result = result.replace(
|
|
276
|
+
'{}'.format(ext), '{} de'.format(ext), 1
|
|
277
|
+
)
|
|
278
|
+
# For floats, keep "e zero cêntimos" to show it's a precise amount
|
|
279
|
+
# Only remove for integers (but we handle integers separately now)
|
|
280
|
+
return result
|
num2words2/lang_PT_BR.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
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 print_function, unicode_literals
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
from . import lang_PT
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def negativeword(self):
|
|
26
|
+
return self.negword
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Num2Word_PT_BR(lang_PT.Num2Word_PT):
|
|
30
|
+
negword = 'menos '
|
|
31
|
+
|
|
32
|
+
CURRENCY_FORMS = {
|
|
33
|
+
'BRL': (('real', 'reais'), ('centavo', 'centavos')),
|
|
34
|
+
'EUR': (('euro', 'euros'), ('cêntimo', 'cêntimos')),
|
|
35
|
+
'USD': (('dólar', 'dólares'), ('centavo', 'centavos')),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
super(Num2Word_PT_BR, self).__init__()
|
|
40
|
+
self.negword = 'menos '
|
|
41
|
+
|
|
42
|
+
def setup(self):
|
|
43
|
+
# First call parent setup
|
|
44
|
+
super(Num2Word_PT_BR, self).setup()
|
|
45
|
+
|
|
46
|
+
# Brazilian Portuguese uses different spelling for 16, 17, 19
|
|
47
|
+
self.low_numwords = [
|
|
48
|
+
"vinte", "dezenove", "dezoito", "dezessete", "dezesseis",
|
|
49
|
+
"quinze", "catorze", "treze", "doze", "onze", "dez",
|
|
50
|
+
"nove", "oito", "sete", "seis", "cinco", "quatro", "três", "dois",
|
|
51
|
+
"um", "zero"
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# Override thousand separators for Brazilian Portuguese ordinals
|
|
55
|
+
# Brazilian uses short scale: bilhão = 10^9, trilhão = 10^12
|
|
56
|
+
self.thousand_separators = {
|
|
57
|
+
3: "milésimo",
|
|
58
|
+
6: "milionésimo",
|
|
59
|
+
9: "bilionésimo", # Brazilian billion = 10^9
|
|
60
|
+
12: "trilionésimo", # Brazilian trillion = 10^12
|
|
61
|
+
15: "quatrilionésimo" # Brazilian quadrillion = 10^15
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def merge(self, curr, next):
|
|
65
|
+
ctext, cnum, ntext, nnum = curr + next
|
|
66
|
+
|
|
67
|
+
if cnum == 1:
|
|
68
|
+
if nnum < 1000000:
|
|
69
|
+
return (ntext, cnum * nnum)
|
|
70
|
+
ctext = "um"
|
|
71
|
+
elif cnum == 100 and nnum > 0 and nnum < 1000:
|
|
72
|
+
# In Brazilian Portuguese, 100 + any number up to 999 becomes "cento"
|
|
73
|
+
# But 100 * 1000 = 100000 stays as "cem mil"
|
|
74
|
+
ctext = "cento"
|
|
75
|
+
|
|
76
|
+
if nnum > cnum:
|
|
77
|
+
# Multiplicative case (e.g., "dois mil")
|
|
78
|
+
if (not nnum % 1000000) and cnum > 1:
|
|
79
|
+
# Handle Brazilian scale for millions
|
|
80
|
+
if nnum == 1000000:
|
|
81
|
+
ntext = "milhões"
|
|
82
|
+
else:
|
|
83
|
+
ntext = ntext[:-4] + "lhões"
|
|
84
|
+
|
|
85
|
+
if nnum == 100:
|
|
86
|
+
ctext = self.hundreds[cnum]
|
|
87
|
+
ntext = ""
|
|
88
|
+
else:
|
|
89
|
+
ntext = " " + ntext
|
|
90
|
+
|
|
91
|
+
return (ctext + ntext, cnum * nnum)
|
|
92
|
+
else:
|
|
93
|
+
# Additive case (e.g., "vinte e dois")
|
|
94
|
+
return ("%s e %s" % (ctext, ntext), cnum + nnum)
|
|
95
|
+
|
|
96
|
+
def to_cardinal(self, value):
|
|
97
|
+
# Handle negative numbers
|
|
98
|
+
if value < 0:
|
|
99
|
+
return "%s%s" % (self.negword, self.to_cardinal(-value))
|
|
100
|
+
|
|
101
|
+
# Handle billions and trillions specially for Brazilian Portuguese short scale
|
|
102
|
+
# Brazil uses: bilhão = 10^9, trilhão = 10^12 (short scale)
|
|
103
|
+
# Parent PT uses: bilião = 10^12, trilião = 10^18 (European long scale)
|
|
104
|
+
|
|
105
|
+
if value >= 1000000000:
|
|
106
|
+
# Handle trillions (10^12)
|
|
107
|
+
if value >= 1000000000000:
|
|
108
|
+
trillions, remainder = divmod(value, 1000000000000)
|
|
109
|
+
if trillions == 1:
|
|
110
|
+
result = "um trilhão"
|
|
111
|
+
else:
|
|
112
|
+
# Recursively convert the number of trillions
|
|
113
|
+
result = "%s trilhões" % self.to_cardinal(trillions)
|
|
114
|
+
|
|
115
|
+
if remainder:
|
|
116
|
+
if remainder >= 1000000000:
|
|
117
|
+
# Has billions
|
|
118
|
+
billions, rest = divmod(remainder, 1000000000)
|
|
119
|
+
if billions == 1:
|
|
120
|
+
result += ", um bilhão"
|
|
121
|
+
else:
|
|
122
|
+
result += ", %s bilhões" % self.to_cardinal(billions)
|
|
123
|
+
if rest:
|
|
124
|
+
# Call parent for the rest (millions and below)
|
|
125
|
+
rest_str = super(Num2Word_PT_BR, self).to_cardinal(rest)
|
|
126
|
+
# Fix "milião" to "milhão" if it appears
|
|
127
|
+
rest_str = rest_str.replace("milião", "milhão").replace("miliões", "milhões")
|
|
128
|
+
result += " e %s" % rest_str
|
|
129
|
+
else:
|
|
130
|
+
# No billions, just add remainder
|
|
131
|
+
remainder_str = super(Num2Word_PT_BR, self).to_cardinal(remainder)
|
|
132
|
+
# Fix "milião" to "milhão" if it appears
|
|
133
|
+
remainder_str = remainder_str.replace("milião", "milhão").replace("miliões", "milhões")
|
|
134
|
+
result += " e %s" % remainder_str
|
|
135
|
+
return result
|
|
136
|
+
|
|
137
|
+
# Handle billions (10^9)
|
|
138
|
+
else:
|
|
139
|
+
billions, remainder = divmod(value, 1000000000)
|
|
140
|
+
if billions == 1:
|
|
141
|
+
result = "um bilhão"
|
|
142
|
+
else:
|
|
143
|
+
# Recursively convert the number of billions
|
|
144
|
+
result = "%s bilhões" % self.to_cardinal(billions)
|
|
145
|
+
|
|
146
|
+
if remainder:
|
|
147
|
+
remainder_str = super(Num2Word_PT_BR, self).to_cardinal(remainder)
|
|
148
|
+
# Fix "milião" to "milhão" if it appears
|
|
149
|
+
remainder_str = remainder_str.replace("milião", "milhão").replace("miliões", "milhões")
|
|
150
|
+
# Use comma if remainder starts with hundreds (i.e., >= 100000)
|
|
151
|
+
if remainder >= 100000:
|
|
152
|
+
result += ", %s" % remainder_str
|
|
153
|
+
else:
|
|
154
|
+
result += " e %s" % remainder_str
|
|
155
|
+
return result
|
|
156
|
+
else:
|
|
157
|
+
# For values below 1 billion but above million, handle specially
|
|
158
|
+
if value >= 1000000:
|
|
159
|
+
millions, remainder = divmod(value, 1000000)
|
|
160
|
+
if millions == 1:
|
|
161
|
+
result = "um milhão"
|
|
162
|
+
else:
|
|
163
|
+
result = "%s milhões" % super(Num2Word_PT_BR, self).to_cardinal(millions)
|
|
164
|
+
|
|
165
|
+
if remainder:
|
|
166
|
+
# Use our own to_cardinal for remainder to ensure proper formatting
|
|
167
|
+
remainder_str = self.to_cardinal(remainder)
|
|
168
|
+
# Use comma if remainder starts with hundreds (i.e., >= 100)
|
|
169
|
+
if remainder >= 100:
|
|
170
|
+
result += ", %s" % remainder_str
|
|
171
|
+
else:
|
|
172
|
+
result += " e %s" % remainder_str
|
|
173
|
+
return result
|
|
174
|
+
else:
|
|
175
|
+
# For values below 1 million, use parent implementation
|
|
176
|
+
result = super(Num2Word_PT_BR, self).to_cardinal(value)
|
|
177
|
+
# Fix "milião" to "milhão" throughout (shouldn't happen but just in case)
|
|
178
|
+
result = result.replace("milião", "milhão").replace("miliões", "milhões")
|
|
179
|
+
|
|
180
|
+
# Transforms "mil e cento e catorze" into "mil, cento e catorze"
|
|
181
|
+
for ext in (
|
|
182
|
+
'mil', 'milhão', 'milhões', 'bilhão', 'bilhões',
|
|
183
|
+
'trilhão', 'trilhões', 'quatrilhão', 'quatrilhões'):
|
|
184
|
+
# Check if pattern is "thousand-word hundreds-word e something"
|
|
185
|
+
if re.search('{} \\w*ento'.format(ext), result):
|
|
186
|
+
result = re.sub(
|
|
187
|
+
'({}) (\\w*entos?)'.format(ext), r'\1, \2', result, count=1
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
return result
|
|
191
|
+
|
|
192
|
+
def to_currency(self, val, currency='BRL', cents=True, separator=' e', adjective=False):
|
|
193
|
+
from decimal import Decimal
|
|
194
|
+
|
|
195
|
+
# Check if this is a whole number (integer or decimal with .00)
|
|
196
|
+
if isinstance(val, Decimal):
|
|
197
|
+
if val % 1 == 0:
|
|
198
|
+
# Convert to integer if it's a whole number
|
|
199
|
+
val = int(val)
|
|
200
|
+
elif isinstance(val, float):
|
|
201
|
+
if val == int(val):
|
|
202
|
+
val = int(val)
|
|
203
|
+
|
|
204
|
+
# Use parent class implementation with our currency forms
|
|
205
|
+
result = super(Num2Word_PT_BR, self).to_currency(
|
|
206
|
+
val, currency=currency, cents=cents, separator=separator, adjective=adjective
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# For Brazilian Portuguese, we need to add "de" after millions/billions/trillions
|
|
210
|
+
# when they are round numbers (no thousands/hundreds after)
|
|
211
|
+
if isinstance(val, int):
|
|
212
|
+
try:
|
|
213
|
+
cr1, cr2 = self.CURRENCY_FORMS[currency]
|
|
214
|
+
currency_str = cr1[1] if abs(val) != 1 and len(cr1) > 1 else cr1[0]
|
|
215
|
+
# Add "de" for Brazilian terms (milhão/milhões, bilhão/bilhões, trilhão/trilhões)
|
|
216
|
+
for ext in ('milhão', 'milhões', 'bilhão', 'bilhões', 'trilhão', 'trilhões'):
|
|
217
|
+
if re.match('.*{} (?={})'.format(ext, currency_str), result):
|
|
218
|
+
result = result.replace(
|
|
219
|
+
'{}'.format(ext), '{} de'.format(ext), 1
|
|
220
|
+
)
|
|
221
|
+
except KeyError:
|
|
222
|
+
pass # Currency not in our forms, use parent's result as-is
|
|
223
|
+
|
|
224
|
+
return result
|