Initial commit (Clean history)

This commit is contained in:
anhduy-tech
2025-12-30 11:27:14 +07:00
commit ef48c93de0
19255 changed files with 3248867 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import unicode_literals
from . import (lang_AM, lang_AR, lang_AZ, lang_BE, lang_BN, lang_CA, lang_CE,
lang_CS, lang_CY, lang_DA, lang_DE, lang_EN, lang_EN_IN,
lang_EN_NG, lang_EO, lang_ES, lang_ES_CO, lang_ES_CR,
lang_ES_GT, lang_ES_NI, lang_ES_VE, lang_FA, lang_FI, lang_FR,
lang_FR_BE, lang_FR_CH, lang_FR_DZ, lang_HE, lang_HU, lang_ID,
lang_IS, lang_IT, lang_JA, lang_KN, lang_KO, lang_KZ, lang_LT,
lang_LV, lang_NL, lang_NO, lang_PL, lang_PT, lang_PT_BR,
lang_RO, lang_RU, lang_SK, lang_SL, lang_SR, lang_SV, lang_TE,
lang_TET, lang_TG, lang_TH, lang_TR, lang_UK, lang_VI)
CONVERTER_CLASSES = {
'am': lang_AM.Num2Word_AM(),
'ar': lang_AR.Num2Word_AR(),
'az': lang_AZ.Num2Word_AZ(),
'be': lang_BE.Num2Word_BE(),
'bn': lang_BN.Num2Word_BN(),
'ca': lang_CA.Num2Word_CA(),
'ce': lang_CE.Num2Word_CE(),
'cs': lang_CS.Num2Word_CS(),
'cy': lang_CY.Num2Word_CY(),
'en': lang_EN.Num2Word_EN(),
'en_IN': lang_EN_IN.Num2Word_EN_IN(),
'en_NG': lang_EN_NG.Num2Word_EN_NG(),
'fa': lang_FA.Num2Word_FA(),
'fr': lang_FR.Num2Word_FR(),
'fr_CH': lang_FR_CH.Num2Word_FR_CH(),
'fr_BE': lang_FR_BE.Num2Word_FR_BE(),
'fr_DZ': lang_FR_DZ.Num2Word_FR_DZ(),
'de': lang_DE.Num2Word_DE(),
'fi': lang_FI.Num2Word_FI(),
'eo': lang_EO.Num2Word_EO(),
'es': lang_ES.Num2Word_ES(),
'es_CO': lang_ES_CO.Num2Word_ES_CO(),
'es_CR': lang_ES_CR.Num2Word_ES_CR(),
'es_GT': lang_ES_GT.Num2Word_ES_GT(),
'es_NI': lang_ES_NI.Num2Word_ES_NI(),
'es_VE': lang_ES_VE.Num2Word_ES_VE(),
'id': lang_ID.Num2Word_ID(),
'ja': lang_JA.Num2Word_JA(),
'kn': lang_KN.Num2Word_KN(),
'ko': lang_KO.Num2Word_KO(),
'kz': lang_KZ.Num2Word_KZ(),
'lt': lang_LT.Num2Word_LT(),
'lv': lang_LV.Num2Word_LV(),
'pl': lang_PL.Num2Word_PL(),
'ro': lang_RO.Num2Word_RO(),
'ru': lang_RU.Num2Word_RU(),
'sk': lang_SK.Num2Word_SK(),
'sl': lang_SL.Num2Word_SL(),
'sr': lang_SR.Num2Word_SR(),
'sv': lang_SV.Num2Word_SV(),
'no': lang_NO.Num2Word_NO(),
'da': lang_DA.Num2Word_DA(),
'pt': lang_PT.Num2Word_PT(),
'pt_BR': lang_PT_BR.Num2Word_PT_BR(),
'he': lang_HE.Num2Word_HE(),
'it': lang_IT.Num2Word_IT(),
'vi': lang_VI.Num2Word_VI(),
'tg': lang_TG.Num2Word_TG(),
'th': lang_TH.Num2Word_TH(),
'tr': lang_TR.Num2Word_TR(),
'nl': lang_NL.Num2Word_NL(),
'uk': lang_UK.Num2Word_UK(),
'te': lang_TE.Num2Word_TE(),
'tet': lang_TET.Num2Word_TET(),
'hu': lang_HU.Num2Word_HU(),
'is': lang_IS.Num2Word_IS(),
}
CONVERTES_TYPES = ['cardinal', 'ordinal', 'ordinal_num', 'year', 'currency']
def num2words(number, ordinal=False, lang='en', to='cardinal', **kwargs):
# We try the full language first
if lang not in CONVERTER_CLASSES:
# ... and then try only the first 2 letters
lang = lang[:2]
if lang not in CONVERTER_CLASSES:
raise NotImplementedError()
converter = CONVERTER_CLASSES[lang]
if isinstance(number, str):
number = converter.str_to_number(number)
# backwards compatible
if ordinal:
to = 'ordinal'
if to not in CONVERTES_TYPES:
raise NotImplementedError()
return getattr(converter, 'to_{}'.format(to))(number, **kwargs)

View File

@@ -0,0 +1,310 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import unicode_literals
import math
from collections import OrderedDict
from decimal import Decimal
from .compat import to_s
from .currency import parse_currency_parts, prefix_currency
class Num2Word_Base(object):
CURRENCY_FORMS = {}
CURRENCY_ADJECTIVES = {}
def __init__(self):
self.is_title = False
self.precision = 2
self.exclude_title = []
self.negword = "(-) "
self.pointword = "(.)"
self.errmsg_nonnum = "type(%s) not in [long, int, float]"
self.errmsg_floatord = "Cannot treat float %s as ordinal."
self.errmsg_negord = "Cannot treat negative num %s as ordinal."
self.errmsg_toobig = "abs(%s) must be less than %s."
self.setup()
# uses cards
if any(hasattr(self, field) for field in
['high_numwords', 'mid_numwords', 'low_numwords']):
self.cards = OrderedDict()
self.set_numwords()
self.MAXVAL = 1000 * list(self.cards.keys())[0]
def set_numwords(self):
self.set_high_numwords(self.high_numwords)
self.set_mid_numwords(self.mid_numwords)
self.set_low_numwords(self.low_numwords)
def set_high_numwords(self, *args):
raise NotImplementedError
def set_mid_numwords(self, mid):
for key, val in mid:
self.cards[key] = val
def set_low_numwords(self, numwords):
for word, n in zip(numwords, range(len(numwords) - 1, -1, -1)):
self.cards[n] = word
def splitnum(self, value):
for elem in self.cards:
if elem > value:
continue
out = []
if value == 0:
div, mod = 1, 0
else:
div, mod = divmod(value, elem)
if div == 1:
out.append((self.cards[1], 1))
else:
if div == value: # The system tallies, eg Roman Numerals
return [(div * self.cards[elem], div*elem)]
out.append(self.splitnum(div))
out.append((self.cards[elem], elem))
if mod:
out.append(self.splitnum(mod))
return out
def parse_minus(self, num_str):
"""Detach minus and return it as symbol with new num_str."""
if num_str.startswith('-'):
# Extra spacing to compensate if there is no minus.
return '%s ' % self.negword.strip(), num_str[1:]
return '', num_str
def str_to_number(self, value):
return Decimal(value)
def to_cardinal(self, value):
try:
assert int(value) == value
except (ValueError, TypeError, AssertionError):
return self.to_cardinal_float(value)
out = ""
if value < 0:
value = abs(value)
out = "%s " % self.negword.strip()
if value >= self.MAXVAL:
raise OverflowError(self.errmsg_toobig % (value, self.MAXVAL))
val = self.splitnum(value)
words, num = self.clean(val)
return self.title(out + words)
def float2tuple(self, value):
pre = int(value)
# Simple way of finding decimal places to update the precision
self.precision = abs(Decimal(str(value)).as_tuple().exponent)
post = abs(value - pre) * 10**self.precision
if abs(round(post) - post) < 0.01:
# We generally floor all values beyond our precision (rather than
# rounding), but in cases where we have something like 1.239999999,
# which is probably due to python's handling of floats, we actually
# want to consider it as 1.24 instead of 1.23
post = int(round(post))
else:
post = int(math.floor(post))
return pre, post
def to_cardinal_float(self, value):
try:
float(value) == value
except (ValueError, TypeError, AssertionError, AttributeError):
raise TypeError(self.errmsg_nonnum % value)
pre, post = self.float2tuple(float(value))
post = str(post)
post = '0' * (self.precision - len(post)) + post
out = [self.to_cardinal(pre)]
if self.precision:
out.append(self.title(self.pointword))
for i in range(self.precision):
curr = int(post[i])
out.append(to_s(self.to_cardinal(curr)))
return " ".join(out)
def merge(self, curr, next):
raise NotImplementedError
def clean(self, val):
out = val
while len(val) != 1:
out = []
left, right = val[:2]
if isinstance(left, tuple) and isinstance(right, tuple):
out.append(self.merge(left, right))
if val[2:]:
out.append(val[2:])
else:
for elem in val:
if isinstance(elem, list):
if len(elem) == 1:
out.append(elem[0])
else:
out.append(self.clean(elem))
else:
out.append(elem)
val = out
return out[0]
def title(self, value):
if self.is_title:
out = []
value = value.split()
for word in value:
if word in self.exclude_title:
out.append(word)
else:
out.append(word[0].upper() + word[1:])
value = " ".join(out)
return value
def verify_ordinal(self, value):
if not value == int(value):
raise TypeError(self.errmsg_floatord % value)
if not abs(value) == value:
raise TypeError(self.errmsg_negord % value)
def to_ordinal(self, value):
return self.to_cardinal(value)
def to_ordinal_num(self, value):
return value
# Trivial version
def inflect(self, value, text):
text = text.split("/")
if value == 1:
return text[0]
return "".join(text)
# //CHECK: generalise? Any others like pounds/shillings/pence?
def to_splitnum(self, val, hightxt="", lowtxt="", jointxt="",
divisor=100, longval=True, cents=True):
out = []
if isinstance(val, float):
high, low = self.float2tuple(val)
else:
try:
high, low = val
except TypeError:
high, low = divmod(val, divisor)
if high:
hightxt = self.title(self.inflect(high, hightxt))
out.append(self.to_cardinal(high))
if low:
if longval:
if hightxt:
out.append(hightxt)
if jointxt:
out.append(self.title(jointxt))
elif hightxt:
out.append(hightxt)
if low:
if cents:
out.append(self.to_cardinal(low))
else:
out.append("%02d" % low)
if lowtxt and longval:
out.append(self.title(self.inflect(low, lowtxt)))
return " ".join(out)
def to_year(self, value, **kwargs):
return self.to_cardinal(value)
def pluralize(self, n, forms):
"""
Should resolve gettext form:
http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html
"""
raise NotImplementedError
def _money_verbose(self, number, currency):
return self.to_cardinal(number)
def _cents_verbose(self, number, currency):
return self.to_cardinal(number)
def _cents_terse(self, number, currency):
return "%02d" % number
def to_currency(self, val, currency='EUR', cents=True, separator=',',
adjective=False):
"""
Args:
val: Numeric value
currency (str): Currency code
cents (bool): Verbose cents
separator (str): Cent separator
adjective (bool): Prefix currency name with adjective
Returns:
str: Formatted string
"""
left, right, is_negative = parse_currency_parts(val)
try:
cr1, cr2 = self.CURRENCY_FORMS[currency]
except KeyError:
raise NotImplementedError(
'Currency code "%s" not implemented for "%s"' %
(currency, self.__class__.__name__))
if adjective and currency in self.CURRENCY_ADJECTIVES:
cr1 = prefix_currency(self.CURRENCY_ADJECTIVES[currency], cr1)
minus_str = "%s " % self.negword.strip() if is_negative else ""
money_str = self._money_verbose(left, currency)
cents_str = self._cents_verbose(right, currency) \
if cents else self._cents_terse(right, currency)
return u'%s%s %s%s %s %s' % (
minus_str,
money_str,
self.pluralize(left, cr1),
separator,
cents_str,
self.pluralize(right, cr2)
)
def setup(self):
pass

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
try:
strtype = basestring
except NameError:
strtype = str
def to_s(val):
try:
return unicode(val)
except NameError:
return str(val)

View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import division
from decimal import ROUND_HALF_UP, Decimal
def parse_currency_parts(value, is_int_with_cents=True):
if isinstance(value, int):
if is_int_with_cents:
# assume cents if value is integer
negative = value < 0
value = abs(value)
integer, cents = divmod(value, 100)
else:
negative = value < 0
integer, cents = abs(value), 0
else:
value = Decimal(value)
value = value.quantize(
Decimal('.01'),
rounding=ROUND_HALF_UP
)
negative = value < 0
value = abs(value)
integer, fraction = divmod(value, 1)
integer = int(integer)
cents = int(fraction * 100)
return integer, cents, negative
def prefix_currency(prefix, base):
return tuple("%s %s" % (prefix, i) for i in base)

View File

@@ -0,0 +1,129 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import division, print_function, unicode_literals
from . import lang_EU
class Num2Word_AM(lang_EU.Num2Word_EU):
CURRENCY_FORMS = {'ETB': (('ብር', 'ብር'), ('ሳንቲም', 'ሳንቲም'))}
GIGA_SUFFIX = 'ቢሊዮን'
MEGA_SUFFIX = 'ሚሊዮን'
def set_high_numwords(self, high):
cap = 3 * (len(high) + 1)
for word, n in zip(high, range(cap, 5, -3)):
if n == 9:
self.cards[10 ** n] = word + self.GIGA_SUFFIX
else:
self.cards[10 ** n] = word + self.MEGA_SUFFIX
def setup(self):
super(Num2Word_AM, self).setup()
self.negword = 'አሉታዊ '
self.pointword = 'ነጥብ'
self.exclude_title = ['እና', 'ነጥብ', 'አሉታዊ']
self.mid_numwords = [(1000, 'ሺህ'), (100, 'መቶ'), (90, 'ዘጠና'),
(80, 'ሰማኒያ'), (70, 'ሰባ'), (60, 'ስድሳ'),
(50, 'አምሳ'), (40, 'አርባ'), (30, 'ሠላሳ')]
self.low_numwords = ['ሃያ', 'አሥራ ዘጠኝ', 'አሥራ ስምንት', 'አሥራ ሰባት',
'አስራ ስድስት', 'አሥራ አምስት', 'አሥራ አራት', 'አሥራ ሦስት',
'አሥራ ሁለት', 'አሥራ አንድ', 'አሥር', 'ዘጠኝ', 'ስምንት',
'ሰባት', 'ስድስት', 'አምስት', 'አራት', 'ሦስት', 'ሁለት',
'አንድ', 'ዜሮ']
self.ords = {'አንድ': 'አንደኛ',
'ሁለት': 'ሁለተኛ',
'ሦስት': 'ሦስተኛ',
'አራት': 'አራተኛ',
'አምስት': 'አምስተኛ',
'ስድስት': 'ስድስተኛ',
'ሰባት': 'ሰባተኛ',
'ስምንት': 'ስምንተኛ',
'ዘጠኝ': 'ዘጠነኛ',
'አሥር': 'አሥረኛ',
'አሥራ አንድ': 'አሥራ አንደኛ',
'አሥራ ሁለት': 'አሥራ ሁለተኛ',
'አሥራ ሦስት': 'አሥራ ሦስተኛ',
'አሥራ አራት': 'አሥራ አራተኛ',
'አሥራ አምስት': 'አሥራ አምስተኛ',
'አሥራ ስድስት': 'አሥራ ስድስተኛ',
'አሥራ ሰባት': 'አሥራ ሰባተኛ',
'አሥራ ስምንት': 'አሥራ ስምንተኛ',
'አሥራ ዘጠኝ': 'አሥራ ዘጠነኛ'}
def to_cardinal(self, value):
try:
assert int(value) == value
except (ValueError, TypeError, AssertionError):
return self.to_cardinal_float(value)
out = ''
if value >= self.MAXVAL:
raise OverflowError(self.errmsg_toobig % (value, self.MAXVAL))
if value == 100:
return self.title(out + 'መቶ')
else:
val = self.splitnum(value)
words, num = self.clean(val)
return self.title(out + words)
def merge(self, lpair, rpair):
ltext, lnum = lpair
rtext, rnum = rpair
if lnum == 1 and rnum < 100:
return rtext, rnum
elif 100 > lnum > rnum:
return '%s %s' % (ltext, rtext), lnum + rnum
elif lnum >= 100 > rnum:
return '%s %s' % (ltext, rtext), lnum + rnum
elif rnum > lnum:
return '%s %s' % (ltext, rtext), lnum * rnum
def to_ordinal(self, value):
self.verify_ordinal(value)
outwords = self.to_cardinal(value).split(' ')
lastwords = outwords[-1].split('-')
lastword = lastwords[-1].lower()
try:
lastword = self.ords[lastword]
except KeyError:
lastword += ''
lastwords[-1] = self.title(lastword)
outwords[-1] = ' '.join(lastwords)
return ' '.join(outwords)
def to_ordinal_num(self, value):
self.verify_ordinal(value)
return '%s%s' % (value, self.to_ordinal(value)[-1:])
def to_currency(self, val, currency='ብር', cents=True, separator='',
adjective=True):
result = super(Num2Word_AM, self).to_currency(
val, currency=currency, cents=cents, separator=separator,
adjective=adjective)
return result
def to_year(self, val, longval=True):
if not (val // 100) % 10:
return self.to_cardinal(val)
return self.to_splitnum(val, hightxt='መቶ', longval=longval)

View File

@@ -0,0 +1,415 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# Copyright (c) 2018, Abdullah Alhazmy, Alhazmy13. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
import decimal
import math
import re
from decimal import Decimal
from math import floor
from .base import Num2Word_Base
CURRENCY_SR = [("ريال", "ريالان", "ريالات", "ريالاً"),
("هللة", "هللتان", "هللات", "هللة")]
CURRENCY_EGP = [("جنيه", "جنيهان", "جنيهات", "جنيهاً"),
("قرش", "قرشان", "قروش", "قرش")]
CURRENCY_KWD = [("دينار", "ديناران", "دينارات", "ديناراً"),
("فلس", "فلسان", "فلس", "فلس")]
CURRENCY_TND = [("دينار", "ديناران", "دينارات", "ديناراً"),
("مليماً", "ميلمان", "مليمات", "مليم")]
ARABIC_ONES = [
"", "واحد", "اثنان", "ثلاثة", "أربعة", "خمسة", "ستة", "سبعة", "ثمانية",
"تسعة",
"عشرة", "أحد عشر", "اثنا عشر", "ثلاثة عشر", "أربعة عشر", "خمسة عشر",
"ستة عشر", "سبعة عشر", "ثمانية عشر",
"تسعة عشر"
]
class Num2Word_AR(Num2Word_Base):
errmsg_toobig = "abs(%s) must be less than %s."
MAXVAL = 10**51
def __init__(self):
super().__init__()
self.number = 0
self.arabicPrefixText = ""
self.arabicSuffixText = ""
self.integer_value = 0
self._decimalValue = ""
self.partPrecision = 2
self.currency_unit = CURRENCY_SR[0]
self.currency_subunit = CURRENCY_SR[1]
self.isCurrencyPartNameFeminine = True
self.isCurrencyNameFeminine = False
self.separator = 'و'
self.arabicOnes = ARABIC_ONES
self.arabicFeminineOnes = [
"", "إحدى", "اثنتان", "ثلاث", "أربع", "خمس", "ست", "سبع", "ثمان",
"تسع",
"عشر", "إحدى عشرة", "اثنتا عشرة", "ثلاث عشرة", "أربع عشرة",
"خمس عشرة", "ست عشرة", "سبع عشرة", "ثماني عشرة",
"تسع عشرة"
]
self.arabicOrdinal = [
"", "اول", "ثاني", "ثالث", "رابع", "خامس", "سادس", "سابع", "ثامن",
"تاسع", "عاشر", "حادي عشر", "ثاني عشر", "ثالث عشر", "رابع عشر",
"خامس عشر", "سادس عشر", "سابع عشر", "ثامن عشر", "تاسع عشر"
]
self.arabicTens = [
"عشرون", "ثلاثون", "أربعون", "خمسون", "ستون", "سبعون", "ثمانون",
"تسعون"
]
self.arabicHundreds = [
"", "مائة", "مئتان", "ثلاثمائة", "أربعمائة", "خمسمائة", "ستمائة",
"سبعمائة", "ثمانمائة", "تسعمائة"
]
self.arabicAppendedTwos = [
"مئتا", "ألفا", "مليونا", "مليارا", "تريليونا", "كوادريليونا",
"كوينتليونا", "سكستيليونا", "سبتيليونا", "أوكتيليونا ",
"نونيليونا", "ديسيليونا", "أندسيليونا", "دوديسيليونا",
"تريديسيليونا", "كوادريسيليونا", "كوينتينيليونا"
]
self.arabicTwos = [
"مئتان", "ألفان", "مليونان", "ملياران", "تريليونان",
"كوادريليونان", "كوينتليونان", "سكستيليونان", "سبتيليونان",
"أوكتيليونان ", "نونيليونان ", "ديسيليونان", "أندسيليونان",
"دوديسيليونان", "تريديسيليونان", "كوادريسيليونان", "كوينتينيليونان"
]
self.arabicGroup = [
"مائة", "ألف", "مليون", "مليار", "تريليون", "كوادريليون",
"كوينتليون", "سكستيليون", "سبتيليون", "أوكتيليون", "نونيليون",
"ديسيليون", "أندسيليون", "دوديسيليون", "تريديسيليون",
"كوادريسيليون", "كوينتينيليون"
]
self.arabicAppendedGroup = [
"", "ألفاً", "مليوناً", "ملياراً", "تريليوناً", "كوادريليوناً",
"كوينتليوناً", "سكستيليوناً", "سبتيليوناً", "أوكتيليوناً",
"نونيليوناً", "ديسيليوناً", "أندسيليوناً", "دوديسيليوناً",
"تريديسيليوناً", "كوادريسيليوناً", "كوينتينيليوناً"
]
self.arabicPluralGroups = [
"", "آلاف", "ملايين", "مليارات", "تريليونات", "كوادريليونات",
"كوينتليونات", "سكستيليونات", "سبتيليونات", "أوكتيليونات",
"نونيليونات", "ديسيليونات", "أندسيليونات", "دوديسيليونات",
"تريديسيليونات", "كوادريسيليونات", "كوينتينيليونات"
]
assert len(self.arabicAppendedGroup) == len(self.arabicGroup)
assert len(self.arabicPluralGroups) == len(self.arabicGroup)
assert len(self.arabicAppendedTwos) == len(self.arabicTwos)
def number_to_arabic(self, arabic_prefix_text, arabic_suffix_text):
self.arabicPrefixText = arabic_prefix_text
self.arabicSuffixText = arabic_suffix_text
self.extract_integer_and_decimal_parts()
def extract_integer_and_decimal_parts(self):
splits = re.split('\\.', str(self.number))
self.integer_value = int(splits[0])
if len(splits) > 1:
self._decimalValue = int(self.decimal_value(splits[1]))
else:
self._decimalValue = 0
def decimal_value(self, decimal_part):
if self.partPrecision is not len(decimal_part):
decimal_part_length = len(decimal_part)
decimal_part_builder = decimal_part
for i in range(0, self.partPrecision - decimal_part_length):
decimal_part_builder += '0'
decimal_part = decimal_part_builder
if len(decimal_part) <= self.partPrecision:
dec = len(decimal_part)
else:
dec = self.partPrecision
result = decimal_part[0: dec]
else:
result = decimal_part
# The following is useless (never happens)
# for i in range(len(result), self.partPrecision):
# result += '0'
return result
def digit_feminine_status(self, digit, group_level):
if group_level == -1:
if self.isCurrencyPartNameFeminine:
return self.arabicFeminineOnes[int(digit)]
else:
# Note: this never happens
return self.arabicOnes[int(digit)]
elif group_level == 0:
if self.isCurrencyNameFeminine:
return self.arabicFeminineOnes[int(digit)]
else:
return self.arabicOnes[int(digit)]
else:
return self.arabicOnes[int(digit)]
def process_arabic_group(self, group_number, group_level,
remaining_number):
tens = Decimal(group_number) % Decimal(100)
hundreds = Decimal(group_number) / Decimal(100)
ret_val = ""
if int(hundreds) > 0:
if tens == 0 and int(hundreds) == 2:
ret_val = "{}".format(self.arabicAppendedTwos[0])
else:
ret_val = "{}".format(self.arabicHundreds[int(hundreds)])
if ret_val != "" and tens != 0:
ret_val += " و "
if tens > 0:
if tens < 20:
# if int(group_level) >= len(self.arabicTwos):
# raise OverflowError(self.errmsg_toobig %
# (self.number, self.MAXVAL))
assert int(group_level) < len(self.arabicTwos)
if tens == 2 and int(hundreds) == 0 and group_level > 0:
pow = int(math.log10(self.integer_value))
if self.integer_value > 10 and pow % 3 == 0 and \
self.integer_value == 2 * (10 ** pow):
ret_val = "{}".format(
self.arabicAppendedTwos[int(group_level)])
else:
ret_val = "{}".format(
self.arabicTwos[int(group_level)])
else:
if tens == 1 and group_level > 0 and hundreds == 0:
# Note: this never happens
# (hundreds == 0 only if group_number is 0)
ret_val += ""
elif (tens == 1 or tens == 2) and (
group_level == 0 or group_level == -1) and \
hundreds == 0 and remaining_number == 0:
# Note: this never happens (idem)
ret_val += ""
elif tens == 1 and group_level > 0:
ret_val += self.arabicGroup[int(group_level)]
else:
ret_val += self.digit_feminine_status(int(tens),
group_level)
else:
ones = tens % 10
tens = (tens / 10) - 2
if ones > 0:
ret_val += self.digit_feminine_status(ones, group_level)
if ret_val != "" and ones != 0:
ret_val += " و "
ret_val += self.arabicTens[int(tens)]
return ret_val
# We use this instead of built-in `abs` function,
# because `abs` suffers from loss of precision for big numbers
def abs(self, number):
return number if number >= 0 else -number
# We use this instead of `"{:09d}".format(number)`,
# because the string conversion suffers from loss of
# precision for big numbers
def to_str(self, number):
integer = int(number)
if integer == number:
return str(integer)
decimal = round((number - integer) * 10**9)
return str(integer) + "." + "{:09d}".format(decimal).rstrip("0")
def convert(self, value):
self.number = self.to_str(value)
self.number_to_arabic(self.arabicPrefixText, self.arabicSuffixText)
return self.convert_to_arabic()
def convert_to_arabic(self):
temp_number = Decimal(self.number)
if temp_number == Decimal(0):
return "صفر"
decimal_string = self.process_arabic_group(self._decimalValue,
-1,
Decimal(0))
ret_val = ""
group = 0
while temp_number > Decimal(0):
temp_number_dec = Decimal(str(temp_number))
try:
number_to_process = int(temp_number_dec % Decimal(str(1000)))
except decimal.InvalidOperation:
decimal.getcontext().prec = len(
temp_number_dec.as_tuple().digits
)
number_to_process = int(temp_number_dec % Decimal(str(1000)))
temp_number = int(temp_number_dec / Decimal(1000))
group_description = \
self.process_arabic_group(number_to_process,
group,
Decimal(floor(temp_number)))
if group_description != '':
if group > 0:
if ret_val != "":
ret_val = "{}و {}".format("", ret_val)
if number_to_process != 2 and number_to_process != 1:
# if group >= len(self.arabicGroup):
# raise OverflowError(self.errmsg_toobig %
# (self.number, self.MAXVAL)
# )
assert group < len(self.arabicGroup)
if number_to_process % 100 != 1:
if 3 <= number_to_process <= 10:
ret_val = "{} {}".format(
self.arabicPluralGroups[group], ret_val)
else:
if ret_val != "":
ret_val = "{} {}".format(
self.arabicAppendedGroup[group],
ret_val)
else:
ret_val = "{} {}".format(
self.arabicGroup[group], ret_val)
else:
ret_val = "{} {}".format(self.arabicGroup[group],
ret_val)
ret_val = "{} {}".format(group_description, ret_val)
group += 1
formatted_number = ""
if self.arabicPrefixText != "":
formatted_number += "{} ".format(self.arabicPrefixText)
formatted_number += ret_val
if self.integer_value != 0:
remaining100 = int(self.integer_value % 100)
if remaining100 == 0:
formatted_number += self.currency_unit[0]
elif remaining100 == 1:
formatted_number += self.currency_unit[0]
elif remaining100 == 2:
if self.integer_value == 2:
formatted_number += self.currency_unit[1]
else:
formatted_number += self.currency_unit[0]
elif 3 <= remaining100 <= 10:
formatted_number += self.currency_unit[2]
elif 11 <= remaining100 <= 99:
formatted_number += self.currency_unit[3]
if self._decimalValue != 0:
formatted_number += " {} ".format(self.separator)
formatted_number += decimal_string
if self._decimalValue != 0:
formatted_number += " "
remaining100 = int(self._decimalValue % 100)
if remaining100 == 0:
formatted_number += self.currency_subunit[0]
elif remaining100 == 1:
formatted_number += self.currency_subunit[0]
elif remaining100 == 2:
formatted_number += self.currency_subunit[1]
elif 3 <= remaining100 <= 10:
formatted_number += self.currency_subunit[2]
elif 11 <= remaining100 <= 99:
formatted_number += self.currency_subunit[3]
if self.arabicSuffixText != "":
formatted_number += " {}".format(self.arabicSuffixText)
return formatted_number
def validate_number(self, number):
if number >= self.MAXVAL:
raise OverflowError(self.errmsg_toobig % (number, self.MAXVAL))
return number
def set_currency_prefer(self, currency):
if currency == 'TND':
self.currency_unit = CURRENCY_TND[0]
self.currency_subunit = CURRENCY_TND[1]
self.partPrecision = 3
elif currency == 'EGP':
self.currency_unit = CURRENCY_EGP[0]
self.currency_subunit = CURRENCY_EGP[1]
self.partPrecision = 2
elif currency == 'KWD':
self.currency_unit = CURRENCY_KWD[0]
self.currency_subunit = CURRENCY_KWD[1]
self.partPrecision = 2
else:
self.currency_unit = CURRENCY_SR[0]
self.currency_subunit = CURRENCY_SR[1]
self.partPrecision = 2
def to_currency(self, value, currency='SR', prefix='', suffix=''):
self.set_currency_prefer(currency)
self.isCurrencyNameFeminine = False
self.separator = "و"
self.arabicOnes = ARABIC_ONES
self.arabicPrefixText = prefix
self.arabicSuffixText = suffix
return self.convert(value=value)
def to_ordinal(self, number, prefix=''):
if number <= 19:
return "{}".format(self.arabicOrdinal[number])
if number < 100:
self.isCurrencyNameFeminine = True
else:
self.isCurrencyNameFeminine = False
self.currency_subunit = ('', '', '', '')
self.currency_unit = ('', '', '', '')
self.arabicPrefixText = prefix
self.arabicSuffixText = ""
return "{}".format(self.convert(self.abs(number)).strip())
def to_year(self, value):
value = self.validate_number(value)
return self.to_cardinal(value)
def to_ordinal_num(self, value):
return self.to_ordinal(value).strip()
def to_cardinal(self, number):
self.isCurrencyNameFeminine = False
number = self.validate_number(number)
minus = ''
if number < 0:
minus = 'سالب '
self.separator = ','
self.currency_subunit = ('', '', '', '')
self.currency_unit = ('', '', '', '')
self.arabicPrefixText = ""
self.arabicSuffixText = ""
self.arabicOnes = ARABIC_ONES
return minus + self.convert(value=self.abs(number)).strip()

View File

@@ -0,0 +1,204 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import unicode_literals
from .base import Num2Word_Base
class Num2Word_AZ(Num2Word_Base):
DIGITS = {
0: u"sıfır",
1: u"bir",
2: u"iki",
3: u"üç",
4: u"dörd",
5: u"beş",
6: u"altı",
7: u"yeddi",
8: u"səkkiz",
9: u"doqquz",
}
DECIMALS = {
1: u"on",
2: u"iyirmi",
3: u"otuz",
4: u"qırx",
5: u"əlli",
6: u"altmış",
7: u"yetmiş",
8: u"səksən",
9: u"doxsan",
}
POWERS_OF_TEN = {
2: u"yüz",
3: u"min",
6: u"milyon",
9: u"milyard",
12: u"trilyon",
15: u"katrilyon",
18: u"kentilyon",
21: u"sekstilyon",
24: u"septilyon",
27: u"oktilyon",
30: u"nonilyon",
33: u"desilyon",
36: u"undesilyon",
39: u"dodesilyon",
42: u"tredesilyon",
45: u"katordesilyon",
48: u"kendesilyon",
51: u"seksdesilyon",
54: u"septendesilyon",
57: u"oktodesilyon",
60: u"novemdesilyon",
63: u"vigintilyon",
}
VOWELS = u"aıoueəiöü"
VOWEL_TO_CARDINAL_SUFFIX_MAP = {
**dict.fromkeys(["a", "ı"], "ıncı"),
**dict.fromkeys(["e", "ə", "i"], "inci"),
**dict.fromkeys(["o", "u"], "uncu"),
**dict.fromkeys(["ö", "ü"], "üncü"),
}
VOWEL_TO_CARDINAL_NUM_SUFFIX_MAP = {
**dict.fromkeys(["a", "ı"], "cı"),
**dict.fromkeys(["e", "ə", "i"], "ci"),
**dict.fromkeys(["o", "u"], "cu"),
**dict.fromkeys(["ö", "ü"], ""),
}
CURRENCY_INTEGRAL = ('manat', 'manat')
CURRENCY_FRACTION = ('qəpik', 'qəpik')
CURRENCY_FORMS = {'AZN': (CURRENCY_INTEGRAL, CURRENCY_FRACTION)}
def setup(self):
super().setup()
self.negword = u"mənfi"
self.pointword = u"nöqtə"
def to_cardinal(self, value):
value_str = str(value)
parts = value_str.split(".")
integral_part = parts[0]
fraction_part = parts[1] if len(parts) > 1 else ""
if integral_part.startswith("-"):
integral_part = integral_part[1:] # ignore minus sign here
integral_part_in_words = self.int_to_word(integral_part)
fraction_part_in_words = "" if not fraction_part else self.int_to_word(
fraction_part, leading_zeros=True)
value_in_words = integral_part_in_words
if fraction_part:
value_in_words = " ".join(
[
integral_part_in_words,
self.pointword,
fraction_part_in_words
]
)
if value < 0:
value_in_words = " ".join([self.negword, value_in_words])
return value_in_words
def to_ordinal(self, value):
assert int(value) == value
cardinal = self.to_cardinal(value)
last_vowel = self._last_vowel(cardinal)
assert last_vowel is not None
suffix = self.VOWEL_TO_CARDINAL_SUFFIX_MAP[last_vowel]
if cardinal.endswith(tuple(self.VOWELS)):
cardinal = cardinal[:-1]
ordinal = "".join([cardinal, suffix])
return ordinal
def to_ordinal_num(self, value):
assert int(value) == value
cardinal = self.to_cardinal(value)
last_vowel = self._last_vowel(cardinal)
assert last_vowel is not None
suffix = self.VOWEL_TO_CARDINAL_NUM_SUFFIX_MAP[last_vowel]
ordinal_num = "-".join([str(value), suffix])
return ordinal_num
def to_year(self, value):
assert int(value) == value
year = self.to_cardinal(abs(value))
if value < 0:
year = " ".join(["e.ə.", year])
return year
def pluralize(self, n, forms):
form = 0 if n == 1 else 1
return forms[form]
def int_to_word(self, num_str, leading_zeros=False):
words = []
reversed_str = list(reversed(num_str))
for index, digit in enumerate(reversed_str):
digit_int = int(digit)
# calculate remainder after dividing index by 3
# because number is parsed by three digit chunks
remainder_to_3 = index % 3
if remainder_to_3 == 0:
if index > 0:
if set(reversed_str[index:index+3]) != {'0'}:
words.insert(0, self.POWERS_OF_TEN[index])
if digit_int > 0:
# we say "min" not "bir min"
if not (digit_int == 1 and index == 3):
words.insert(0, self.DIGITS[digit_int])
elif remainder_to_3 == 1:
if digit_int != 0:
words.insert(0, self.DECIMALS[digit_int])
else: # remainder is 2
if digit_int > 0:
words.insert(0, self.POWERS_OF_TEN[2]) # "yüz"
if digit_int > 1:
words.insert(0, self.DIGITS[digit_int])
if num_str == '0':
words.append(self.DIGITS[0])
if leading_zeros:
zeros_count = len(num_str) - len(str(int(num_str)))
words[:0] = zeros_count * [self.DIGITS[0]]
return " ".join(words)
def _last_vowel(self, value):
for char in value[::-1]:
if char in self.VOWELS:
return char

View File

@@ -0,0 +1,351 @@
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# Copyright (c) 2022, Sergei Ruzki. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import unicode_literals
from .base import Num2Word_Base
from .utils import get_digits, splitbyx
ZERO = "нуль"
ONES_FEMININE = {
1: "адна",
2: "дзве",
3: "тры",
4: "чатыры",
5: "пяць",
6: "шэсць",
7: "сем",
8: "восем",
9: "дзевяць",
}
ONES = {
"f": {
1: "адна",
2: "дзве",
3: "тры",
4: "чатыры",
5: "пяць",
6: "шэсць",
7: "сем",
8: "восем",
9: "дзевяць",
},
"m": {
1: "адзін",
2: "два",
3: "тры",
4: "чатыры",
5: "пяць",
6: "шэсць",
7: "сем",
8: "восем",
9: "дзевяць",
},
"n": {
1: "адно",
2: "два",
3: "тры",
4: "чатыры",
5: "пяць",
6: "шэсць",
7: "сем",
8: "восем",
9: "дзевяць",
},
}
TENS = {
0: "дзесяць",
1: "адзінаццаць",
2: "дванаццаць",
3: "трынаццаць",
4: "чатырнаццаць",
5: "пятнаццаць",
6: "шаснаццаць",
7: "сямнаццаць",
8: "васямнаццаць",
9: "дзевятнаццаць",
}
TWENTIES = {
2: "дваццаць",
3: "трыццаць",
4: "сорак",
5: "пяцьдзясят",
6: "шэсцьдзясят",
7: "семдзесят",
8: "восемдзесят",
9: "дзевяноста",
}
TWENTIES_ORD = (
("дваццаць", "дваццаці"),
("трыццаць", "трыццаці"),
("сорак", "сарака"),
("пяцьдзясят", "пяцідзясяці"),
("шэсцьдзясят", "шaсцідзясяці"),
("семдзесят", "сямідзесяці"),
("восемдзесят", "васьмідзесяці"),
("дзевяноста", "дзевяноста"),
)
HUNDREDS = {
1: "сто",
2: "дзвесце",
3: "трыста",
4: "чатырыста",
5: "пяцьсот",
6: "шэсцьсот",
7: "семсот",
8: "восемсот",
9: "дзевяцьсот",
}
THOUSANDS = {
1: ("тысяча", "тысячы", "тысяч"), # 10^3
2: ("мільён", "мільёны", "мільёнаў"), # 10^6
3: ("мільярд", "мільярды", "мільярдаў"), # 10^9
4: ("трыльён", "трыльёны", "трыльёнаў"), # 10^12
5: ("квадрыльён", "квадрыльёны", "квадрыльёнаў"), # 10^15
6: ("квінтыльён", "квінтыльёны", "квінтыльёнаў"), # 10^18
7: ("секстыльён", "секстыльёны", "секстыльёнаў"), # 10^21
8: ("сэптыльён", "сэптыльёны", "сэптыльёнаў"), # 10^24
9: ("актыльён", "актыльёны", "актыльёнаў"), # 10^27
10: ("нанільён", "нанільёны", "нанільёнаў"), # 10^30
}
class Num2Word_BE(Num2Word_Base):
CURRENCY_FORMS = {
"RUB": (
("расійскі рубель", "расійскія рублі", "расійскіх рублёў"),
("капейка", "капейкі", "капеек"),
),
"EUR": (("еўра", "еўра", "еўра"), ("цэнт", "цэнты", "цэнтаў")),
"USD": (("долар", "долары", "долараў"), ("цэнт", "цэнты", "цэнтаў")),
"UAH": (
("грыўна", "грыўны", "грыўнаў"),
("капейка", "капейкі", "капеек"),
),
"KZT": (("тэнге", "тэнге", "тэнге"), ("тыйін", "тыйіны", "тыйінаў")),
"BYN": (
("беларускі рубель", "беларускія рублі", "беларускіх рублёў"),
("капейка", "капейкі", "капеек"),
),
"UZS": (("сум", "сумы", "сумаў"), ("тыйін", "тыйіны", "тыйінаў")),
"PLN": (("злоты", "злотых", "злотых"), ("грош", "грошы", "грошаў")),
}
def setup(self):
self.negword = "мінус"
self.pointword = "коска"
self.ords = {
"нуль": "нулявы",
"адзін": "першы",
"два": "другі",
"тры": "трэці",
"чатыры": "чацвёрты",
"пяць": "пяты",
"шэсць": "шосты",
"сем": "сёмы",
"восем": "восьмы",
"дзевяць": "дзявяты",
"сто": "соты",
"тысяча": "тысячны",
}
self.ords_adjective = {
"адзін": "адна",
"адна": "адна",
"дзве": "двух",
"тры": "трох",
"чатыры": "чатырох",
"пяць": "пяці",
"шэсць": "шасці",
"сем": "сямі",
"восем": "васьмі",
"дзевяць": "дзевяцi",
"сто": "ста",
}
def to_cardinal(self, number, gender="m"):
n = str(number).replace(",", ".")
if "." in n:
left, right = n.split(".")
if set(right) == {"0"}:
leading_zero_count = 0
else:
leading_zero_count = len(right) - len(right.lstrip("0"))
decimal_part = (ZERO + " ") * leading_zero_count + self._int2word(
int(right), gender
)
return "{} {} {}".format(
self._int2word(int(left), gender), self.pointword, decimal_part
)
else:
return self._int2word(int(n), gender)
def pluralize(self, n, forms):
if n % 100 < 10 or n % 100 > 20:
if n % 10 == 1:
form = 0
elif 5 > n % 10 > 1:
form = 1
else:
form = 2
else:
form = 2
return forms[form]
def to_ordinal(self, number, gender="m"):
self.verify_ordinal(number)
if isinstance(gender, bool) and gender:
gender = "f"
outwords = self.to_cardinal(number, gender).split(" ")
lastword = outwords[-1].lower()
try:
if len(outwords) > 1:
if outwords[-2] in self.ords_adjective:
outwords[-2] = self.ords_adjective.get(
outwords[-2], outwords[-2]
)
elif outwords[-2] == "дзесяць":
outwords[-2] = outwords[-2][:-1] + "і"
if len(outwords) == 3:
if outwords[-3] in ["адзін", "адна"]:
outwords[-3] = ""
lastword = self.ords[lastword]
except KeyError:
if lastword[:-3] in self.ords_adjective:
lastword = (
self.ords_adjective.get(lastword[:-3], lastword) + "соты"
)
elif lastword[-7:] == "дзесяць":
lastword = "дзясяты"
elif lastword[-9:] == "семдзесят":
lastword = "сямідзясяты"
elif lastword[-1] == "ь" or lastword[-2] == "ц":
lastword = lastword[:-2] + "ты"
elif lastword[-1] == "к":
lastword = lastword.replace("о", "а") + "авы"
elif lastword[-2] == "ч" or lastword[-1] == "ч":
if lastword[-2] == "ч":
lastword = lastword[:-1] + "ны"
if lastword[-1] == "ч":
lastword = lastword + "ны"
elif lastword[-1] == "н" or lastword[-2] == "н":
lastword = lastword[: lastword.rfind("н") + 1] + "ны"
elif lastword[-3:] == "наў":
lastword = lastword[: lastword.rfind("н") + 1] + "ны"
elif lastword[-1] == "д" or lastword[-2] == "д":
lastword = lastword[: lastword.rfind("д") + 1] + "ны"
if gender == "f":
if lastword[-2:] in [
"ці",
]:
lastword = lastword[:-2] + "цяя"
else:
lastword = lastword[:-1] + "ая"
if gender == "n":
if lastword[-2:] in ["ці", "ца"]:
lastword = lastword[:-2] + "цяе"
else:
lastword = lastword[:-1] + "ае"
outwords[-1] = self.title(lastword)
if len(outwords) == 2 and "адна" in outwords[-2]:
outwords[-2] = outwords[-1]
del outwords[-1]
if len(outwords) > 1 and (
(any(x[0] in outwords[-1] for x in THOUSANDS.values()))
or "тысяч" in outwords[-1]
):
new_outwords = []
for _w in outwords:
replacement = next(
(x for x in TWENTIES_ORD if x[0] in _w), None
)
if replacement:
_w = _w.replace(replacement[0], replacement[1])
new_outwords.append(_w)
outwords = ["".join(new_outwords)]
return " ".join(outwords).strip()
def _money_verbose(self, number, currency):
gender = "m"
if currency == "UAH":
gender = "f"
return self._int2word(number, gender)
def _cents_verbose(self, number, currency):
if currency in ("UAH", "RUB", "BYN"):
gender = "f"
else:
gender = "m"
return self._int2word(number, gender)
def _int2word(self, n, gender="m"):
if n < 0:
return " ".join([self.negword, self._int2word(abs(n), gender)])
if n == 0:
return ZERO
words = []
chunks = list(splitbyx(str(n), 3))
i = len(chunks)
for x in chunks:
i -= 1
if x == 0:
continue
n1, n2, n3 = get_digits(x)
if n3 > 0:
words.append(HUNDREDS[n3])
if n2 > 1:
words.append(TWENTIES[n2])
if n2 == 1:
words.append(TENS[n1])
elif n1 > 0:
if i == 0:
ones = ONES[gender]
elif i == 1:
ones = ONES["f"] # Thousands are feminine
else:
ones = ONES["m"]
words.append(ones[n1])
if i > 0:
words.append(self.pluralize(x, THOUSANDS[i]))
return " ".join(words)

View File

@@ -0,0 +1,203 @@
# -*- coding: utf-8 -*-
# Author: Mehedi Hasan Khondoker
# Email: mehedihasankhondoker [at] gmail.com
# Copyright (c) 2024, Mehedi Hasan Khondoker. All Rights Reserved.
# This library is build for Bangladesh format Number to Word conversion.
# You are welcome as contributor to the library.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
from decimal import Decimal
RANKING = ['', 'প্রথম', 'দ্বিতীয়', 'তৃতীয়', 'চতুর্থ', 'পঞ্চম', 'ষষ্ঠ',
'সপ্তম', 'অষ্টম', 'নবম', 'দশম'] # pragma: no cover
AKOK = ['', 'এক', 'দুই', 'তিন', 'চার', 'পাঁচ', 'ছয়',
'সাত', 'আট', 'নয়'] # pragma: no cover
DOSOK = [
'দশ', 'এগারো', 'বারো', 'তেরো', 'চৌদ্দ', 'পনের',
'ষোল', 'সতের', 'আঠারো', 'উনিশ',
'বিশ', 'একুশ', 'বাইশ', 'তেইশ', 'চব্বিশ', 'পঁচিশ',
'ছাব্বিশ', 'সাতাশ', 'আটাশ', 'উনত্রিশ',
'ত্রিশ', 'একত্রিশ', 'বত্রিশ', 'তেত্রিশ', 'চৌত্রিশ', 'পঁইত্রিশ',
'ছত্রিশ', 'সাতত্রিশ', 'আটত্রিশ', 'উনচল্লিশ', 'চল্লিশ',
'একচল্লিশ', 'বিয়াল্লিশ', 'তেতাল্লিশ', 'চৌচল্লিশ',
'পঁয়তাল্লিশ', 'ছেচল্লিশ', 'সাতচল্লিশ', 'আটচল্লিশ', 'উনপঞ্চাশ',
'পঞ্চাশ', 'একান্ন', 'বাহান্ন', 'তিপ্পান্ন', 'চুয়ান্ন', 'পঞ্চান্ন',
'ছাপ্পান্ন', 'সাতান্ন', 'আটান্ন', 'উনষাট', 'ষাট',
'একষট্টি', 'বাষট্টি', 'তেষট্টি', 'চৌষট্টি', 'পঁয়ষট্টি',
'ছিষট্টি', 'সাতষট্টি', 'আটষট্টি', 'উনসত্তর', 'সত্তর',
'একাত্তর ', 'বাহাত্তর', 'তিয়াত্তর', 'চুয়াত্তর', 'পঁচাত্তর',
'ছিয়াত্তর', 'সাতাত্তর', 'আটাত্তর', 'উনআশি', 'আশি',
'একাশি', 'বিরাশি', 'তিরাশি', 'চুরাশি', 'পঁচাশি',
'ছিয়াশি', 'সাতাশি', 'আটাশি', 'উননব্বই', 'নব্বই',
'একানব্বই', 'বিরানব্বই', 'তিরানব্বই', 'চুরানব্বই', 'পঁচানব্বই',
'ছিয়ানব্বই', 'সাতানব্বই', 'আটানব্বই', 'নিরানব্বই'
] # pragma: no cover
HAZAR = ' হাজার ' # pragma: no cover
LAKH = ' লাখ ' # pragma: no cover
KOTI = ' কোটি ' # pragma: no cover
MAX_NUMBER = 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 # noqa: E501 # pragma: no cover
class NumberTooLargeError(Exception):
"""Custom exception for numbers that are too large."""
pass
class Num2Word_BN:
@staticmethod
def str_to_number(number):
return abs(Decimal(str(number))) # pragma: no cover
@staticmethod
def parse_number(number: Decimal):
dosomik = str(number - int(number)).split('.')[1:]
dosomik_str = ''.join(dosomik) if dosomik else 0
return int(number), int(dosomik_str)
@staticmethod
def parse_paisa(number: Decimal):
# 1-99 for paisa count so two digits are valid.
paisa = str(number - int(number)).split('.')[1:]
paisa_str = ''.join(paisa) if paisa else 0
# this is need, when we parse to decimal it removes trailing 0 .
if paisa_str:
paisa_str = str(int(paisa_str) * 100)[:2]
return int(number), int(paisa_str)
def _is_smaller_than_max_number(self, number):
if MAX_NUMBER >= number:
return True
raise NumberTooLargeError(f'Too Large number maximum '
f'value={MAX_NUMBER}')
def _dosomik_to_bengali_word(self, number):
word = ''
for i in str(number):
word += ' ' + AKOK[int(i)]
return word
def _number_to_bengali_word(self, number):
if number == 0:
return 'শূন্য'
words = ''
if number >= 10 ** 7:
words += self._number_to_bengali_word(number // 10 ** 7) + KOTI
number %= 10 ** 7
if number >= 10 ** 5:
words += self._number_to_bengali_word(number // 10 ** 5) + LAKH
number %= 10 ** 5
if number >= 1000:
words += self._number_to_bengali_word(number // 1000) + HAZAR
number %= 1000
if number >= 100:
words += AKOK[number // 100] + 'শত '
number %= 100
if 10 <= number <= 99:
words += DOSOK[number - 10] + ' '
number = 0
if 0 < number < 10:
words += AKOK[number] + ' '
return words.strip()
def to_currency(self, val):
"""
This function represent a number to word in bangla taka and paisa.
example:
1 = এক টাকা,
101 = একশত এক টাকা,
9999.15 = নয় হাজার নয়শত নিরানব্বই টাকা পনের পয়সা
and so on.
"""
dosomik_word = None
number = self.str_to_number(val)
number, decimal_part = self.parse_paisa(number)
self._is_smaller_than_max_number(number)
if decimal_part > 0:
dosomik_word = f' {self._number_to_bengali_word(decimal_part)} পয়সা' # noqa: E501
words = f'{self._number_to_bengali_word(number)} টাকা'
if dosomik_word:
return (words + dosomik_word).strip()
return words.strip()
def to_cardinal(self, number):
"""
This function represent a number to word in bangla.
example:
1 = এক,
101 = একশত এক,
9999 = নয় হাজার নয়শত নিরানব্বই
and so on.
"""
dosomik_word = None
number = self.str_to_number(number)
number, decimal_part = self.parse_number(number)
self._is_smaller_than_max_number(number)
if decimal_part > 0:
dosomik_word = f' দশমিক{self._dosomik_to_bengali_word(decimal_part)}' # noqa: E501
words = self._number_to_bengali_word(number)
if dosomik_word:
return (words + dosomik_word).strip()
return words.strip()
def to_ordinal(self, number):
return self.to_cardinal(number)
def to_ordinal_num(self, number):
"""
This function represent a number to ranking in bangla.
example:
1 = প্রথম,
2 = দ্বিতীয়,
1001 = এক হাজার একতম
and so on.
"""
self._is_smaller_than_max_number(number)
if number in range(1, 11):
return RANKING[number]
else:
rank = self.to_cardinal(int(abs(number)))
if rank.endswith(''):
return rank + ''
return rank + 'তম'
def to_year(self, number):
"""
This function represent a number to year in bangla.
example:
2002 = দুই হাজার দুই সাল,
2024 = দুই হাজার চব্বিশ সাল
and so on.
"""
self._is_smaller_than_max_number(number)
return self.to_cardinal(int(abs(number))) + ' সাল'

View File

@@ -0,0 +1,476 @@
from __future__ import division, print_function, unicode_literals
import math
from .lang_EU import Num2Word_EU
GENERIC_DOLLARS = ('dòlar', 'dòlars')
GENERIC_CENTS = ('centau', 'centaus')
CURRENCIES_UNA = (
'SLL',
'SEK',
'NOK',
'CZK',
'DKK',
'ISK',
'SKK',
'GBP',
'CYP',
'EGP',
'FKP',
'GIP',
'LBP',
'SDG',
'SHP',
'SSP',
'SYP',
'INR',
'IDR',
'LKR',
'MUR',
'NPR',
'PKR',
'SCR',
'ESP',
'TRY',
'ITL',
)
CENTS_UNA = ('EGP', 'JOD', 'LBP', 'SDG', 'SSP', 'SYP')
class Num2Word_CA(Num2Word_EU):
CURRENCY_FORMS = {
'EUR': (('euro', 'euros'), ('cèntim', 'cèntims')),
'ESP': (('pesseta', 'pessetes'), ('cèntim', 'cèntims')),
'USD': (GENERIC_DOLLARS, ('centau', 'centaus')),
'PEN': (('sol', 'sols'), ('cèntim', 'cèntims')),
'CRC': (('colón', 'colons'), GENERIC_CENTS),
'AUD': (GENERIC_DOLLARS, GENERIC_CENTS),
'CAD': (GENERIC_DOLLARS, GENERIC_CENTS),
'GBP': (('lliura', 'lliures'), ('penic', 'penics')),
'RUB': (('ruble', 'rubles'), ('copec', 'copecs')),
'SEK': (('corona', 'corones'), ('öre', 'öre')),
'NOK': (('corona', 'corones'), ('øre', 'øre')),
'PLN': (('zloty', 'zlotys'), ('grosz', 'groszy')),
'MXN': (('peso', 'pesos'), GENERIC_CENTS),
'RON': (('leu', 'lei'), ('ban', 'bani')),
'INR': (('rupia', 'rupies'), ('paisa', 'paise')),
'HUF': (('fòrint', 'fòrints'), ('fillér', 'fillérs')),
'FRF': (('franc', 'francs'), ('cèntim', 'cèntims')),
'CNY': (('iuan', 'iuans'), ('fen', 'jiao')),
'CZK': (('corona', 'corones'), ('haléř', 'haléřů')),
'NIO': (('córdoba', 'córdobas'), GENERIC_CENTS),
'VES': (('bolívar', 'bolívars'), ('cèntim', 'cèntims')),
'BRL': (('real', 'reals'), GENERIC_CENTS),
'CHF': (('franc', 'francs'), ('cèntim', 'cèntims')),
'JPY': (('ien', 'iens'), ('sen', 'sen')),
'KRW': (('won', 'wons'), ('jeon', 'jeon')),
'KPW': (('won', 'wons'), ('chŏn', 'chŏn')),
'TRY': (('lira', 'lires'), ('kuruş', 'kuruş')),
'ZAR': (('rand', 'rands'), ('cèntim', 'cèntims')),
'KZT': (('tenge', 'tenge'), ('tin', 'tin')),
'UAH': (('hrívnia', 'hrívnies'), ('kopiika', 'kopíok')),
'THB': (('baht', 'bahts'), ('satang', 'satang')),
'AED': (('dirham', 'dirhams'), ('fils', 'fulūs')),
'AFN': (('afgani', 'afganis'), ('puli', 'puls')),
'ALL': (('lek', 'lekë'), ('qqindarka', 'qindarkë')),
'AMD': (('dram', 'drams'), ('luma', 'lumas')),
'ANG': (('florí', 'florins'), ('cèntim', 'cèntims')),
'AOA': (('kwanza', 'kwanzes'), ('cèntim', 'cèntims')),
'ARS': (('peso', 'pesos'), GENERIC_CENTS),
'AWG': (('florí', 'florins'), GENERIC_CENTS),
'AZN': (('manat', 'manats'), ('qəpik', 'qəpik')),
'BBD': (GENERIC_DOLLARS, GENERIC_CENTS),
'BDT': (('taka', 'taka'), ('poisha', 'poisha')),
'BGN': (('lev', 'leva'), ('stotinka', 'stotinki')),
'BHD': (('dinar', 'dinars'), ('fils', 'fulūs')),
'BIF': (('franc', 'francs'), ('cèntim', 'cèntims')),
'BMD': (GENERIC_DOLLARS, GENERIC_CENTS),
'BND': (GENERIC_DOLLARS, GENERIC_CENTS),
'BOB': (('boliviano', 'bolivianos'), GENERIC_CENTS),
'BSD': (GENERIC_DOLLARS, GENERIC_CENTS),
'BTN': (('ngultrum', 'ngultrums'), ('chetrum', 'chetrums')),
'BWP': (('pula', 'pula'), ('thebe', 'thebe')),
'BYN': (('ruble', 'rubles'), ('copec', 'copecs')),
'BYR': (('ruble', 'rubles'), ('copec', 'copecs')),
'BZD': (GENERIC_DOLLARS, ('cèntim', 'cèntims')),
'CDF': (('franc', 'francs'), ('cèntim', 'cèntims')),
'CLP': (('peso', 'pesos'), GENERIC_CENTS),
'COP': (('peso', 'pesos'), GENERIC_CENTS),
'CUP': (('peso', 'pesos'), GENERIC_CENTS),
'CVE': (('escut', 'escuts'), GENERIC_CENTS),
'CYP': (('lliura', 'lliures'), ('cèntim', 'cèntims')),
'DJF': (('franc', 'francs'), ('cèntim', 'cèntims')),
'DKK': (('corona', 'corones'), ('øre', 'øre')),
'DOP': (('peso', 'pesos'), GENERIC_CENTS),
'DZD': (('dinar', 'dinars'), ('cèntim', 'cèntims')),
'ECS': (('sucre', 'sucres'), GENERIC_CENTS),
'EGP': (('lliura', 'lliures'), ('piastre', 'piastres')),
'ERN': (('nakfa', 'nakfes'), ('cèntim', 'cèntims')),
'ETB': (('birr', 'birr'), ('cèntim', 'cèntims')),
'FJD': (GENERIC_DOLLARS, GENERIC_CENTS),
'FKP': (('lliura', 'lliures'), ('penic', 'penics')),
'GEL': (('lari', 'laris'), ('tetri', 'tetri')),
'GHS': (('cedi', 'cedis'), ('pesewa', 'pesewas')),
'GIP': (('lliura', 'lliures'), ('penic', 'penics')),
'GMD': (('dalasi', 'dalasis'), ('butut', 'bututs')),
'GNF': (('franc', 'francs'), ('cèntim', 'cèntims')),
'GTQ': (('quetzal', 'quetzals'), GENERIC_CENTS),
'GYD': (GENERIC_DOLLARS, GENERIC_CENTS),
'HKD': (GENERIC_DOLLARS, GENERIC_CENTS),
'HNL': (('lempira', 'lempires'), GENERIC_CENTS),
'HRK': (('kuna', 'kuna'), ('lipa', 'lipa')),
'HTG': (('gourde', 'gourdes'), ('cèntim', 'cèntims')),
'IDR': (('rúpia', 'rúpies'), ('cèntim', 'cèntims')),
'ILS': (('xéquel', 'xéquels'), ('agorà', 'agorot')),
'IQD': (('dinar', 'dinars'), ('fils', 'fils')),
'IRR': (('rial', 'rials'), ('dinar', 'dinars')),
'ISK': (('corona', 'corones'), ('eyrir', 'aurar')),
'ITL': (('lira', 'lires'), ('cèntim', 'cèntims')),
'JMD': (GENERIC_DOLLARS, ('cèntim', 'cèntims')),
'JOD': (('dinar', 'dinars'), ('piastra', 'piastres')),
'KES': (('xiling', 'xílings'), ('cèntim', 'cèntims')),
'KGS': (('som', 'som'), ('tyiyn', 'tyiyn')),
'KHR': (('riel', 'riels'), ('cèntim', 'cèntims')),
'KMF': (('franc', 'francs'), ('cèntim', 'cèntims')),
'KWD': (('dinar', 'dinars'), ('fils', 'fils')),
'KYD': (GENERIC_DOLLARS, ('cèntim', 'cèntims')),
'LAK': (('kip', 'kips'), ('at', 'at')),
'LBP': (('lliura', 'lliures'), ('piastra', 'piastres')),
'LKR': (('rúpia', 'rúpies'), ('cèntim', 'cèntims')),
'LRD': (GENERIC_DOLLARS, ('cèntim', 'cèntims')),
'LSL': (('loti', 'maloti'), ('sente', 'lisente')),
'LTL': (('lita', 'litai'), ('cèntim', 'cèntims')),
'LYD': (('dinar', 'dinars'), ('dírham', 'dírhams')),
'MAD': (('dírham', 'dirhams'), ('cèntim', 'cèntims')),
'MDL': (('leu', 'lei'), ('ban', 'bani')),
'MGA': (('ariary', 'ariary'), ('iraimbilanja', 'iraimbilanja')),
'MKD': (('denar', 'denari'), ('deni', 'deni')),
'MMK': (('kyat', 'kyats'), ('pya', 'pyas')),
'MNT': (('tögrög', 'tögrög'), ('möngö', 'möngö')),
'MOP': (('pataca', 'pataques'), ('avo', 'avos')),
'MRO': (('ouguiya', 'ouguiya'), ('khoums', 'khoums')),
'MRU': (('ouguiya', 'ouguiya'), ('khoums', 'khoums')),
'MUR': (('rupia', 'rúpies'), ('cèntim', 'cèntims')),
'MVR': (('rufiyaa', 'rufiyaa'), ('laari', 'laari')),
'MWK': (('kwacha', 'kwacha'), ('tambala', 'tambala')),
'MYR': (('ringgit', 'ringgits'), ('sen', 'sens')),
'MZN': (('metical', 'meticals'), GENERIC_CENTS),
'NAD': (GENERIC_DOLLARS, ('cèntim', 'cèntims')),
'NGN': (('naira', 'naires'), ('kobo', 'kobos')),
'NPR': (('rupia', 'rupies'), ('paisa', 'paises')),
'NZD': (GENERIC_DOLLARS, GENERIC_CENTS),
'OMR': (('rial', 'rials'), ('baisa', 'baisa')),
'PAB': (GENERIC_DOLLARS, ('centésimo', 'centésimos')),
'PGK': (('kina', 'kina'), ('toea', 'toea')),
'PHP': (('peso', 'pesos'), GENERIC_CENTS),
'PKR': (('rupia', 'rupies'), ('paisa', 'paise')),
'PLZ': (('zloty', 'zlotys'), ('grosz', 'groszy')),
'PYG': (('guaraní', 'guaranís'), ('cèntim', 'cèntims')),
'QAR': (('rial', 'rials'), ('dírham', 'dírhams')),
'QTQ': (('quetzal', 'quetzals'), GENERIC_CENTS),
'RSD': (('dinar', 'dinars'), ('para', 'para')),
'RUR': (('ruble', 'rubles'), ('copec', 'copecs')),
'RWF': (('franc', 'francs'), ('cèntim', 'cèntims')),
'SAR': (('riyal', 'riyals'), ('hàl·lala', 'hàl·lalat')),
'SBD': (GENERIC_DOLLARS, ('cèntim', 'cèntims')),
'SCR': (('rupia', 'rupies'), ('cèntim', 'cèntims')),
'SDG': (('lliura', 'lliures'), ('piastre', 'piastres')),
'SGD': (GENERIC_DOLLARS, ('cèntim', 'cèntims')),
'SHP': (('lliura', 'lliures'), ('penic', 'penics')),
'SLL': (('leonE', 'leones'), ('cèntim', 'cèntims')),
'SRD': (GENERIC_DOLLARS, ('cèntim', 'cèntims')),
'SSP': (('lliura', 'lliures'), ('piastre', 'piastres')),
'STD': (('dobra', 'dobrAs'), ('cèntim', 'cèntims')),
'SVC': (('colón', 'colons'), GENERIC_CENTS),
'SYP': (('lliura', 'lliures'), ('piastre', 'piastres')),
'SZL': (('lilangeni', 'emalangeni'), ('cèntim', 'cèntims')),
'TJS': (('somoni', 'somoni'), ('diram', 'diram')),
'TMT': (('manat', 'manats'), ('teňňesi', 'teňňesi')),
'TND': (('dinar', 'dinars'), ('mil·lim', 'mil·limat')),
'TOP': (('paanga', 'paangas'), ('seniti', 'seniti')),
'TTD': (GENERIC_DOLLARS, ('cèntim', 'cèntims')),
'TWD': (('nou dòlar', 'nous dòlars'), ('fen', 'fen')),
'TZS': (('xíling', 'xílings'), ('cèntim', 'cèntims')),
'UGX': (('xíling', 'xílings'), ('cèntim', 'cèntims')),
'UYU': (('peso', 'pesos'), ('centèsim', 'centèsims')),
'UZS': (('som', 'som'), ('tiyin', 'tiyin')),
'VND': (('dong', 'dongs'), ('xu', 'xu')),
'VUV': (('vatu', 'vatus'), ('cèntim', 'cèntims')),
'WST': (('tala', 'tala'), ('sene', 'sene')),
'XAF': (('franc CFA', 'francs CFA'), ('cèntim', 'cèntims')),
'XCD': (GENERIC_DOLLARS, ('cèntim', 'cèntims')),
'XOF': (('franc CFA', 'francs CFA'), ('cèntim', 'cèntims')),
'XPF': (('franc CFP', 'francs CFP'), ('cèntim', 'cèntims')),
'YER': (('rial', 'rials'), ('fils', 'fils')),
'YUM': (('dinar', 'dinars'), ('para', 'para')),
'ZMW': (('kwacha', 'kwacha'), ('ngwee', 'ngwee')),
'ZWL': (GENERIC_DOLLARS, ('cèntim', 'cèntims')),
'ZWL': (GENERIC_DOLLARS, ('cèntim', 'cèntims')),
}
GIGA_SUFFIX = None
MEGA_SUFFIX = "ilió"
def setup(self):
lows = ["quadr", "tr", "b", "m"]
self.high_numwords = self.gen_high_numwords([], [], lows)
self.negword = "menys "
self.pointword = "punt"
self.errmsg_nonnum = "type(%s) no és [long, int, float]"
self.errmsg_floatord = "El float %s no pot ser tractat com un" \
" ordinal."
self.errmsg_negord = "El número negatiu %s no pot ser tractat" \
" com un ordinal."
self.errmsg_toobig = "abs(%s) ha de ser inferior a %s."
self.gender_stem = "è"
self.exclude_title = ["i", "menys", "punt"]
self.mid_numwords = [
(1000, "mil"),
(100, "cent"),
(90, "noranta"),
(80, "vuitanta"),
(70, "setanta"),
(60, "seixanta"),
(50, "cinquanta"),
(40, "quaranta"),
(30, "trenta"),
]
self.low_numwords = [
"vint-i-nou",
"vint-i-vuit",
"vint-i-set",
"vint-i-sis",
"vint-i-cinc",
"vint-i-quatre",
"vint-i-tres",
"vint-i-dos",
"vint-i-un",
"vint",
"dinou",
"divuit",
"disset",
"setze",
"quinze",
"catorze",
"tretze",
"dotze",
"onze",
"deu",
"nou",
"vuit",
"set",
"sis",
"cinc",
"quatre",
"tres",
"dos",
"un",
"zero",
]
self.mid_num = {
1000: "mil",
100: "cent",
90: "noranta",
80: "vuitanta",
70: "setanta",
60: "seixanta",
50: "cinquanta",
40: "quaranta",
30: "trenta",
20: "vint",
10: "deu",
}
self.low_num = {
0: "zero",
1: "un",
2: "dos",
3: "tres",
4: "quatre",
5: "cinc",
6: "sis",
7: "set",
8: "vuit",
9: "nou",
10: "deu",
11: "onze",
12: "dotze",
13: "tretze",
14: "catorze",
15: "quinze",
16: "setze",
17: "disset",
18: "divuit",
19: "dinou",
20: "vint",
21: "vint-i-un",
22: "vint-i-dos",
23: "vint-i-tres",
24: "vint-i-quatre",
25: "vint-i-cinc",
26: "vint-i-sis",
27: "vint-i-set",
28: "vint-i-vuit",
29: "vint-i-nou",
}
self.ords = {
1: "primer",
2: "segon",
3: "tercer",
4: "quart",
5: "cinqu",
6: "sis",
7: "set",
8: "vuit",
9: "nov",
10: "des",
11: "onz",
12: "dotz",
13: "tretz",
14: "catorz",
15: "quinz",
16: "setz",
17: "disset",
18: "divuit",
19: "dinov",
20: "vint",
30: "trent",
40: "quarant",
50: "cinquant",
60: "seixant",
70: "setant",
80: "vuitant",
90: "norant",
100: "cent",
200: "dos-cent",
300: "tres-cent",
400: "quatre-cent",
500: "cinc-cent",
600: "sis-cent",
700: "set-cent",
800: "vuit-cent",
900: "nou-cent",
1e3: "mil",
1e6: "milion",
1e9: "mil milion",
1e12: "bilion",
1e15: "mil bilion",
}
self.ords_2 = {1: "1r", 2: "2n", 3: "3r", 4: "4t"}
self.ords_3 = {
1: "unè",
2: "dosè",
3: "tresè",
4: "quatrè",
5: "cinquè",
6: "sisè",
7: "setè",
8: "vuitè",
9: "novè",
}
def merge(self, curr, next):
ctext, cnum, ntext, nnum = curr + next
if cnum == 1:
if nnum < 1000000:
return next
ctext = "un"
if nnum < cnum:
if cnum < 100:
return "%s-%s" % (ctext, ntext), cnum + nnum
elif nnum == 1:
return "%s %s" % (ctext, ntext), cnum + nnum
elif cnum == 100:
return "%s %s" % (ctext, ntext), cnum + nnum
else:
return "%s %s" % (ctext, ntext), cnum + nnum
elif (not nnum % 1000000) and cnum > 1:
ntext = ntext[:-3] + "lions"
if nnum == 100:
ntext += "s"
ctext += "-"
else:
ntext = " " + ntext
return (ctext + ntext, cnum * nnum)
def to_ordinal(self, value):
self.verify_ordinal(value)
if value == 0:
text = ""
elif value < 5:
text = self.ords[value]
elif value <= 20:
text = "%s%s" % (self.ords[value], self.gender_stem)
elif value <= 30:
frac = value % 10
text = "%s%s%s" % (self.ords[20], "-i-", self.ords_3[frac])
elif value < 100:
dec = (value // 10) * 10
text = "%s%s%s%s" % (self.ords[dec], "a",
"-", self.ords_3[value - dec])
elif value == 1e2:
text = "%s%s" % (self.ords[value], self.gender_stem)
elif value < 2e2:
cen = (value // 100) * 100
text = "%s %s" % (self.ords[cen], self.to_ordinal(value - cen))
elif value < 1e3:
cen = (value // 100) * 100
text = "%s%s %s" % (self.ords[cen], "s",
self.to_ordinal(value - cen))
elif value == 1e3:
text = "%s%s" % (self.ords[value], self.gender_stem)
elif value < 1e6:
dec = 1000 ** int(math.log(int(value), 1000))
high_part, low_part = divmod(value, dec)
cardinal = self.to_cardinal(high_part) if high_part != 1 else ""
text = "%s %s %s" % (cardinal, self.ords[dec],
self.to_ordinal(low_part))
elif value < 1e18:
dec = 1000 ** int(math.log(int(value), 1000))
high_part, low_part = divmod(value, dec)
cardinal = self.to_cardinal(high_part) if high_part != 1 else ""
text = "%s%s%s %s" % (cardinal, self.ords[dec],
self.gender_stem, self.to_ordinal(low_part))
else:
part1 = self.to_cardinal(value)
text = "%s%s" % (part1[:-1], "onè")
return text.strip()
def to_ordinal_num(self, value):
self.verify_ordinal(value)
if value not in self.ords_2:
return "%s%s" % (value, "è" if self.gender_stem == "è" else "a")
else:
return self.ords_2[value]
def to_currency(self, val, currency="EUR", cents=True,
separator=" amb", adjective=False):
result = super(Num2Word_CA, self).to_currency(
val, currency=currency, cents=cents,
separator=separator, adjective=adjective
)
list_result = result.split(separator + " ")
if currency in CURRENCIES_UNA:
list_result[0] = list_result[0].replace("un", "una")
list_result[0] = list_result[0].replace("dos", "dues")
list_result[0] = list_result[0].replace("cents", "centes")
list_result[0] = list_result[0].replace("vint-i-un", "vint-i-un")
list_result[0] = list_result[0].replace(" i un", "-un")
list_result[0] = list_result[0].replace("un", "un")
if currency in CENTS_UNA:
list_result[1] = list_result[1].replace("un", "una")
list_result[1] = list_result[1].replace("dos", "dues")
list_result[1] = list_result[1].replace("vint-i-un", "vint-i-una")
list_result[1] = list_result[1].replace("un", "un")
result = (separator + " ").join(list_result)
return result

View File

@@ -0,0 +1,522 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2023, Johannes Heinecke. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import unicode_literals
from .currency import parse_currency_parts
from .lang_EU import Num2Word_EU
# Chechen numbers inflect in case if without noun or
# use a special oblique ending when followed by a counted noun
# 4, 14, 40 and composites thereof agree in class (gender) with the
# noun. Chechen has 6 classes which are indicated by the initial
# letter of 4, 14 and 40. By default it is "д" but
# it can also be "б", "й" or "в".
# Indicate the needed class prefix as follows
# num2words(4, lang='ce', case="abs", clazz="б")
CARDINALS = {
"casenames": {
"abs": "Им.",
"gen": "Род.",
"dat": "Дат.",
"erg": "Эрг;",
"instr": "Твор.",
"mat": "Вещ.",
"comp": "Сравнит.",
"all": "Местн.",
},
"casesuffix_cons": { # to be added to numerals with final consonant
"gen": "аннан",
"dat": "анна",
"erg": "амма",
"instr": "анца",
"mat": "аннах",
"comp": "аннал",
"all": "анга",
"obl": "ан",
"ORD": "алгӀа",
},
"casesuffix_voc": { # to be added to numerals with final vowel
"gen": "ннан",
"dat": "нна",
"erg": "мма",
"instr": "нца",
"mat": "ннах",
"comp": "ннал",
"all": "нга",
"obl": "н",
"ORD": "лгӀа",
},
0: {
"attr": "ноль",
"abs": "ноль",
"gen": "нолан",
"dat": "нолана",
"erg": "ноло",
"instr": "ноланца",
"mat": "ноланах",
"comp": "ноланал",
"all": "ноланга",
},
1: {
"attr": "цхьа", # in front of nouns in ABS
"obl": "цхьана", # with nouns in other cases than ABS
"abs": "цхьаъ",
"gen": "цхьаннан",
"dat": "цхьанна",
"erg": "цхьамма",
"instr": "цхьаьнца",
"mat": "цхьаннах",
"comp": "цхьаннал",
"all": "цхаьнга",
"ORD": "цхьалгӀа",
},
2: {
"attr": "ши", # in front of 100, 1000
"obl": "шина",
"abs": "шиъ",
"gen": "шиннан",
"dat": "шинна",
"erg": "шимма",
"instr": "шинца",
"mat": "шиннах",
"comp": "шиннал",
"all": "шинга",
"ORD": "шолгӀа",
},
3: {
"attr": "кхо",
"obl": "кхона",
"abs": "кхоъ",
"gen": "кхааннан",
"dat": "кхаанна",
"erg": "кхаамма",
"instr": "кхаанца",
"mat": "кхааннах",
"comp": "кхааннал",
"all": "кхаанга",
"ORD": "кхоалгӀа",
},
4: {
"attr": "д*и",
"obl": "д*еа",
"abs": "д*иъ",
"gen": "д*еаннан",
"dat": "д*еанна",
"erg": "д*еамма",
"instr": "д*еанца",
"mat": "д*еаннах",
"comp": "д*еаннал",
"all": "д*еанга",
"ORD": "д*оьалгӀа",
},
5: {
"attr": "пхи",
"obl": "пхеа",
"abs": "пхиъ",
"gen": "пхеаннан",
"dat": "пхеанна",
"erg": "пхеамма",
"instr": "нхеанца",
"mat": "пхеаннах",
"comp": "пхеаннал",
"all": "пхеанга",
"ORD": "пхоьалгӀа",
},
6: {
"abs": "ялх",
"attr": "ялх",
"ORD": "йолхалгӀа",
},
7: {
"abs": "ворхӀ",
"attr": "ворхӀ",
"ORD": "ворхӀалгӀа",
},
8: {
"abs": "бархӀ",
"attr": "бархӀ",
"ORD": "борхӀалӀа",
},
9: {
"abs": "исс",
"attr": "исс",
"ORD": "уьссалгӀа",
},
10: {
"attr": "итт",
"abs": "итт",
"gen": "иттаннан",
"dat": "иттанна",
"erg": "иттамма",
"instr": "иттанца",
"mat": "иттаннах",
"comp": "иттаннал",
"all": "иттанга",
"ORD": "уьтталгӀа",
},
11: {
"abs": "цхьайтта",
"attr": "цхьайтта",
"ORD": "цхьайтталгӀа",
},
12: {
"abs": "шийтта",
"attr": "шийтта",
"ORD": "шийтталга",
},
13: {
"abs": "кхойтта",
"attr": "кхойтта",
"ORD": "кхойтталгӀа",
},
14: {
"abs": "д*ейтта",
"attr": "д*ейтта",
"ORD": "д*ейтталгӀа",
},
15: {
"abs": "пхийтта",
"attr": "пхийтта",
"ORD": "пхийтталгӀа",
},
16: {
"abs": "ялхитта",
"attr": "ялхитта",
"ORD": "ялхитталгӀа",
},
17: {
"abs": "вуьрхӀитта",
"attr": "вуьрхӀитта",
"ORD": "вуьрхӀитталгӀа",
},
18: {
"abs": "берхӀитта",
"attr": "берхӀитта",
"ORD": "берхитталӀа",
},
19: {
"abs": "ткъайесна",
"attr": "ткъайесна",
"ORD": "ткъаесналгӀа",
},
20: {
"abs": "ткъа",
"gen": "ткъаннан",
"dat": "ткъанна",
"erg": "ткъамма",
"instr": "ткъанца",
"mat": "ткъаннах",
"comp": "ткъаннал",
"all": "ткъанга",
"attr": "ткъе",
"ORD": "ткъолгӀа",
},
40: {
"abs": "шовзткъа",
"attr": "шовзткъе",
"ORD": "шовзткъалгІа",
},
60: {
"abs": "кхузткъа",
"attr": "кхузткъе",
"ORD": "кхузткъалгІа",
},
80: {
"abs": "дезткъа",
"attr": "дезткъе",
"ORD": "дезткъалгІа",
},
100: {
"attr": "бӀе",
"abs": "бӀе",
"obl": "бӀен",
"gen": "бӀеннан",
"dat": "бӀенна",
"erg": "бӀемма",
"instr": "бӀенца",
"mat": "бӀеннах",
"comp": "бӀеннал",
"all": "бӀенга",
"ORD": "бІолгІа",
},
1000: {
"attr": "эзар",
"abs": "эзар",
"obl": "эзаран",
"gen": "эзарнан",
"dat": "эзарна",
"erg": "эзарно",
"instr": "эзарнаца",
"mat": "эзарнах",
"comp": "эзарнал",
"all": "эзаранга",
"ORD": "эзарлагІа",
},
1000000: {
"attr": "миллион",
"abs": "миллион",
"ORD": "миллионалгІа",
},
}
ILLIONS = {
6: {
"attr": "миллион",
"abs": "миллион",
"ORD": "миллионалгІа",
},
9: {
"attr": "миллиард",
"abs": "миллиард",
"ORD": "миллиардалгІа",
},
12: {
"attr": "биллион",
"abs": "биллион",
"ORD": "биллионалгІа",
},
15: {
"attr": "биллиард",
"abs": "биллиард",
"ORD": "биллиардалгІа",
},
18: {
"attr": "триллион",
"abs": "триллион",
"ORD": "триллионалгІа",
},
21: {
"attr": "триллиард",
"abs": "триллиард",
"ORD": "триллиардалгІа",
},
24: {
"attr": "квадриллион",
"abs": "квадриллион",
"ORD": "квадриллионалгІа",
},
27: {
"attr": "квадриллиард",
"abs": "квадриллиард",
"ORD": "квадриллиардалгІа",
},
30: {
"attr": "квинтиллион",
"abs": "квинтиллион",
"ORD": "квинтиллионалгІа",
},
33: {
"attr": "квинтиллиард",
"abs": "квинтиллиард",
"ORD": "квинтиллиардалгІа",
},
}
MINUS = "минус"
# DECIMALPOINT = "запятая" # check !
DECIMALPOINT = "а"
class Num2Word_CE(Num2Word_EU):
CURRENCY_FORMS = {
# currency code: (sg, pl), (sg, pl)
"EUR": (("Евро", "Евро"), ("Сент", "Сенташ")),
"RUB": (("Сом", "Сомаш"), ("Кепек", "Кепекаш")),
"USD": (("Доллар", "Доллараш"), ("Сент", "Сенташ")),
"GBP": (("Фунт", "Фунташ"), ("Пенни", "Пенни")),
}
def setup(self):
Num2Word_EU.setup(self)
self.negword = "минус"
self.pointword = "запятая" # check !
# self.errmsg_nonnum = (
# u"Seulement des nombres peuvent être convertis en mots."
# )
# self.errmsg_toobig = (
# u"Nombre trop grand pour être converti en mots (abs(%s) > %s)."
# )
# self.exclude_title = ["et", "virgule", "moins"]
self.mid_numwords = []
self.low_numwords = []
self.ords = {}
def to_ordinal(self, number, clazz="д"):
# implement here your code. number is the integer to
# be transformed into an ordinal as a word (str)
# which is returned
return self.to_cardinal(number, clazz=clazz, case="ORD")
def to_cardinal(self, number, clazz="д", case="abs"):
if isinstance(number, float):
entires = self.to_cardinal(int(number))
float_part = str(number).split(".")[1]
postfix = " ".join(
# Drops the trailing zero and comma
[self.to_cardinal(int(c)) for c in float_part]
)
return entires + " " + DECIMALPOINT + " " + postfix
elif number < 20:
return self.makecase(number, case, clazz)
elif number < 100:
twens = number // 20
units = number % 20
base = twens * 20
if units == 0:
return self.makecase(number, case, clazz)
else:
twenties = self.makecase(base, "attr", clazz)
rest = self.to_cardinal(units, clazz=clazz, case=case)
return twenties + " " + rest.replace("д*", clazz)
elif number < 1000:
hundreds = number // 100
tens = number % 100
if hundreds > 1:
hundert = (
CARDINALS[hundreds]["attr"].replace("д*", clazz) + " "
)
else:
hundert = ""
if tens != 0:
rest = self.to_cardinal(tens, clazz=clazz, case=case)
return hundert + CARDINALS[100]["abs"] + " " + rest
else:
return hundert + self.makecase(100, case, clazz)
elif number < 1000000:
thousands = number // 1000
hundert = number % 1000
if hundert > 0:
tcase = "attr"
else:
tcase = case
if thousands > 1:
tausend = (
self.to_cardinal(thousands, clazz=clazz, case="attr")
+ " "
+ CARDINALS[1000][tcase]
)
else:
tausend = self.makecase(1000, tcase, clazz)
if hundert != 0:
rest = " " + self.to_cardinal(hundert, clazz=clazz, case=case)
else:
rest = ""
return tausend + rest
elif number < 10**34:
out = []
for pot in reversed([6, 9, 12, 15, 18, 21, 24, 27, 30, 33]):
# 3 digits of billion, trillion etc
step = number // 10**pot % 1000
if step > 0:
words = self.to_cardinal(step, clazz=clazz, case="attr")
out.append(words + " " + ILLIONS[pot]["attr"])
rest = number % 10**6
if rest:
out.append(self.to_cardinal(rest, clazz=clazz, case=case))
return " ".join(out)
return "NOT IMPLEMENTED"
def _money_verbose(self, number, currency, case):
mcase = "attr"
if case != "abs":
mcase = "obl"
return self.to_cardinal(number, case=mcase)
def _cents_verbose(self, number, currency, case):
mcase = "attr"
if case != "abs":
mcase = "obl"
return self.to_cardinal(number, case=mcase)
def to_currency(
self,
val,
currency="RUB",
cents=True,
separator=",",
adjective=False,
case="abs",
):
"""
Args:
val: Numeric value
currency (str): Currency code
cents (bool): Verbose cents
separator (str): Cent separator
adjective (bool): Prefix currency name with adjective
Returns:
str: Formatted string
"""
left, right, is_negative = parse_currency_parts(val)
try:
cr1, cr2 = self.CURRENCY_FORMS[currency]
devise = cr1[0]
centime = cr2[0]
except KeyError:
raise NotImplementedError(
'Currency code "%s" not implemented for "%s"'
% (currency, self.__class__.__name__)
)
minus_str = "%s " % self.negword.strip() if is_negative else ""
money_str = self._money_verbose(left, currency, case)
cents_str = (
self._cents_verbose(right, currency, case)
if cents
else self._cents_terse(right, currency)
)
return "%s%s %s%s %s %s" % (
minus_str,
money_str,
devise, # always singular
separator,
cents_str,
centime,
)
def to_ordinal_num(self, number):
self.verify_ordinal(number)
return str(number) + ""
def to_year(self, year, case="abs"):
return self.to_cardinal(year, case=case)
def makecase(self, number, case, clazz):
# print("ZZZZ", number, CARDINALS[number])
if case in CARDINALS[number]:
return CARDINALS[number][case].replace("д*", clazz)
else:
if CARDINALS[number]["abs"][-1] in "а":
return (
CARDINALS[number]["abs"].replace("д*", clazz)
+ CARDINALS["casesuffix_voc"][case]
)
else:
return (
CARDINALS[number]["abs"].replace("д*", clazz)
+ CARDINALS["casesuffix_cons"][case]
)

View File

@@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import unicode_literals
from .base import Num2Word_Base
from .utils import get_digits, splitbyx
ZERO = ('nula',)
ONES = {
1: ('jedna',),
2: ('dva',),
3: ('tři',),
4: ('čtyři',),
5: ('pět',),
6: ('šest',),
7: ('sedm',),
8: ('osm',),
9: ('devět',),
}
TENS = {
0: ('deset',),
1: ('jedenáct',),
2: ('dvanáct',),
3: ('třináct',),
4: ('čtrnáct',),
5: ('patnáct',),
6: ('šestnáct',),
7: ('sedmnáct',),
8: ('osmnáct',),
9: ('devatenáct',),
}
TWENTIES = {
2: ('dvacet',),
3: ('třicet',),
4: ('čtyřicet',),
5: ('padesát',),
6: ('šedesát',),
7: ('sedmdesát',),
8: ('osmdesát',),
9: ('devadesát',),
}
HUNDREDS = {
1: ('sto',),
2: ('dvěstě',),
3: ('třista',),
4: ('čtyřista',),
5: ('pětset',),
6: ('šestset',),
7: ('sedmset',),
8: ('osmset',),
9: ('devětset',),
}
THOUSANDS = {
1: ('tisíc', 'tisíce', 'tisíc'), # 10^3
2: ('milion', 'miliony', 'milionů'), # 10^6
3: ('miliarda', 'miliardy', 'miliard'), # 10^9
4: ('bilion', 'biliony', 'bilionů'), # 10^12
5: ('biliarda', 'biliardy', 'biliard'), # 10^15
6: ('trilion', 'triliony', 'trilionů'), # 10^18
7: ('triliarda', 'triliardy', 'triliard'), # 10^21
8: ('kvadrilion', 'kvadriliony', 'kvadrilionů'), # 10^24
9: ('kvadriliarda', 'kvadriliardy', 'kvadriliard'), # 10^27
10: ('quintillion', 'quintilliony', 'quintillionů'), # 10^30
}
class Num2Word_CS(Num2Word_Base):
CURRENCY_FORMS = {
'CZK': (
('koruna', 'koruny', 'korun'), ('halíř', 'halíře', 'haléřů')
),
'EUR': (
('euro', 'euro', 'euro'), ('cent', 'centy', 'centů')
),
}
def setup(self):
self.negword = "mínus"
self.pointword = "celá"
def to_cardinal(self, number):
n = str(number).replace(',', '.')
if '.' in n:
left, right = n.split('.')
leading_zero_count = len(right) - len(right.lstrip('0'))
decimal_part = ((ZERO[0] + ' ') * leading_zero_count +
self._int2word(int(right)))
return u'%s %s %s' % (
self._int2word(int(left)),
self.pointword,
decimal_part
)
else:
return self._int2word(int(n))
def pluralize(self, n, forms):
if n == 1:
form = 0
elif 5 > n % 10 > 1 and (n % 100 < 10 or n % 100 > 20):
form = 1
else:
form = 2
return forms[form]
def to_ordinal(self, number):
raise NotImplementedError()
def _int2word(self, n):
if n == 0:
return ZERO[0]
words = []
chunks = list(splitbyx(str(n), 3))
i = len(chunks)
for x in chunks:
i -= 1
if x == 0:
continue
n1, n2, n3 = get_digits(x)
if n3 > 0:
words.append(HUNDREDS[n3][0])
if n2 > 1:
words.append(TWENTIES[n2][0])
if n2 == 1:
words.append(TENS[n1][0])
elif n1 > 0 and not (i > 0 and x == 1):
words.append(ONES[n1][0])
if i > 0:
words.append(self.pluralize(x, THOUSANDS[i]))
return ' '.join(words)

View File

@@ -0,0 +1,607 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2023, Johannes Heinecke. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import unicode_literals
from .currency import parse_currency_parts
from .lang_EU import Num2Word_EU
# Welsh numerals differs to many other languages since the counted
# object does not follow the numeral but is inserted between
# e.g. "23 hours" is
# tri awr ar hugain
# 3 hour on twenty
# in addition to that some numeral trigger a mutation on the following word
# either another numeral or the counted object
# (https://en.wikipedia.org/wiki/Consonant_mutation#Welsh)
# e.g. "23 dogs" (aspirated mutation, c -> ch)
# tri chi ar hugain
# 3 dog on twenty
# but "22 dogs" (soft mutation, c -> g)
# dau gi ar hugain
# 2 dog on twenty
# and "24 dogs" (no mutation)
# pedwar ci ar hugain
# 4 dog on twenty
# (BTW, the counted word is always in singular when following a numeral)
# numerals are mutated as well
# e.g. "300"
# tri chant
# 3 hundred
# "200"
# dau gant
# 2 hundred
# "500"
# pump cant
# 5 hundreds
# the numerals for 2, 3 and 4 are different in function of gender (MASC, FEM)
# 2 cats
# dwy gath
# 2 dogs
# dau gi
# 2000
# dwy fil
# 3000
# tair mil
# to add the counted object in the correct position use
# num2words(17, lang="cy", counted="ci", gender="masc")
# num2words(17, lang="cy", counted="cath", gender="fem")
# if the number is > 99, use plural form of counted object
# num2words(117, lang="cy", counted="cathod", gender="fem")
# Globals
# -------
OBJ = "__OBJECT__"
CARDINAL_WORDS = {
# masc, fem, triggers mutation
0: [("dim", None), (OBJ, None)],
1: [("un", None), (OBJ, None)],
2: [("dau", "SM"), (OBJ, None)],
3: [("tri", "AM"), (OBJ, None)],
4: [("pedwar", None), (OBJ, None)],
5: [("pump", None), (OBJ, None)],
6: [("chwech", "AM"), (OBJ, None)],
7: [("saith", None), (OBJ, None)],
8: [("wyth", None), (OBJ, None)],
9: [("naw", None), (OBJ, None)],
10: [("deg", None), (OBJ, None)],
11: [("un", None), (OBJ, None), ("ar ddeg", None)],
12: [("deuddeg", None), (OBJ, None)],
13: [("tri", "AM"), (OBJ, None), ("ar ddeg", None)],
14: [("pedwar", None), (OBJ, None), ("ar ddeg", None)],
15: [("pymtheg", None), (OBJ, None)],
16: [("un", None), (OBJ, None), ("ar bymtheg", None)],
17: [("dau", "SM"), (OBJ, None), ("ar bymtheg", None)],
18: [("deunaw", None), (OBJ, None)],
19: [("pedwar", None), ("ar bymtheg", None)],
}
CARDINAL_WORDS_FEM = {
# masc, fem, triggers mutation
0: [("dim", None), (OBJ, None)],
1: [("un", None), (OBJ, None)],
2: [("dwy", "SM"), (OBJ, None)],
3: [("tair", None), (OBJ, None)],
4: [("pedair", None), (OBJ, None)],
5: [("pump", None), (OBJ, None)],
6: [("chwech", "AM"), (OBJ, None)],
7: [("saith", None), (OBJ, None)],
8: [("wyth", None), (OBJ, None)],
9: [("naw", None), (OBJ, None)],
10: [("deg", None), (OBJ, None)],
11: [("un", None), (OBJ, None), ("ar ddeg", None)],
12: [("deuddeg", None), (OBJ, None)],
13: [("tair", None), (OBJ, None), ("ar ddeg", None)],
14: [("pedair", None), (OBJ, None), ("ar ddeg", None)],
15: [("pymtheg", None), (OBJ, None)],
16: [("un", None), (OBJ, None), ("ar bymtheg", None)],
17: [("dwy", "SM"), (OBJ, None), ("ar bymtheg", None)],
18: [("deunaw", None), (OBJ, None)],
19: [("pedair", None), ("ar bymtheg", None)],
}
MILLION_WORDS = {
3: ("mil", None),
6: ("miliwn", None),
9: ("biliwn", None),
12: ("triliwn", None),
15: ("cwadriliwn", None),
18: ("cwintiliwn", None),
21: ("secsttiliwn", None),
24: ("septiliwn", None),
27: ("octiliwn", None),
30: ("noniliwn", None),
33: ("dengiliwn", None),
}
ORDINAL_WORDS = {
0: [("dimfed", None), (OBJ, None)],
1: [(OBJ, None), ("cyntaf", None)],
2: [("ail", "SM"), (OBJ, None)],
3: [("trydydd", None), (OBJ, None)],
4: [("pedwerydd", None), (OBJ, None)],
5: [("pumed", None), (OBJ, None)],
6: [("chweched", None), (OBJ, None)],
7: [("saithfed", None), (OBJ, None)],
8: [("wythfed", None), (OBJ, None)],
9: [("nawfed", None), (OBJ, None)],
10: [("degfed", None), (OBJ, None)],
11: [("unfed", "SM"), (OBJ, None), ("ar ddeg", None)],
12: [("deuddegfed", None), (OBJ, None)],
13: [("trydydd", None), (OBJ, None), ("ar ddeg", None)],
14: [("pedwerydd", None), (OBJ, None), ("ar ddeg", None)],
15: [("pymthegfed", None), (OBJ, None)],
16: [("unfed", None), (OBJ, None), ("ar bymtheg", None)],
17: [("ail", "SM"), (OBJ, None), ("ar bymtheg", None)],
18: [("deunawfed", None), (OBJ, None)],
19: [("pedwerydd", None), (OBJ, None), ("ar bymtheg", None)],
}
ORDINAL_WORDS_FEM = {
0: [("dimfed", None), (OBJ, None)],
1: [(OBJ, None), ("gyntaf", None)],
2: [("ail", "SM"), (OBJ, None)],
3: [("trydedd", "SM"), (OBJ, None)],
4: [("pedwaredd", "SM"), (OBJ, None)],
5: [("pumed", None), (OBJ, None)],
6: [("chweched", None), (OBJ, None)],
7: [("saithfed", None), (OBJ, None)],
8: [("wythfed", None), (OBJ, None)],
9: [("nawfed", None), (OBJ, None)],
10: [("degfed", None), (OBJ, None)],
11: [("unfed", "SM"), (OBJ, None), ("ar ddeg", None)],
12: [("deuddegfed", None), (OBJ, None)],
13: [("trydedd", "SM"), (OBJ, None), ("ar ddeg", None)],
14: [("pedwaredd", "SM"), (OBJ, None), ("ar ddeg", None)],
15: [("pymthegfed", None), (OBJ, None)],
16: [("unfed", None), (OBJ, None), ("ar bymtheg", None)],
17: [("ail", "SM"), (OBJ, None), ("ar bymtheg", None)],
18: [("deunawfed", None), (OBJ, None)],
19: [("pedwaredd", None), (OBJ, None), ("ar bymtheg", None)],
}
# The script can extrapolate the missing numbers from the base forms.
STR_TENS = {
1: [("ugain", None), (OBJ, None)],
2: [("deugain", None), (OBJ, None)],
3: [("trigain", None), (OBJ, None)],
4: [("pedwar ugain", None), (OBJ, None)],
}
ORD_STR_TENS = {
1: [("ugainfed", None), (OBJ, None)],
2: [("deugainfed", None), (OBJ, None)],
3: [("trigainfed", None), (OBJ, None)],
4: [("pedwar ugainfed", None), (OBJ, None)],
}
STR_TENS_INFORMAL = {
1: ("undeg", None),
2: ("dauddeg", None),
3: ("trideg", None),
4: ("pedwardeg", None),
5: ("pumdeg", None),
6: ("chwedeg", None),
7: ("saithdeg", None),
8: ("wythdeg", None),
9: ("nawdeg", None),
}
GENERIC_DOLLARS = ("dolar", "dolarau")
GENERIC_CENTS = ("ceiniog", "ceiniogau")
CURRENCIES_FEM = ["GBP"]
class Num2Word_CY(Num2Word_EU):
CURRENCY_FORMS = {
# currency code: (sg, pl), (sg, pl)
# in Welsh a noun after a numeral is ALWAYS in the singular
"EUR": (("euro", "euros"), GENERIC_CENTS),
"USD": (GENERIC_DOLLARS, GENERIC_CENTS),
"GBP": (("punt", "punnoedd"), ("ceiniog", "ceiniogau")),
"CNY": (("yuan", "yuans"), ("ffen", "ffens")),
}
MINUS_PREFIX_WORD = "meinws "
FLOAT_INFIX_WORD = " pwynt "
# def setup(self):
# Num2Word_EU.setup(self)
def __init__(self):
pass
def float_to_words(self, float_number):
# if ordinal:
# prefix = self.to_ordinal(int(float_number))
# else:
prefix = self.to_cardinal(int(float_number))
float_part = str(float_number).split(".")[1]
postfix = " ".join(
# Drops the trailing zero and comma
[self.to_cardinal(int(c)) for c in float_part]
)
return prefix + Num2Word_CY.FLOAT_INFIX_WORD + postfix
def hundred_group(
self, number, informal=False, gender="masc", ordinal=False
):
hundreds = number // 100
until100 = number % 100 # 0 - 99
# list group of number words and mutation info (for the following word)
result = (
[]
)
if gender == "fem":
CW = CARDINAL_WORDS_FEM
else:
if ordinal:
CW = ORDINAL_WORDS
else:
CW = CARDINAL_WORDS
if hundreds > 0:
if hundreds > 1:
result.extend((CARDINAL_WORDS[hundreds]))
result.extend([("cant", None), (OBJ, None)])
if until100:
if until100 in [
1,
8,
11,
16,
20,
21,
31,
36,
41,
48,
61,
68,
71,
81,
88,
91,
]:
result.append(("ac", None))
else:
result.append(("a", "AM"))
if until100:
# if informal:
# pass
if not ordinal and until100 >= 50 and until100 <= 59:
units = number % 10
if hundreds > 0:
if units == 0:
result.append(("hanner", None))
elif units == 1:
result.extend([("hanner ac un", None), (OBJ, None)])
else:
result.append(("hanner a", "AM"))
result.extend(CW[units])
else:
if units == 0:
result.extend([("hanner cant", None), (OBJ, None)])
elif units == 1:
result.extend(
[("hanner cant ac un", None), (OBJ, None)]
)
else:
result.append(("hanner cant a", "AM"))
result.extend(CW[units])
else:
if (number < 20 and number > 0) or (
number == 0 and hundreds == 0
):
if gender == "fem":
result.extend(CARDINAL_WORDS_FEM[int(number)])
else:
result.extend(CARDINAL_WORDS[int(number)])
else:
tens = until100 // 20
units = number % 20
if ordinal and units == 0:
degau = ORD_STR_TENS.get(tens)
else:
degau = STR_TENS.get(tens)
if units != 0:
if tens > 1:
result.extend(CW[units])
if degau:
result.append(("a", "AM"))
result.extend(degau)
else:
result.extend(CW[units])
if degau:
result.append(("ar", "SM"))
result.extend(degau)
elif degau:
result.extend(degau)
return result
def to_ordinal(self, number, informal=False, gender="masc"):
if number < 20:
return makestring(ORDINAL_WORDS[number])
if number == 100:
return "canfed"
elif number > 100:
raise NotImplementedError("The given number is too large.")
return self.to_cardinal(
number, informal=False, gender=gender, ordinal=True
)
def to_cardinal(
self,
number,
informal=False,
gender="masc",
ordinal=False,
counted=None,
raw=False,
):
negative = False
if number < 0:
negative = True
number = -1 * number
if number == 0:
if raw:
return CARDINAL_WORDS[0]
else:
return makestring(CARDINAL_WORDS[0])
elif not number < 999 * 10**33:
raise NotImplementedError("The given number is too large.")
elif isinstance(number, float):
return self.float_to_words(number)
# split in groups of 10**3
# groups of three digits starting from right (units (1 - 999),
# thousands, millions, ...)
groups = (
[]
)
lowestgroup = (
None # find the lowest group of 3 digits > 0 for the ordinals
)
for pot in [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36]:
gr = (number % 10**pot) // 10 ** (pot - 3)
groups.append((gr, pot))
if gr and not lowestgroup:
lowestgroup = gr
# print("groups", groups)
result = []
if negative:
result.append(("meinws", None))
for gr, pot in reversed(groups):
if gr:
# print("AAAA", gr, pot, gender)
if pot == 6:
g = "fem" # mil (1000) is feminine
elif pot == 3:
g = gender # units depend on the following noun
else:
g = "masc" # millions etc are masculine
# "mil" is feminine
if gr > 1 or pot == 3:
words = self.hundred_group(
gr,
informal=informal,
gender=g,
ordinal=ordinal and (lowestgroup == gr),
)
result += words
# print(">>>> ", words)
if pot > 3:
result.append(MILLION_WORDS[pot - 3])
if raw:
# need to be able trigger correct mutation on currencies
return result
else:
if number < 100:
return makestring(result, counted=counted)
else:
if counted:
result.extend([("o", "SM"), (counted, None)])
return makestring(result)
def to_currency(
self, val, currency="EUR", cents=True, separator=",", adjective=False
):
"""
Args:
val: Numeric value
currency (str): Currency code
cents (bool): Verbose cents
separator (str): Cent separator
adjective (bool): Prefix currency name with adjective
Returns:
str: Formatted string
"""
left, right, is_negative = parse_currency_parts(val)
try:
cr1, cr2 = self.CURRENCY_FORMS[currency]
except KeyError:
raise NotImplementedError(
'Currency code "%s" not implemented for "%s"'
% (currency, self.__class__.__name__)
)
# if adjective and currency in self.CURRENCY_ADJECTIVES:
# cr1 = prefix_currency(self.CURRENCY_ADJECTIVES[currency], cr1)
minus_str = "%s " % self.negword.strip() if is_negative else ""
money_str = self._money_verbose(left, currency)
cents_str = (
self._cents_verbose(right, currency)
if cents
else self._cents_terse(right, currency)
)
if right == 0:
# no pence
return "%s%s" % (
minus_str,
money_str,
# self.pluralize(right, cr2)
)
elif left == 0:
# no pounds
return "%s%s" % (
minus_str,
cents_str,
# self.pluralize(right, cr2)
)
return "%s%s%s %s" % (
minus_str,
money_str,
# self.pluralize(left, cr1),
separator,
cents_str,
# self.pluralize(right, cr2)
)
def _money_verbose(self, number, currency):
# used in super().to_currency(), we need to add gender
# here for feminine currencies
# if currency in CURRENCIES_FEM: # always true in this context
if number > 100:
m = self.to_cardinal(number, gender="fem", raw=True)
# if currency in self.CURRENCY_FORMS:
c = self.CURRENCY_FORMS[currency][0][1]
m.append(("o", "SM"))
m.append((c, None))
# else:
# c = currency
# m.append((c, None))
return makestring(m)
else:
# if number > 1:
m = self.to_cardinal(number, gender="fem", raw=True)
# elif number == 0:
# m = self.to_cardinal(number, gender="fem", raw=True)
# else:
# m = [(OBJ, None)]
# if currency in self.CURRENCY_FORMS:
c = self.CURRENCY_FORMS[currency][0][0]
# else:
# c = currency
# print("eeeeeeeee", m)
# m.append((c, None))
# print("fffffffff", m)
return makestring(m, counted=c)
# else:
# return self.to_cardinal(number, raw=True)
def _cents_verbose(self, number, currency):
if number == 0:
return ""
# elif number > 100:
# m = self.to_cardinal(number, raw=True)
# # if currency in self.CURRENCY_FORMS:
# c = self.CURRENCY_FORMS[currency][0][1]
# m.append(("o", "SM"))
# m.append((c, None))
# # else:
# # c = currency
# # m.append((c, None))
# return makestring(m)
else:
if number > 1:
m = self.to_cardinal(number, raw=True)
else:
m = [(OBJ, None)]
# if currency in self.CURRENCY_FORMS:
c = self.CURRENCY_FORMS[currency][1][0]
# else:
# c = currency
return makestring(m, counted=c)
def makestring(result, counted=None):
# concatenate numberwords with correct mutation
out = []
lastmut = None
for w, mut in result:
if w == OBJ:
if not counted:
continue
else:
w = counted
counted = None # only first position
if lastmut:
out.append(mutate(w, lastmut))
else:
out.append(w)
lastmut = mut
return " ".join(out)
def mutate(word, mutation):
# print("uuu", word, mutation)
if mutation == "SM":
return softmutation(word)
elif mutation == "AM":
return aspiratedmutation(word)
# return word # does not occur
def softmutation(word):
# print("SM<<<<%s>" % word)
if word[0] == "p" and word[1] != "h":
return "b" + word[1:]
elif word[0] == "t" and word[1] != "h":
return "d" + word[1:]
elif word[0] == "c" and word[1] != "h":
return "g" + word[1:]
elif word[0] == "b" or word[0] == "m":
return "f" + word[1:]
elif word[0] == "d" and word[1] != "d":
return "d" + word
elif word.startswith("ll"):
return word[1:]
elif word.startswith("rh"):
return "r" + word[2:]
elif word == "ugain":
return "hugain"
else:
return word
def aspiratedmutation(word):
if word[0] == "p" and word[1] != "h":
return "ph" + word[1:]
elif word[0] == "t" and word[1] != "h":
return "th" + word[1:]
elif word[0] == "c" and word[1] != "h":
return "ch" + word[1:]
else:
return word

View File

@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import division, print_function, unicode_literals
from . import lang_EU
class Num2Word_DA(lang_EU.Num2Word_EU):
GIGA_SUFFIX = "illiarder"
MEGA_SUFFIX = "illioner"
def setup(self):
super(Num2Word_DA, self).setup()
self.negword = "minus "
self.pointword = "komma"
self.exclude_title = ["og", "komma", "minus"]
self.mid_numwords = [(1000, "tusind"), (100, "hundrede"),
(90, "halvfems"), (80, "firs"),
(70, "halvfjerds"), (60, "treds"),
(50, "halvtreds"), (40, "fyrre"), (30, "tredive")]
self.low_numwords = ["tyve", "nitten", "atten", "sytten",
"seksten", "femten", "fjorten", "tretten",
"tolv", "elleve", "ti", "ni", "otte",
"syv", "seks", "fem", "fire", "tre", "to",
"et", "nul"]
self.ords = {"nul": "nul",
"et": "f\xf8rste",
"to": "anden",
"tre": "tredje",
"fire": "fjerde",
"fem": "femte",
"seks": "sjette",
"syv": "syvende",
"otte": "ottende",
"ni": "niende",
"ti": "tiende",
"elleve": "ellevte",
"tolv": "tolvte",
"tretten": "trett",
"fjorten": "fjort",
"femten": "femt",
"seksten": "sekst",
"sytten": "sytt",
"atten": "att",
"nitten": "nitt",
"tyve": "tyv"}
self.ordflag = False
def merge(self, curr, next):
ctext, cnum, ntext, nnum = curr + next
if next[1] == 100 or next[1] == 1000:
lst = list(next)
lst[0] = 'et' + lst[0]
next = tuple(lst)
if cnum == 1:
if nnum < 10 ** 6 or self.ordflag:
return next
ctext = "en"
if nnum > cnum:
if nnum >= 10 ** 6:
ctext += " "
val = cnum * nnum
else:
if cnum >= 100 and cnum < 1000:
ctext += " og "
elif cnum >= 1000 and cnum <= 100000:
ctext += "e og "
if nnum < 10 < cnum < 100:
if nnum == 1:
ntext = "en"
ntext, ctext = ctext, ntext + "og"
elif cnum >= 10 ** 6:
ctext += " "
val = cnum + nnum
word = ctext + ntext
return (word, val)
def to_ordinal(self, value):
self.verify_ordinal(value)
self.ordflag = True
outword = self.to_cardinal(value)
self.ordflag = False
for key in self.ords:
if outword.endswith(key):
outword = outword[:len(outword) - len(key)] + self.ords[key]
break
if value % 100 >= 30 and value % 100 <= 39 or value % 100 == 0:
outword += "te"
elif value % 100 > 12 or value % 100 == 0:
outword += "ende"
return outword
def to_ordinal_num(self, value):
self.verify_ordinal(value)
vaerdte = (0, 1, 5, 6, 11, 12)
if value % 100 >= 30 and value % 100 <= 39 or value % 100 in vaerdte:
return str(value) + "te"
elif value % 100 == 2:
return str(value) + "en"
return str(value) + "ende"
def to_currency(self, val, longval=True):
if val // 100 == 1 or val == 1:
ret = self.to_splitnum(val, hightxt="kr", lowtxt="\xf8re",
jointxt="og", longval=longval)
return "en " + ret[3:]
return self.to_splitnum(val, hightxt="kr", lowtxt="\xf8re",
jointxt="og", longval=longval)
def to_year(self, val, longval=True):
if val == 1:
return 'en'
if not (val // 100) % 10:
return self.to_cardinal(val)
return self.to_splitnum(val, hightxt="hundrede", longval=longval)

View File

@@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import print_function, unicode_literals
import re
from .lang_EU import Num2Word_EU
class Num2Word_DE(Num2Word_EU):
CURRENCY_FORMS = {
'EUR': (('Euro', 'Euro'), ('Cent', 'Cent')),
'GBP': (('Pfund', 'Pfund'), ('Penny', 'Pence')),
'USD': (('Dollar', 'Dollar'), ('Cent', 'Cent')),
'CNY': (('Yuan', 'Yuan'), ('Jiao', 'Fen')),
'DEM': (('Mark', 'Mark'), ('Pfennig', 'Pfennig')),
}
GIGA_SUFFIX = "illiarde"
MEGA_SUFFIX = "illion"
def setup(self):
self.negword = "minus "
self.pointword = "Komma"
# "Cannot treat float %s as ordinal."
self.errmsg_floatord = (
"Die Gleitkommazahl %s kann nicht in eine Ordnungszahl " +
"konvertiert werden."
)
# "type(((type(%s)) ) not in [long, int, float]"
self.errmsg_nonnum = (
"Nur Zahlen (type(%s)) können in Wörter konvertiert werden."
)
# "Cannot treat negative num %s as ordinal."
self.errmsg_negord = (
"Die negative Zahl %s kann nicht in eine Ordnungszahl " +
"konvertiert werden."
)
# "abs(%s) must be less than %s."
self.errmsg_toobig = "Die Zahl %s muss kleiner als %s sein."
self.exclude_title = []
lows = ["Non", "Okt", "Sept", "Sext", "Quint", "Quadr", "Tr", "B", "M"]
units = ["", "un", "duo", "tre", "quattuor", "quin", "sex", "sept",
"okto", "novem"]
tens = ["dez", "vigint", "trigint", "quadragint", "quinquagint",
"sexagint", "septuagint", "oktogint", "nonagint"]
self.high_numwords = (
["zent"] + self.gen_high_numwords(units, tens, lows)
)
self.mid_numwords = [(1000, "tausend"), (100, "hundert"),
(90, "neunzig"), (80, "achtzig"), (70, "siebzig"),
(60, "sechzig"), (50, "f\xFCnfzig"),
(40, "vierzig"), (30, "drei\xDFig")]
self.low_numwords = ["zwanzig", "neunzehn", "achtzehn", "siebzehn",
"sechzehn", "f\xFCnfzehn", "vierzehn", "dreizehn",
"zw\xF6lf", "elf", "zehn", "neun", "acht",
"sieben", "sechs", "f\xFCnf", "vier", "drei",
"zwei", "eins", "null"]
self.ords = {"eins": "ers",
"drei": "drit",
"acht": "ach",
"sieben": "sieb",
"ig": "igs",
"ert": "erts",
"end": "ends",
"ion": "ions",
"nen": "ns",
"rde": "rds",
"rden": "rds"}
def merge(self, curr, next):
ctext, cnum, ntext, nnum = curr + next
if cnum == 1:
if nnum == 100 or nnum == 1000:
return ("ein" + ntext, nnum)
elif nnum < 10 ** 6:
return next
ctext = "eine"
if nnum > cnum:
if nnum >= 10 ** 6:
if cnum > 1:
if ntext.endswith("e"):
ntext += "n"
else:
ntext += "en"
ctext += " "
val = cnum * nnum
else:
if nnum < 10 < cnum < 100:
if nnum == 1:
ntext = "ein"
ntext, ctext = ctext, ntext + "und"
elif cnum >= 10 ** 6:
ctext += " "
val = cnum + nnum
word = ctext + ntext
return (word, val)
def to_ordinal(self, value):
self.verify_ordinal(value)
outword = self.to_cardinal(value).lower()
for key in self.ords:
if outword.endswith(key):
outword = outword[:len(outword) - len(key)] + self.ords[key]
break
res = outword + "te"
# Exception: "hundertste" is usually preferred over "einhundertste"
if res == "eintausendste" or res == "einhundertste":
res = res.replace("ein", "", 1)
# ... similarly for "millionste" etc.
res = re.sub(r'eine ([a-z]+(illion|illiard)ste)$',
lambda m: m.group(1), res)
# Ordinals involving "Million" etc. are written without a space.
# see https://de.wikipedia.org/wiki/Million#Sprachliches
res = re.sub(r' ([a-z]+(illion|illiard)ste)$',
lambda m: m.group(1), res)
return res
def to_ordinal_num(self, value):
self.verify_ordinal(value)
return str(value) + "."
def to_currency(self, val, currency='EUR', cents=True, separator=' und',
adjective=False):
result = super(Num2Word_DE, self).to_currency(
val, currency=currency, cents=cents, separator=separator,
adjective=adjective)
# Handle exception, in german is "ein Euro" and not "eins Euro"
return result.replace("eins ", "ein ")
def to_year(self, val, longval=True):
if not (val // 100) % 10:
return self.to_cardinal(val)
return self.to_splitnum(val, hightxt="hundert", longval=longval)\
.replace(' ', '')

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import division, print_function, unicode_literals
from . import lang_EU
class Num2Word_EN(lang_EU.Num2Word_EU):
def set_high_numwords(self, high):
max = 3 + 3 * len(high)
for word, n in zip(high, range(max, 3, -3)):
self.cards[10 ** n] = word + "illion"
def setup(self):
super(Num2Word_EN, self).setup()
self.negword = "minus "
self.pointword = "point"
self.exclude_title = ["and", "point", "minus"]
self.mid_numwords = [(1000, "thousand"), (100, "hundred"),
(90, "ninety"), (80, "eighty"), (70, "seventy"),
(60, "sixty"), (50, "fifty"), (40, "forty"),
(30, "thirty")]
self.low_numwords = ["twenty", "nineteen", "eighteen", "seventeen",
"sixteen", "fifteen", "fourteen", "thirteen",
"twelve", "eleven", "ten", "nine", "eight",
"seven", "six", "five", "four", "three", "two",
"one", "zero"]
self.ords = {"one": "first",
"two": "second",
"three": "third",
"four": "fourth",
"five": "fifth",
"six": "sixth",
"seven": "seventh",
"eight": "eighth",
"nine": "ninth",
"ten": "tenth",
"eleven": "eleventh",
"twelve": "twelfth"}
def merge(self, lpair, rpair):
ltext, lnum = lpair
rtext, rnum = rpair
if lnum == 1 and rnum < 100:
return (rtext, rnum)
elif 100 > lnum > rnum:
return ("%s-%s" % (ltext, rtext), lnum + rnum)
elif lnum >= 100 > rnum:
return ("%s and %s" % (ltext, rtext), lnum + rnum)
elif rnum > lnum:
return ("%s %s" % (ltext, rtext), lnum * rnum)
return ("%s, %s" % (ltext, rtext), lnum + rnum)
def to_ordinal(self, value):
self.verify_ordinal(value)
outwords = self.to_cardinal(value).split(" ")
lastwords = outwords[-1].split("-")
lastword = lastwords[-1].lower()
try:
lastword = self.ords[lastword]
except KeyError:
if lastword[-1] == "y":
lastword = lastword[:-1] + "ie"
lastword += "th"
lastwords[-1] = self.title(lastword)
outwords[-1] = "-".join(lastwords)
return " ".join(outwords)
def to_ordinal_num(self, value):
self.verify_ordinal(value)
return "%s%s" % (value, self.to_ordinal(value)[-2:])
def to_year(self, val, suffix=None, longval=True):
if val < 0:
val = abs(val)
suffix = 'BC' if not suffix else suffix
high, low = (val // 100, val % 100)
# If year is 00XX, X00X, or beyond 9999, go cardinal.
if (high == 0
or (high % 10 == 0 and low < 10)
or high >= 100):
valtext = self.to_cardinal(val)
else:
hightext = self.to_cardinal(high)
if low == 0:
lowtext = "hundred"
elif low < 10:
lowtext = "oh-%s" % self.to_cardinal(low)
else:
lowtext = self.to_cardinal(low)
valtext = "%s %s" % (hightext, lowtext)
return (valtext if not suffix
else "%s %s" % (valtext, suffix))

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import unicode_literals
from .lang_EN import Num2Word_EN
class Num2Word_EN_IN(Num2Word_EN):
def set_high_numwords(self, high):
self.cards[10 ** 7] = "crore"
self.cards[10 ** 5] = "lakh"

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import unicode_literals
from . import lang_EN
class Num2Word_EN_NG(lang_EN.Num2Word_EN):
CURRENCY_FORMS = {'NGN': (('naira', 'naira'), ('kobo', 'kobo'))}
CURRENCY_ADJECTIVES = {'NGN': 'Nigerian'}
def to_currency(
self, val, currency='NGN',
kobo=True, separator=',',
adjective=False
):
result = super(Num2Word_EN_NG, self).to_currency(
val, currency=currency, cents=kobo, separator=separator,
adjective=adjective)
return result

View File

@@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import print_function, unicode_literals
from .base import Num2Word_Base
class Num2Word_EO(Num2Word_Base):
CURRENCY_FORMS = {
"EUR": (("eŭro", "eŭroj"), ("centimo", "centimoj")),
"USD": (("dolaro", "dolaroj"), ("cendo", "cendoj")),
"FRF": (("franko", "frankoj"), ("centimo", "centimoj")),
"GBP": (("pundo", "pundoj"), ("penco", "pencoj")),
"CNY": (("juano", "juanoj"), ("feno", "fenoj")),
}
GIGA_SUFFIX = "iliardo"
MEGA_SUFFIX = "iliono"
def set_high_numwords(self, high):
cap = 3 + 6 * len(high)
for word, n in zip(high, range(cap, 3, -6)):
if self.GIGA_SUFFIX:
self.cards[10 ** n] = word + self.GIGA_SUFFIX
if self.MEGA_SUFFIX:
self.cards[10 ** (n - 3)] = word + self.MEGA_SUFFIX
def gen_high_numwords(self, units, tens, lows):
out = [u + t for t in tens for u in units]
out.reverse()
return out + lows
def setup(self):
lows = ["naŭ", "ok", "sep", "ses", "kvin", "kvar", "tr", "b", "m"]
units = ["", "un", "duo", "tre", "kvatuor",
"kvin", "seks", "septen", "okto", "novem"]
tens = ["dek", "vigint", "trigint", "kvadragint", "kvinkvagint",
"seksagint", "septuagint", "oktogint", "nonagint"]
self.high_numwords = ["cent"] + self.gen_high_numwords(units, tens,
lows)
self.negword = "minus "
self.pointword = "komo"
self.errmsg_nonnum = u"Sole nombroj povas esti konvertita en vortojn."
self.errmsg_toobig = (
u"Tro granda nombro por esti konvertita en vortojn (abs(%s) > %s)."
)
self.exclude_title = ["kaj", "komo", "minus"]
self.mid_numwords = [(1000, "mil"), (100, "cent"), (90, "naŭdek"),
(80, "okdek"), (70, "sepdek"), (60, "sesdek"),
(50, "kvindek"), (40, "kvardek"), (30, "tridek")]
self.low_numwords = ["dudek", "dek naŭ", "dek ok", "dek sep",
"dek ses", "dek kvin", "dek kvar", "dek tri",
"dek du", "dek unu", "dek", "naŭ", "ok", "sep",
"ses", "kvin", "kvar", "tri", "du", "unu", "nul"]
self.ords = {
"unu": "unua",
"du": "dua",
"tri": "tria",
"kvar": "kvara",
"kvin": "kvina",
"ses": "sesa",
"sep": "sepa",
"ok": "oka",
"naŭ": "naŭa",
"dek": "deka"
}
def merge(self, curr, next):
ctext, cnum, ntext, nnum = curr + next
if cnum == 1 and nnum < 1000000:
return next
if nnum >= 10**6 and cnum > 1:
return ("%s %sj" % (ctext, ntext), cnum + nnum)
if nnum == 100:
return ("%s%s" % (ctext, ntext), cnum + nnum)
return ("%s %s" % (ctext, ntext), cnum + nnum)
def to_ordinal(self, value):
self.verify_ordinal(value)
word = self.to_cardinal(value)
for src, repl in self.ords.items():
if word.endswith(src):
word = word[:-len(src)] + repl
return word
if word.endswith("o"):
word = word[:-1] + "a"
elif word.endswith("oj"):
word = word[:-2] + "a"
else:
word = word + "a"
return word
def to_ordinal_num(self, value):
self.verify_ordinal(value)
out = str(value)
out += "a"
return out
def to_currency(self, val, currency="EUR", cents=True, separator=" kaj",
adjective=False):
result = super(Num2Word_EO, self).to_currency(
val, currency=currency, cents=cents, separator=separator,
adjective=adjective)
return result
def pluralize(self, n, forms):
form = 0 if n <= 1 else 1
return forms[form]

View File

@@ -0,0 +1,408 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import division, print_function, unicode_literals
import math
from .lang_EU import Num2Word_EU
GENERIC_DOLLARS = ('dólar', 'dólares')
GENERIC_CENTS = ('centavo', 'centavos')
CURRENCIES_UNA = ('SLL', 'SEK', 'NOK', 'CZK', 'DKK', 'ISK',
'SKK', 'GBP', 'CYP', 'EGP', 'FKP', 'GIP',
'LBP', 'SDG', 'SHP', 'SSP', 'SYP', 'INR',
'IDR', 'LKR', 'MUR', 'NPR', 'PKR', 'SCR',
'ESP', 'TRY', 'ITL')
CENTS_UNA = ('EGP', 'JOD', 'LBP', 'SDG', 'SSP', 'SYP')
class Num2Word_ES(Num2Word_EU):
CURRENCY_FORMS = {
'EUR': (('euro', 'euros'), ('céntimo', 'céntimos')),
'ESP': (('peseta', 'pesetas'), ('céntimo', 'céntimos')),
'USD': (GENERIC_DOLLARS, GENERIC_CENTS),
'PEN': (('sol', 'soles'), ('céntimo', 'céntimos')),
'CRC': (('colón', 'colones'), GENERIC_CENTS),
'AUD': (GENERIC_DOLLARS, GENERIC_CENTS),
'CAD': (GENERIC_DOLLARS, GENERIC_CENTS),
'GBP': (('libra', 'libras'), ('penique', 'peniques')),
'RUB': (('rublo', 'rublos'), ('kopeyka', 'kopeykas')),
'SEK': (('corona', 'coronas'), ('öre', 'öre')),
'NOK': (('corona', 'coronas'), ('øre', 'øre')),
'PLN': (('zloty', 'zlotys'), ('grosz', 'groszy')),
'MXN': (('peso', 'pesos'), GENERIC_CENTS),
'RON': (('leu', 'leus'), ('ban', 'bani')),
'INR': (('rupia', 'rupias'), ('paisa', 'paisas')),
'HUF': (('florín', 'florines'), ('fillér', 'fillér')),
'FRF': (('franco', 'francos'), ('céntimo', 'céntimos')),
'CNY': (('yuan', 'yuanes'), ('fen', 'jiaos')),
'CZK': (('corona', 'coronas'), ('haléř', 'haléř')),
'NIO': (('córdoba', 'córdobas'), GENERIC_CENTS),
'VES': (('bolívar', 'bolívares'), ('céntimo', 'céntimos')),
'BRL': (('real', 'reales'), GENERIC_CENTS),
'CHF': (('franco', 'francos'), ('céntimo', 'céntimos')),
'JPY': (('yen', 'yenes'), ('sen', 'sen')),
'KRW': (('won', 'wones'), ('jeon', 'jeon')),
'KPW': (('won', 'wones'), ('chon', 'chon')),
'TRY': (('lira', 'liras'), ('kuruş', 'kuruş')),
'ZAR': (('rand', 'rands'), ('céntimo', 'céntimos')),
'KZT': (('tenge', 'tenges'), ('ın', 'ın')),
'UAH': (('hryvnia', 'hryvnias'), ('kopiyka', 'kopiykas')),
'THB': (('baht', 'bahts'), ('satang', 'satang')),
'AED': (('dirham', 'dirhams'), ('fils', 'fils')),
'AFN': (('afghani', 'afghanis'), ('pul', 'puls')),
'ALL': (('lek ', 'leke'), ('qindarkë', 'qindarka')),
'AMD': (('dram', 'drams'), ('luma', 'lumas')),
'ANG': (('florín', 'florines'), GENERIC_CENTS),
'AOA': (('kwanza', 'kwanzas'), ('céntimo', 'céntimos')),
'ARS': (('peso', 'pesos'), GENERIC_CENTS),
'AWG': (('florín', 'florines'), GENERIC_CENTS),
'AZN': (('manat', 'manat'), ('qəpik', 'qəpik')),
'BBD': (GENERIC_DOLLARS, GENERIC_CENTS),
'BDT': (('taka', 'takas'), ('paisa', 'paisas')),
'BGN': (('lev', 'leva'), ('stotinka', 'stotinki')),
'BHD': (('dinar', 'dinares'), ('fils', 'fils')),
'BIF': (('franco', 'francos'), ('céntimo', 'céntimos')),
'BMD': (GENERIC_DOLLARS, GENERIC_CENTS),
'BND': (GENERIC_DOLLARS, GENERIC_CENTS),
'BOB': (('boliviano', 'bolivianos'), GENERIC_CENTS),
'BSD': (GENERIC_DOLLARS, GENERIC_CENTS),
'BTN': (('ngultrum', 'ngultrum'), ('chetrum', 'chetrum')),
'BWP': (('pula', 'pulas'), ('thebe', 'thebes')),
'BYN': (('rublo', 'rublos'), ('kópek', 'kópeks')),
'BYR': (('rublo', 'rublos'), ('kópek', 'kópeks')),
'BZD': (GENERIC_DOLLARS, ('céntimo', 'céntimos')),
'CDF': (('franco', 'francos'), ('céntimo', 'céntimos')),
'CLP': (('peso', 'pesos'), GENERIC_CENTS),
'COP': (('peso', 'pesos'), GENERIC_CENTS),
'CUP': (('peso', 'pesos'), GENERIC_CENTS),
'CVE': (('escudo', 'escudos'), GENERIC_CENTS),
'CYP': (('libra', 'libras'), ('céntimo', 'céntimos')),
'DJF': (('franco', 'francos'), ('céntimo', 'céntimos')),
'DKK': (('corona', 'coronas'), ('øre', 'øre')),
'DOP': (('peso', 'pesos'), GENERIC_CENTS),
'DZD': (('dinar', 'dinares'), ('céntimo', 'céntimos')),
'ECS': (('sucre', 'sucres'), GENERIC_CENTS),
'EGP': (('libra', 'libras'), ('piastra', 'piastras')),
'ERN': (('nakfa', 'nakfas'), ('céntimo', 'céntimos')),
'ETB': (('birr', 'birrs'), ('céntimo', 'céntimos')),
'FJD': (GENERIC_DOLLARS, GENERIC_CENTS),
'FKP': (('libra', 'libras'), ('penique', 'peniques')),
'GEL': (('lari', 'laris'), ('tetri', 'tetris')),
'GHS': (('cedi', 'cedis'), ('pesewa', 'pesewas')),
'GIP': (('libra', 'libras'), ('penique', 'peniques')),
'GMD': (('dalasi', 'dalasis'), ('butut', 'bututs')),
'GNF': (('franco', 'francos'), ('céntimo', 'céntimos')),
'GTQ': (('quetzal', 'quetzales'), GENERIC_CENTS),
'GYD': (GENERIC_DOLLARS, GENERIC_CENTS),
'HKD': (GENERIC_DOLLARS, GENERIC_CENTS),
'HNL': (('lempira', 'lempiras'), GENERIC_CENTS),
'HRK': (('kuna', 'kunas'), ('lipa', 'lipas')),
'HTG': (('gourde', 'gourdes'), ('céntimo', 'céntimos')),
'IDR': (('rupia', 'rupias'), ('céntimo', 'céntimos')),
'ILS': (('séquel', 'séqueles'), ('agora', 'agoras')),
'IQD': (('dinar', 'dinares'), ('fils', 'fils')),
'IRR': (('rial', 'riales'), ('dinar', 'dinares')),
'ISK': (('corona', 'coronas'), ('eyrir', 'aurar')),
'ITL': (('lira', 'liras'), ('céntimo', 'céntimos')),
'JMD': (GENERIC_DOLLARS, ('céntimo', 'céntimos')),
'JOD': (('dinar', 'dinares'), ('piastra', 'piastras')),
'KES': (('chelín', 'chelines'), ('céntimo', 'céntimos')),
'KGS': (('som', 'som'), ('tyiyn', 'tyiyn')),
'KHR': (('riel', 'rieles'), ('céntimo', 'céntimos')),
'KMF': (('franco', 'francos'), ('céntimo', 'céntimos')),
'KWD': (('dinar', 'dinares'), ('fils', 'fils')),
'KYD': (GENERIC_DOLLARS, ('céntimo', 'céntimos')),
'LAK': (('kip', 'kips'), ('att', 'att')),
'LBP': (('libra', 'libras'), ('piastra', 'piastras')),
'LKR': (('rupia', 'rupias'), ('céntimo', 'céntimos')),
'LRD': (GENERIC_DOLLARS, ('céntimo', 'céntimos')),
'LSL': (('loti', 'lotis'), ('céntimo', 'céntimos')),
'LTL': (('lita', 'litas'), ('céntimo', 'céntimos')),
'LVL': (('lat', 'lats'), ('céntimo', 'céntimos')),
'LYD': (('dinar', 'dinares'), ('dírham', 'dírhams')),
'MAD': (('dírham', 'dirhams'), ('céntimo', 'céntimos')),
'MDL': (('leu', 'lei'), ('ban', 'bani')),
'MGA': (('ariary', 'ariaris'), ('iraimbilanja', 'iraimbilanja')),
'MKD': (('denar', 'denares'), ('deni', 'denis')),
'MMK': (('kiat', 'kiats'), ('pya', 'pyas')),
'MNT': (('tugrik', 'tugriks'), ('möngö', 'möngö')),
'MOP': (('pataca', 'patacas'), ('avo', 'avos')),
'MRO': (('ouguiya', 'ouguiyas'), ('khoums', 'khoums')),
'MRU': (('ouguiya', 'ouguiyas'), ('khoums', 'khoums')),
'MUR': (('rupia', 'rupias'), ('céntimo', 'céntimos')),
'MVR': (('rufiyaa', 'rufiyaas'), ('laari', 'laari')),
'MWK': (('kuacha', 'kuachas'), ('tambala', 'tambalas')),
'MYR': (('ringgit', 'ringgit'), ('céntimo', 'céntimos')),
'MZN': (('metical', 'metical'), GENERIC_CENTS),
'NAD': (GENERIC_DOLLARS, ('céntimo', 'céntimos')),
'NGN': (('naira', 'nairas'), ('kobo', 'kobo')),
'NPR': (('rupia', 'rupias'), ('paisa', 'paisas')),
'NZD': (GENERIC_DOLLARS, GENERIC_CENTS),
'OMR': (('rial', 'riales'), ('baisa', 'baisa')),
'PAB': (('balboa', 'balboas'), ('centésimo', 'centésimos')),
'PGK': (('kina', 'kinas'), ('toea', 'toea')),
'PHP': (('peso', 'pesos'), GENERIC_CENTS),
'PKR': (('rupia', 'rupias'), ('paisa', 'paisas')),
'PLZ': (('zloty', 'zlotys'), ('grosz', 'groszy')),
'PYG': (('guaraní', 'guaranís'), ('céntimo', 'céntimos')),
'QAR': (('rial', 'riales'), ('dírham', 'dírhams')),
'QTQ': (('quetzal', 'quetzales'), GENERIC_CENTS),
'RSD': (('dinar', 'dinares'), ('para', 'para')),
'RUR': (('rublo', 'rublos'), ('kopek', 'kopeks')),
'RWF': (('franco', 'francos'), ('céntimo', 'céntimos')),
'SAR': (('riyal', 'riales'), ('halala', 'halalas')),
'SBD': (GENERIC_DOLLARS, ('céntimo', 'céntimos')),
'SCR': (('rupia', 'rupias'), ('céntimo', 'céntimos')),
'SDG': (('libra', 'libras'), ('piastra', 'piastras')),
'SGD': (GENERIC_DOLLARS, ('céntimo', 'céntimos')),
'SHP': (('libra', 'libras'), ('penique', 'peniques')),
'SKK': (('corona', 'coronas'), ('halier', 'haliers')),
'SLL': (('leona', 'leonas'), ('céntimo', 'céntimos')),
'SRD': (GENERIC_DOLLARS, ('céntimo', 'céntimos')),
'SSP': (('libra', 'libras'), ('piastra', 'piastras')),
'STD': (('dobra', 'dobras'), ('céntimo', 'céntimos')),
'SVC': (('colón', 'colones'), GENERIC_CENTS),
'SYP': (('libra', 'libras'), ('piastra', 'piastras')),
'SZL': (('lilangeni', 'emalangeni'), ('céntimo', 'céntimos')),
'TJS': (('somoni', 'somonis'), ('dirame', 'dirames')),
'TMT': (('manat', 'manat'), ('tenge', 'tenge')),
'TND': (('dinar', 'dinares'), ('milésimo', 'milésimos')),
'TOP': (('paanga', 'paangas'), ('céntimo', 'céntimos')),
'TTD': (GENERIC_DOLLARS, ('céntimo', 'céntimos')),
'TWD': (('nuevo dólar', 'nuevos dólares'), ('céntimo', 'céntimos')),
'TZS': (('chelín', 'chelines'), ('céntimo', 'céntimos')),
'UAG': (('hryvnia', 'hryvnias'), ('kopiyka', 'kopiykas')),
'UGX': (('chelín', 'chelines'), ('céntimo', 'céntimos')),
'UYU': (('peso', 'pesos'), ('centésimo', 'centésimos')),
'UZS': (('sum', 'sum'), ('tiyin', 'tiyin')),
'VEF': (('bolívar fuerte', 'bolívares fuertes'),
('céntimo', 'céntimos')),
'VND': (('dong', 'dongs'), ('xu', 'xu')),
'VUV': (('vatu', 'vatu'), ('nenhum', 'nenhum')),
'WST': (('tala', 'tala'), GENERIC_CENTS),
'XAF': (('franco CFA', 'francos CFA'), ('céntimo', 'céntimos')),
'XCD': (GENERIC_DOLLARS, ('céntimo', 'céntimos')),
'XOF': (('franco CFA', 'francos CFA'), ('céntimo', 'céntimos')),
'XPF': (('franco CFP', 'francos CFP'), ('céntimo', 'céntimos')),
'YER': (('rial', 'riales'), ('fils', 'fils')),
'YUM': (('dinar', 'dinares'), ('para', 'para')),
'ZMW': (('kwacha', 'kwachas'), ('ngwee', 'ngwee')),
'ZRZ': (('zaire', 'zaires'), ('likuta', 'makuta')),
'ZWL': (GENERIC_DOLLARS, ('céntimo', 'céntimos')),
'ZWL': (GENERIC_DOLLARS, ('céntimo', 'céntimos')),
}
# //CHECK: Is this sufficient??
GIGA_SUFFIX = None
MEGA_SUFFIX = "illón"
def setup(self):
lows = ["cuatr", "tr", "b", "m"]
self.high_numwords = self.gen_high_numwords([], [], lows)
self.negword = "menos "
self.pointword = "punto"
self.errmsg_nonnum = "type(%s) no es [long, int, float]"
self.errmsg_floatord = "El float %s no puede ser tratado como un" \
" ordinal."
self.errmsg_negord = "El número negativo %s no puede ser tratado" \
" como un ordinal."
self.errmsg_toobig = (
"abs(%s) deber ser inferior a %s."
)
self.gender_stem = "o"
self.exclude_title = ["y", "menos", "punto"]
self.mid_numwords = [(1000, "mil"), (100, "cien"), (90, "noventa"),
(80, "ochenta"), (70, "setenta"), (60, "sesenta"),
(50, "cincuenta"), (40, "cuarenta"),
(30, "treinta")]
self.low_numwords = ["veintinueve", "veintiocho", "veintisiete",
"veintiséis", "veinticinco", "veinticuatro",
"veintitrés", "veintidós", "veintiuno",
"veinte", "diecinueve", "dieciocho", "diecisiete",
"dieciséis", "quince", "catorce", "trece", "doce",
"once", "diez", "nueve", "ocho", "siete", "seis",
"cinco", "cuatro", "tres", "dos", "uno", "cero"]
self.ords = {1: "primer",
2: "segund",
3: "tercer",
4: "cuart",
5: "quint",
6: "sext",
7: "séptim",
8: "octav",
9: "noven",
10: "décim",
20: "vigésim",
30: "trigésim",
40: "quadragésim",
50: "quincuagésim",
60: "sexagésim",
70: "septuagésim",
80: "octogésim",
90: "nonagésim",
100: "centésim",
200: "ducentésim",
300: "tricentésim",
400: "cuadrigentésim",
500: "quingentésim",
600: "sexcentésim",
700: "septigentésim",
800: "octigentésim",
900: "noningentésim",
1e3: "milésim",
1e6: "millonésim",
1e9: "billonésim",
1e12: "trillonésim",
1e15: "cuadrillonésim"}
def merge(self, curr, next):
ctext, cnum, ntext, nnum = curr + next
if cnum == 1:
if nnum < 1000000:
return next
ctext = "un"
elif cnum == 100 and not nnum % 1000 == 0:
ctext += "t" + self.gender_stem
if nnum < cnum:
if cnum < 100:
return "%s y %s" % (ctext, ntext), cnum + nnum
return "%s %s" % (ctext, ntext), cnum + nnum
elif (not nnum % 1000000) and cnum > 1:
ntext = ntext[:-3] + "lones"
if nnum == 100:
if cnum == 5:
ctext = "quinien"
ntext = ""
elif cnum == 7:
ctext = "sete"
elif cnum == 9:
ctext = "nove"
ntext += "t" + self.gender_stem + "s"
else:
ntext = " " + ntext
return (ctext + ntext, cnum * nnum)
def to_ordinal(self, value):
self.verify_ordinal(value)
if value == 0:
text = ""
elif value <= 10:
text = "%s%s" % (self.ords[value], self.gender_stem)
elif value <= 12:
text = (
"%s%s%s" % (self.ords[10], self.gender_stem,
self.to_ordinal(value - 10))
)
elif value <= 100:
dec = (value // 10) * 10
text = (
"%s%s %s" % (self.ords[dec], self.gender_stem,
self.to_ordinal(value - dec))
)
elif value <= 1e3:
cen = (value // 100) * 100
text = (
"%s%s %s" % (self.ords[cen], self.gender_stem,
self.to_ordinal(value - cen))
)
elif value < 1e18:
# Round down to the nearest 1e(3n)
# dec contains the following:
# [ 1e3, 1e6): 1e3
# [ 1e6, 1e9): 1e6
# [ 1e9, 1e12): 1e9
# [1e12, 1e15): 1e12
# [1e15, 1e18): 1e15
dec = 1000 ** int(math.log(int(value), 1000))
# Split the parts before and after the word for 'dec'
# eg (12, 345) = divmod(12_345, 1_000)
high_part, low_part = divmod(value, dec)
cardinal = self.to_cardinal(high_part) if high_part != 1 else ""
text = (
"%s%s%s %s" % (cardinal, self.ords[dec], self.gender_stem,
self.to_ordinal(low_part))
)
else:
text = self.to_cardinal(value)
return text.strip()
def to_ordinal_num(self, value):
self.verify_ordinal(value)
return "%s%s" % (value, "º" if self.gender_stem == 'o' else "ª")
def to_currency(self, val, currency='EUR', cents=True, separator=' con',
adjective=False):
result = super(Num2Word_ES, self).to_currency(
val, currency=currency, cents=cents, separator=separator,
adjective=adjective)
# Handle exception: In Spanish it's "un euro" and not "uno euro",
# except in these currencies, where it's "una": leona, corona,
# libra, lira, rupia, lempira, peseta.
# The same goes for "veintiuna", "treinta y una"...
# Also, this needs to be handled separately for "dollars" and
# "cents".
# All "cents" are masculine except for: piastra.
# Source: https://www.rae.es/dpd/una (section 2.2)
# split "dollars" part from "cents" part
list_result = result.split(separator + " ")
# "DOLLARS" PART (list_result[0])
# Feminine currencies ("una libra", "trescientas libras"...)
if currency in CURRENCIES_UNA:
# "una libra", "veintiuna libras", "treinta y una libras"...
list_result[0] = list_result[0].replace("uno", "una")
# "doscientas libras", "trescientas libras"...
list_result[0] = list_result[0].replace("cientos", "cientas")
# Masc.: Correct orthography for the specific case of "veintiún":
list_result[0] = list_result[0].replace("veintiuno", "veintiún")
# Masculine currencies: general case ("un euro", "treinta y un
# euros"...):
list_result[0] = list_result[0].replace("uno", "un")
# "CENTS" PART (list_result[1])
# Feminine "cents" ("una piastra", "veintiuna piastras"...)
if currency in CENTS_UNA:
# "una piastra", "veintiuna piastras", "treinta y una piastras"...
list_result[1] = list_result[1].replace("uno", "una")
# Masc.: Correct orthography for the specific case of "veintiún":
list_result[1] = list_result[1].replace("veintiuno", "veintiún")
# Masculine "cents": general case ("un centavo", "treinta y un
# centavos"...):
list_result[1] = list_result[1].replace("uno", "un")
# join back "dollars" part with "cents" part
result = (separator + " ").join(list_result)
return result

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import print_function, unicode_literals
from .lang_ES import Num2Word_ES
class Num2Word_ES_CO(Num2Word_ES):
def to_currency(self, val, longval=True, old=False):
result = self.to_splitnum(val, hightxt="peso/s", lowtxt="centavo/s",
divisor=1, jointxt="y", longval=longval)
# Handle exception, in spanish is "un euro" and not "uno euro"
return result.replace("uno", "un")

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# Copyright (c) 2024, Randall Castro. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import print_function, unicode_literals
from .lang_ES import Num2Word_ES
class Num2Word_ES_CR(Num2Word_ES):
def to_currency(self, val, longval=True, old=False):
result = self.to_splitnum(val, hightxt="colón/es", lowtxt="céntimo/s",
divisor=1, jointxt="y", longval=longval)
# Handle exception, in spanish is "un euro" and not "uno euro"
return result.replace("uno", "un")

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import print_function, unicode_literals
from .lang_ES import Num2Word_ES
class Num2Word_ES_GT(Num2Word_ES):
def to_currency(self, val, longval=True, old=False):
result = self.to_splitnum(val, hightxt="quetzal/es",
lowtxt="centavo/s",
divisor=1, jointxt="y",
longval=longval)
# Handle exception, in spanish is "un euro"
# and not "uno euro"
return result.replace("uno", "un")

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import print_function, unicode_literals
from .lang_ES import Num2Word_ES
class Num2Word_ES_NI(Num2Word_ES):
CURRENCY_FORMS = {
'NIO': (('córdoba', 'córdobas'), ('centavo', 'centavos')),
}
def to_currency(self, val, currency='NIO', cents=True, separator=' con',
adjective=False):
result = super(Num2Word_ES, self).to_currency(
val, currency=currency, cents=cents, separator=separator,
adjective=adjective)
return result.replace("uno", "un")

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import print_function, unicode_literals
from .lang_ES import Num2Word_ES
class Num2Word_ES_VE(Num2Word_ES):
def to_currency(self, val, longval=True, old=False):
hightxt = "bolívar/es" if old else "bolívar/es fuerte/s"
result = self.to_splitnum(
val, hightxt=hightxt, lowtxt="centavo/s",
divisor=1, jointxt="y", longval=longval
)
# Handle exception, in spanish is "un euro" and not "uno euro"
return result.replace("uno", "un")

View File

@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import unicode_literals
from .base import Num2Word_Base
GENERIC_DOLLARS = ('dollar', 'dollars')
GENERIC_CENTS = ('cent', 'cents')
class Num2Word_EU(Num2Word_Base):
CURRENCY_FORMS = {
'AUD': (GENERIC_DOLLARS, GENERIC_CENTS),
'BYN': (('rouble', 'roubles'), ('kopek', 'kopeks')),
'CAD': (GENERIC_DOLLARS, GENERIC_CENTS),
# repalced by EUR
'EEK': (('kroon', 'kroons'), ('sent', 'senti')),
'EUR': (('euro', 'euro'), GENERIC_CENTS),
'GBP': (('pound sterling', 'pounds sterling'), ('penny', 'pence')),
# replaced by EUR
'LTL': (('litas', 'litas'), GENERIC_CENTS),
# replaced by EUR
'LVL': (('lat', 'lats'), ('santim', 'santims')),
'USD': (GENERIC_DOLLARS, GENERIC_CENTS),
'RUB': (('rouble', 'roubles'), ('kopek', 'kopeks')),
'SEK': (('krona', 'kronor'), ('öre', 'öre')),
'NOK': (('krone', 'kroner'), ('øre', 'øre')),
'PLN': (('zloty', 'zlotys', 'zlotu'), ('grosz', 'groszy')),
'MXN': (('peso', 'pesos'), GENERIC_CENTS),
'RON': (('leu', 'lei', 'de lei'), ('ban', 'bani', 'de bani')),
'INR': (('rupee', 'rupees'), ('paisa', 'paise')),
'HUF': (('forint', 'forint'), ('fillér', 'fillér')),
'ISK': (('króna', 'krónur'), ('aur', 'aurar')),
'UZS': (('sum', 'sums'), ('tiyin', 'tiyins')),
'SAR': (('saudi riyal', 'saudi riyals'), ('halalah', 'halalas'))
}
CURRENCY_ADJECTIVES = {
'AUD': 'Australian',
'BYN': 'Belarusian',
'CAD': 'Canadian',
'EEK': 'Estonian',
'USD': 'US',
'RUB': 'Russian',
'NOK': 'Norwegian',
'MXN': 'Mexican',
'RON': 'Romanian',
'INR': 'Indian',
'HUF': 'Hungarian',
'ISK': 'íslenskar',
'UZS': 'Uzbekistan',
'SAR': 'Saudi'
}
GIGA_SUFFIX = "illiard"
MEGA_SUFFIX = "illion"
def set_high_numwords(self, high):
cap = 3 + 6 * len(high)
for word, n in zip(high, range(cap, 3, -6)):
if self.GIGA_SUFFIX:
self.cards[10 ** n] = word + self.GIGA_SUFFIX
if self.MEGA_SUFFIX:
self.cards[10 ** (n - 3)] = word + self.MEGA_SUFFIX
def gen_high_numwords(self, units, tens, lows):
out = [u + t for t in tens for u in units]
out.reverse()
return out + lows
def pluralize(self, n, forms):
form = 0 if n == 1 else 1
return forms[form]
def setup(self):
lows = ["non", "oct", "sept", "sext", "quint", "quadr", "tr", "b", "m"]
units = ["", "un", "duo", "tre", "quattuor", "quin", "sex", "sept",
"octo", "novem"]
tens = ["dec", "vigint", "trigint", "quadragint", "quinquagint",
"sexagint", "septuagint", "octogint", "nonagint"]
self.high_numwords = ["cent"] + self.gen_high_numwords(units, tens,
lows)

View File

@@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# Copyright (c) 2018, Abdullah Alhazmy, Alhazmy13. All Rights Reserved.
# Copyright (c) 2020, Hamidreza Kalbasi. All Rights Reserved.
# Copyright (c) 2023, Nika Soltani Tehrani. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from decimal import Decimal
from math import floor
farsiOnes = [
"", "یک", "دو", "سه", "چهار", "پنج", "شش", "هفت", "هشت",
"نه",
"ده",
"یازده",
"دوازده",
"سیزده",
"چهارده",
"پانزده",
"شانزده",
"هفده",
"هجده",
"نوزده",
]
farsiTens = [
"",
"ده",
"بیست",
"سی",
"چهل",
"پنجاه",
"شصت",
"هفتاد",
"هشتاد",
"نود",
]
farsiHundreds = [
"",
"صد",
"دویست",
"سیصد",
"چهارصد",
"پانصد",
"ششصد",
"هفتصد",
"هشتصد",
"نهصد",
]
farsiBig = [
'',
' هزار',
' میلیون',
" میلیارد",
' تریلیون',
" تریلیارد",
]
farsiFrac = ["", "دهم", "صدم"]
farsiFracBig = ["", "هزارم", "میلیونیم", "میلیاردیم"]
farsiSeperator = ' و '
class Num2Word_FA(object):
# Those are unused
errmsg_toobig = "Too large"
MAXNUM = 10 ** 36
def __init__(self):
self.number = 0
def float2tuple(self, value):
pre = int(value)
# Simple way of finding decimal places to update the precision
self.precision = abs(Decimal(str(value)).as_tuple().exponent)
post = abs(value - pre) * 10**self.precision
if abs(round(post) - post) < 0.01:
# We generally floor all values beyond our precision (rather than
# rounding), but in cases where we have something like 1.239999999,
# which is probably due to python's handling of floats, we actually
# want to consider it as 1.24 instead of 1.23
post = int(round(post))
else:
post = int(floor(post))
return pre, post, self.precision
def cardinal3(self, number):
if number <= 19:
return farsiOnes[number]
if number < 100:
x, y = divmod(number, 10)
if y == 0:
return farsiTens[x]
return farsiTens[x] + farsiSeperator + farsiOnes[y]
x, y = divmod(number, 100)
if y == 0:
return farsiHundreds[x]
return farsiHundreds[x] + farsiSeperator + self.cardinal3(y)
def cardinalPos(self, number):
x = number
res = ''
for b in farsiBig:
x, y = divmod(x, 1000)
if y == 0:
continue
yx = self.cardinal3(y) + b
if b == ' هزار' and y == 1:
yx = 'هزار'
if res == '':
res = yx
else:
res = yx + farsiSeperator + res
return res
def fractional(self, number, level):
if number == 5:
return "نیم"
x = self.cardinalPos(number)
ld3, lm3 = divmod(level, 3)
ltext = (farsiFrac[lm3] + " " + farsiFracBig[ld3]).strip()
return x + " " + ltext
def to_currency(self, value):
return self.to_cardinal(value) + " تومان"
def to_ordinal(self, number):
r = self.to_cardinal(number)
if r[-1] == 'ه' and r[-2] == 'س':
return r[:-1] + 'وم'
return r + 'م'
def to_year(self, value):
return self.to_cardinal(value)
@staticmethod
def to_ordinal_num(value):
return str(value)+"م"
def to_cardinal(self, number):
if number < 0:
return "منفی " + self.to_cardinal(-number)
if number == 0:
return "صفر"
x, y, level = self.float2tuple(number)
if y == 0:
return self.cardinalPos(x)
if x == 0:
return self.fractional(y, level)
return self.cardinalPos(x) + farsiSeperator + self.fractional(y, level)

View File

@@ -0,0 +1,736 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import division, print_function, unicode_literals
from collections import OrderedDict
from . import lang_EU
GENERIC_CENTS = ('sentti', 'senttiä')
GENERIC_CENTAVOS = ('centavo', 'centavoa')
# grammatical cases
NOM = 10 # nominative: the dictionary form
GEN = 11 # genitive: ~of/'s
ACC = 12 # accusative: not used; either nominative or genitive
PTV = 13 # partitive: as an object
# locative cases (internal)
INE = 14 # inessive: in
ELA = 15 # elative: from/out of
ILL = 16 # illative: into
# locative cases (external)
ADE = 17 # adessive: at/on
ABL = 18 # ablative: from (after being at/on, not in)
ALL = 19 # allative: to
# essive
ESS = 20 # essive: as (in the role of)
TRANSL = 21 # translative: to (the role of; being sth)
# rare
INSTRUC = 22 # instructive: with (plural is the same as singular)
ABE = 23 # abessive: without
COM = 24 # comitative: together with (plural = singular)
NAME_TO_CASE = {
'nominative': NOM,
'genitive': GEN,
'accusative': ACC,
'partitive': PTV,
'inessive': INE,
'elative': ELA,
'illative': ILL,
'adessive': ADE,
'ablative': ABL,
'allative': ALL,
'essive': ESS,
'translative': TRANSL,
'instructive': INSTRUC,
'abessive': ABE,
'comitative': COM,
}
# https://en.wikibooks.org/wiki/Finnish/Grammar-Vowel_harmony
BACK_TO_FRONT = {
'a': 'ä',
'o': 'ö',
'u': 'y',
}
# https://en.wiktionary.org/wiki/Appendix:Finnish_nominal_inflection
# CASE: (SINGULAR_SUFFIX+, PLURAL_SUFFIX+)
KOTUS_TYPE = {
# Kotus type 5/risti, no gradation
5: {
# grammatical
NOM: ('i', 'it'),
GEN: ('in', 'ien'),
PTV: ('ia', 'eja'),
# locative, internal
INE: ('issa', 'eissa'),
ELA: ('ista', 'eista'),
ILL: ('iin', 'eihin'),
# locative, external
ADE: ('illa', 'eilla'),
ABL: ('ilta', 'eilta'),
ALL: ('ille', 'eille'),
# essive
ESS: ('ina', 'eina'),
TRANSL: ('iksi', 'eiksi'),
# rare
INSTRUC: ('ein', 'ein'),
ABE: ('itta', 'eitta'),
COM: ('eine', 'eine'), # works better
},
# Kotus type 7/ovi, no gradation
7: {
# grammatical
NOM: ('i', 'et'),
GEN: ('en', 'ien'),
PTV: ('ea', 'ia'),
# locative, internal
INE: ('essa', 'issa'),
ELA: ('esta', 'ista'),
ILL: ('een', 'iin'),
# locative, external
ADE: ('ella', 'illa'),
ABL: ('elta', 'ilta'),
ALL: ('elle', 'ille'),
# essive
ESS: ('ena', 'ina'),
TRANSL: ('eksi', 'iksi'),
# rare
INSTRUC: ('in', 'in'),
ABE: ('etta', 'itta'),
COM: ('ine', 'ine'), # works better
},
# Kotus type 8/nalle, no gradation
8: {
# grammatical
NOM: ('e', 'et'),
GEN: ('en', ('ejen', 'ein')),
PTV: ('ea', 'eja'),
# locative, internal
INE: ('essa', 'eissa'),
ELA: ('esta', 'eista'),
ILL: ('een', 'eihin'),
# locative, external
ADE: ('ella', 'eilla'),
ABL: ('elta', 'eilta'),
ALL: ('elle', 'eille'),
# essive
ESS: ('ena', 'eina'),
TRANSL: ('eksi', 'eiksi'),
# rare
INSTRUC: ('ein', 'ein'),
ABE: ('etta', 'eitta'),
COM: ('eine', 'eine'), # works better
},
# Kotus type 9/kala, t-d gradation (sata)
109: {
# grammatical
NOM: ('ta', 'dat'),
GEN: ('dan', ('tojen', 'tain')),
PTV: ('taa', 'toja'),
# locative, internal
INE: ('dassa', 'doissa'),
ELA: ('dasta', 'doista'),
ILL: ('taan', 'toihin'),
# locative, external
ADE: ('dalla', 'doilla'),
ABL: ('dalta', 'doilta'),
ALL: ('dalle', 'doille'),
# essive
ESS: ('tana', 'toina'),
TRANSL: ('daksi', 'doiksi'),
# rare
INSTRUC: ('doin', 'doin'),
ABE: ('datta', 'doitta'),
COM: ('toine', 'toine'), # works better
},
# Kotus type 10/koira, no gradation
10: {
# grammatical
NOM: ('a', 'at'),
GEN: ('an', ('ien', 'ain')),
PTV: ('aa', 'ia'),
# locative, internal
INE: ('assa', 'issa'),
ELA: ('asta', 'ista'),
ILL: ('aan', 'iin'),
# locative, external
ADE: ('alla', 'illa'),
ABL: ('alta', 'ilta'),
ALL: ('alle', 'ille'),
# essive
ESS: ('ana', 'ina'),
TRANSL: ('aksi', 'iksi'),
# rare
INSTRUC: ('in', 'in'),
ABE: ('atta', 'itta'),
COM: ('ine', 'ine'), # works better
},
# Kotus type 27/käsi, t-d gradation
27: {
# grammatical
NOM: ('si', 'det'),
GEN: ('den', ('sien', 'tten')),
PTV: ('tta', 'sia'),
# locative, internal
INE: ('dessa', 'sissa'),
ELA: ('desta', 'sista'),
ILL: ('teen', 'siin'),
# locative, external
ADE: ('della', 'silla'),
ABL: ('delta', 'silta'),
ALL: ('delle', 'sille'),
# essive
ESS: ('tena', 'sina'),
TRANSL: ('deksi', 'siksi'),
# rare
INSTRUC: ('sin', 'sin'),
ABE: ('detta', 'sitta'),
COM: ('sine', 'sine'), # works better
},
# Kotus type 31/kaksi, t-d gradation
31: {
# grammatical
NOM: ('ksi', 'hdet'),
GEN: ('hden', 'ksien'),
PTV: ('hta', 'ksia'),
# locative, internal
INE: ('hdessa', 'ksissa'),
ELA: ('hdesta', 'ksista'),
ILL: ('hteen', 'ksiin'),
# locative, external
ADE: ('hdella', 'ksilla'),
ABL: ('hdelta', 'ksilta'),
ALL: ('hdelle', 'ksille'),
# essive
ESS: ('htena', 'ksina'),
TRANSL: ('hdeksi', 'ksiksi'),
# rare
INSTRUC: ('ksin', 'ksin'),
ABE: ('hdetta', 'ksitta'),
COM: ('ksine', 'ksine'), # works better
},
# Kotus type 32/sisar, no gradation
32: {
# grammatical
NOM: ('', 'et'),
GEN: ('en', ('ien', 'ten')),
PTV: ('ta', 'ia'),
# locative, internal
INE: ('essa', 'issa'),
ELA: ('esta', 'ista'),
ILL: ('een', 'iin'),
# locative, external
ADE: ('ella', 'illa'),
ABL: ('elta', 'ilta'),
ALL: ('elle', 'ille'),
# essive
ESS: ('ena', 'ina'),
TRANSL: ('eksi', 'iksi'),
# rare
INSTRUC: ('in', 'in'),
ABE: ('etta', 'itta'),
COM: ('ine', 'ine'), # works better
},
# Kotus type 38/nainen, no gradation
38: {
# grammatical
NOM: ('nen', 'set'),
GEN: ('sen', ('sten', 'sien')),
PTV: ('sta', 'sia'),
# locative, internal
INE: ('sessa', 'sissa'),
ELA: ('sesta', 'sista'),
ILL: ('seen', 'siin'),
# locative, external
ADE: ('sella', 'silla'),
ABL: ('selta', 'silta'),
ALL: ('selle', 'sille'),
# essive
ESS: ('sena', 'sina'),
TRANSL: ('seksi', 'siksi'),
# rare
INSTRUC: ('sin', 'sin'),
ABE: ('setta', 'sitta'),
COM: ('sine', 'sine'), # works better
},
# Kotus type 45/kahdeksas, nt-nn gradation
45: {
# grammatical
NOM: ('s', 'nnet'),
GEN: ('nnen', 'nsien'),
PTV: ('tta', 'nsia'),
# locative, internal
INE: ('nnessa', 'nsissa'),
ELA: ('nnesta', 'nsista'),
ILL: ('nteen', 'nsiin'),
# locative, external
ADE: ('nnella', 'nsilla'),
ABL: ('nnelta', 'nsilta'),
ALL: ('nnelle', 'nsille'),
# essive
ESS: ('ntena', 'nsina'),
TRANSL: ('nneksi', 'nsiksi'),
# rare
INSTRUC: ('nsin', 'nsin'),
ABE: ('nnetta', 'nsitta'),
COM: ('nsine', 'nsine'), # works better
},
# Kotus type 46/tuhat, nt-nn gradation
46: {
# grammatical
NOM: ('t', 'nnet'),
GEN: ('nnen', ('nsien', 'nten')),
PTV: ('tta', 'nsia'),
# locative, internal
INE: ('nnessa', 'nsissa'),
ELA: ('nnesta', 'nsista'),
ILL: ('nteen', 'nsiin'),
# locative, external
ADE: ('nnella', 'nsilla'),
ABL: ('nnelta', 'nsilta'),
ALL: ('nnelle', 'nsille'),
# essive
ESS: ('ntena', 'nsina'),
TRANSL: ('nneksi', 'nsiksi'),
# rare
INSTRUC: ('nsin', 'nsin'),
ABE: ('nnetta', 'nsitta'),
COM: ('nsine', 'nsine'), # works better
},
}
# kolme
KOTUS_TYPE[108] = {
c: (KOTUS_TYPE[8][c][0], KOTUS_TYPE[7][c][1])
for c in KOTUS_TYPE[8]
}
KOTUS_TYPE[108][INSTRUC] = ('en', 'in')
KOTUS_TYPE[108][ABE] = ('etta', 'itta')
KOTUS_TYPE[108][COM] = ('ine', 'ine')
# seitsemän, kahdeksan, yhdeksän
KOTUS_TYPE[110] = KOTUS_TYPE[10].copy()
KOTUS_TYPE[110][NOM] = ('an', 'at')
# kymmenen
KOTUS_TYPE[132] = KOTUS_TYPE[32].copy()
KOTUS_TYPE[132][NOM] = ('en', 'et')
def inflect(parts, options):
if not isinstance(parts, list):
parts = [parts]
out = ''
for part in parts:
# part is plain text, concat and continue
if not isinstance(part, tuple):
out += part
continue
# predefined case (kaksikymmentä, ...)
tmp_case = options.case
if len(part) == 3:
# override singular nominative only
if options.case == NOM and not options.plural:
tmp_case = part[2]
part = part[:2]
# stem and suffix
stem, kotus_type = part
suffix = KOTUS_TYPE[kotus_type][tmp_case][options.plural]
# many choices, choose preferred or first
if isinstance(suffix, tuple):
common = set(suffix) & set(options.prefer or set())
if len(common) == 1:
suffix = common.pop()
else:
suffix = suffix[0]
# apply vowel harmony
if not set(BACK_TO_FRONT) & set(stem):
for back, front in BACK_TO_FRONT.items():
suffix = suffix.replace(back, front)
# concat
out += stem + suffix
return out
class Options(object):
def __init__(self, ordinal, case, plural, prefer):
self.ordinal = ordinal
self.case = case
self.plural = plural
self.prefer = prefer
def variation(self, ordinal=None, case=None, plural=None, prefer=None):
return Options(
ordinal if ordinal is not None else self.ordinal,
case if case is not None else self.case,
plural if plural is not None else self.plural,
prefer if prefer is not None else self.prefer,
)
class Num2Word_FI(lang_EU.Num2Word_EU):
CURRENCY_FORMS = {
'BRL': (('real', 'realia'), GENERIC_CENTAVOS),
'CHF': (('frangi', 'frangia'), ('rappen', 'rappenia')),
'CNY': (('juan', 'juania'), ('fen', 'feniä')),
'EUR': (('euro', 'euroa'), GENERIC_CENTS),
'FIM': (('markka', 'markkaa'), ('penni', 'penniä')), # historical
'INR': (('rupia', 'rupiaa'), ('paisa', 'paisaa')),
'JPY': (('jeni', 'jeniä'), ('sen', 'seniä')), # rare subunit
'KRW': (('won', 'wonia'), ('jeon', 'jeonia')), # rare subunit
'KPW': (('won', 'wonia'), ('chon', 'chonia')), # rare subunit
'MXN': (('peso', 'pesoa'), GENERIC_CENTAVOS),
'RUB': (('rupla', 'ruplaa'), ('kopeekka', 'kopeekkaa')),
'TRY': (('liira', 'liiraa'), ('kuruş', 'kuruşia')),
'ZAR': (('randi', 'randia'), GENERIC_CENTS),
}
# crowns
for curr_code in 'DKK', 'ISK', 'NOK', 'SEK':
CURRENCY_FORMS[curr_code] = (('kruunu', 'kruunua'), ('äyri', 'äyriä'))
# dollars
for curr_code in 'AUD', 'CAD', 'HKD', 'NZD', 'SGD', 'USD':
CURRENCY_FORMS[curr_code] = (
('dollari', 'dollaria'), GENERIC_CENTS)
# pounds
for curr_code in ('GBP',):
CURRENCY_FORMS[curr_code] = (('punta', 'puntaa'), ('penny', 'pennyä'))
CURRENCY_ADJECTIVES = {
'AUD': 'Australian',
'BRL': 'Brasilian',
'CAD': 'Kanadan',
'CHF': 'Sveitsin',
'DKK': 'Tanskan',
'FIM': 'Suomen', # historical
'GBP': 'Englannin',
'HKD': 'Hongkongin',
'INR': 'Intian',
'ISK': 'Islannin',
'KRW': 'Etelä-Korean',
'KPW': 'Pohjois-Korean',
'MXN': 'Meksikon',
'NOK': 'Norjan',
'NZD': 'Uuden-Seelannin',
'RUB': 'Venäjän',
'SEK': 'Ruotsin',
'SGD': 'Singaporen',
'TRY': 'Turkin',
'USD': 'Yhdysvaltain',
'ZAR': 'Etelä-Afrikan',
}
def __init__(self):
self.ords = OrderedDict()
super(Num2Word_FI, self).__init__()
def set_numwords(self):
self.set_high_numwords(self.high_numwords)
self.set_mid_numwords(self.mid_numwords, self.mid_ords)
self.set_low_numwords(self.low_numwords, self.low_ords)
def set_high_numwords(self, high):
# references:
# https://fi.wikipedia.org/wiki/Suurten_lukujen_nimet
# https://en.wikipedia.org/wiki/Names_of_large_numbers#Standard_dictionary_numbers
# translate to Finnish
replacements = [
("qu", "kv"),
("x", "ks"),
("c", "k"),
("kent", "sent"), # applied after c -> k to cent
]
translated = []
for i, numword in enumerate(high):
# notes:
# - 1e6**9 can be either noviljoona or noniljoona
# - 1e6**38 and above are untested
# 1e6**6 is sekstiljoona but 1e6**16 is sedekiljoona
if numword.startswith("sex") and numword != "sext":
numword = numword.replace("sex", "se")
# 1e6**7 is septiljoona but 1e6**17 is septendekiljoona
elif numword.startswith("sept") and numword != "sept":
numword = "septen" + numword[len("sept"):]
# 1e6**8 is oktiljoona but 1e6**18 is duodevigintiljoona
# (2 from 20)
elif numword.startswith("octo"):
numword = high[i + -10]
numword = "duode" + numword[len("octo"):]
# 1e6**9 is noniljoona but 1e6**19 is undevigintiljoona (1 from 20)
elif numword.startswith("nove"):
numword = high[i + -10]
numword = "unde" + numword[len("nove") + 1:]
# apply general replacements to all numwords
for repl in replacements:
numword = numword.replace(repl[0], repl[1])
translated.append(numword)
max = 6 * len(translated)
for word, n in zip(translated, range(max, 0, -6)):
if n == 6:
# irregularity considering short scale and long scale
self.cards[10 ** 9] = ("miljard", 5)
self.ords[10 ** 9] = ("miljardi", 45)
self.cards[10 ** n] = (word + "iljoon", 10)
self.ords[10 ** n] = (word + "iljoona", 45)
def set_mid_numwords(self, cards, ords):
for key, val in cards:
self.cards[key] = val
for key, val in ords:
self.ords[key] = val
def set_low_numwords(self, cards, ords):
for key, val in cards:
self.cards[key] = val
for key, val in ords:
self.ords[key] = val
def setup(self):
super(Num2Word_FI, self).setup()
self.negword = "miinus "
self.pointword = "pilkku"
self.exclude_title = ["pilkku", "miinus"]
self.mid_numwords = [
(1000, ("tuha", 46)),
(100, ("sa", 109)),
(90, [("yhdeks", 110), ("kymmen", 132, PTV)]),
(80, [("kahdeks", 110), ("kymmen", 132, PTV)]),
(70, [("seitsem", 110), ("kymmen", 132, PTV)]),
(60, [("kuu", 27), ("kymmen", 132, PTV)]),
(50, [("vii", 27), ("kymmen", 132, PTV)]),
(40, [("nelj", 10), ("kymmen", 132, PTV)]),
(30, [("kolm", 108), ("kymmen", 132, PTV)]),
]
self.mid_ords = [
(1000, ("tuhanne", 45)),
(100, ("sada", 45)),
(90, [("yhdeksä", 45), ("kymmene", 45)]),
(80, [("kahdeksa", 45), ("kymmene", 45)]),
(70, [("seitsemä", 45), ("kymmene", 45)]),
(60, [("kuude", 45), ("kymmene", 45)]),
(50, [("viide", 45), ("kymmene", 45)]),
(40, [("neljä", 45), ("kymmene", 45)]),
(30, [("kolma", 45), ("kymmene", 45)]),
]
self.low_numwords = [
(20, [("ka", 31), ("kymmen", 132, PTV)]),
(19, [("yhdeks", 110), "toista"]),
(18, [("kahdeks", 110), "toista"]),
(17, [("seitsem", 110), "toista"]),
(16, [("kuu", 27), "toista"]),
(15, [("vii", 27), "toista"]),
(14, [("nelj", 10), "toista"]),
(13, [("kolm", 108), "toista"]),
(12, [("ka", 31), "toista"]),
(11, [("y", 31), "toista"]),
(10, ("kymmen", 132)),
(9, ("yhdeks", 110)),
(8, ("kahdeks", 110)),
(7, ("seitsem", 110)),
(6, ("kuu", 27)),
(5, ("vii", 27)),
(4, ("nelj", 10)),
(3, ("kolm", 108)),
(2, ("ka", 31)),
(1, ("y", 31)),
(0, ("noll", 10)),
]
self.low_ords = [
(20, [("kahde", 45), ("kymmene", 45)]),
(19, [("yhdeksä", 45), "toista"]),
(18, [("kahdeksa", 45), "toista"]),
(17, [("seitsemä", 45), "toista"]),
(16, [("kuude", 45), "toista"]),
(15, [("viide", 45), "toista"]),
(14, [("neljä", 45), "toista"]),
(13, [("kolma", 45), "toista"]),
(12, [("kahde", 45), "toista"]),
(11, [("yhde", 45), "toista"]),
(10, ("kymmene", 45)),
(9, ("yhdeksä", 45)),
(8, ("kahdeksa", 45)),
(7, ("seitsemä", 45)),
(6, ("kuude", 45)),
(5, ("viide", 45)),
(4, ("neljä", 45)),
(3, ("kolma", 45)),
(2, ("toi", 38)),
(1, ("ensimmäi", 38)),
(0, ("nolla", 45)),
]
def merge(self, lpair, rpair, options):
ltext, lnum = lpair
rtext, rnum = rpair
# http://www.kielitoimistonohjepankki.fi/ohje/49
fmt = "%s%s"
# ignore lpair if lnum is 1
if lnum == 1:
rtext = inflect(rtext, options)
return (rtext, rnum)
# rnum is added to lnum
elif lnum > rnum:
ltext = inflect(ltext, options)
rtext = inflect(rtext, options)
# separate groups with space
if lnum >= 1000:
fmt = "%s %s"
return (fmt % (ltext, rtext), lnum + rnum)
# rnum is multiplied by lnum
elif lnum < rnum:
if options.ordinal:
# kahdessadas, not toinensadas
if lnum == 2:
ltext = ("kahde", 45)
rtext = inflect(rtext, options)
else:
# kaksituhatta but kahdettuhannet
rcase = options.case
if options.case == NOM and not options.plural:
rcase = PTV
rtext = inflect(rtext, options.variation(case=rcase))
ltext = inflect(ltext, options)
return (fmt % (ltext, rtext), lnum * rnum)
def to_cardinal(self, value, case='nominative', plural=False, prefer=None):
case = NAME_TO_CASE[case]
options = Options(False, case, plural, prefer)
try:
assert int(value) == value
except (ValueError, TypeError, AssertionError):
if case != NOM:
raise NotImplementedError(
"Cases other than nominative are not implemented for "
"cardinal floating point numbers.")
return self.to_cardinal_float(value)
out = ""
if value < 0:
value = abs(value)
out = self.negword
if value >= self.MAXVAL:
raise OverflowError(self.errmsg_toobig % (value, self.MAXVAL))
val = self.splitnum(value, options)
words, num = self.clean(val, options)
return self.title(out + words)
def to_ordinal(self, value, case='nominative', plural=False, prefer=None):
case = NAME_TO_CASE[case]
options = Options(True, case, plural, prefer)
self.verify_ordinal(value)
if value >= self.MAXVAL:
raise OverflowError(self.errmsg_toobig % (value, self.MAXVAL))
val = self.splitnum(value, options)
words, num = self.clean(val, options)
return self.title(words)
def to_ordinal_num(self, value, case='nominative', plural=False):
case = NAME_TO_CASE[case]
raise NotImplementedError
def to_year(self, val, suffix=None, longval=True):
suffix = suffix or ""
if val < 0:
val = abs(val)
suffix = suffix or " ennen ajanlaskun alkua"
return self.to_cardinal(val).replace(" ", "") + suffix
def to_currency(self, val, currency="EUR", cents=True, separator=" ja",
adjective=False):
return super(Num2Word_FI, self).to_currency(
val, currency=currency, cents=cents, separator=separator,
adjective=adjective)
def splitnum(self, value, options):
elems = self.ords if options.ordinal else self.cards
for elem in elems:
if elem > value:
continue
out = []
if value == 0:
div, mod = 1, 0
else:
div, mod = divmod(value, elem)
if div == 1:
out.append((elems[1], 1))
else:
if div == value: # The system tallies, eg Roman Numerals
return [(div * elems[elem], div*elem)]
out.append(self.splitnum(div, options))
out.append((elems[elem], elem))
if mod:
out.append(self.splitnum(mod, options))
return out
def clean(self, val, options):
out = val
while len(val) != 1:
out = []
left, right = val[:2]
if isinstance(left, tuple) and isinstance(right, tuple):
out.append(self.merge(left, right, options))
if val[2:]:
out.append(val[2:])
else:
for elem in val:
if isinstance(elem, list):
if len(elem) == 1:
out.append(elem[0])
else:
out.append(self.clean(elem, options))
else:
out.append(elem)
val = out
return out[0]

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import print_function, unicode_literals
from .lang_EU import Num2Word_EU
class Num2Word_FR(Num2Word_EU):
CURRENCY_FORMS = {
'EUR': (('euro', 'euros'), ('centime', 'centimes')),
'USD': (('dollar', 'dollars'), ('cent', 'cents')),
'FRF': (('franc', 'francs'), ('centime', 'centimes')),
'GBP': (('livre', 'livres'), ('penny', 'pence')),
'CNY': (('yuan', 'yuans'), ('fen', 'jiaos')),
}
def setup(self):
Num2Word_EU.setup(self)
self.negword = "moins "
self.pointword = "virgule"
self.errmsg_nonnum = (
u"Seulement des nombres peuvent être convertis en mots."
)
self.errmsg_toobig = (
u"Nombre trop grand pour être converti en mots (abs(%s) > %s)."
)
self.exclude_title = ["et", "virgule", "moins"]
self.mid_numwords = [(1000, "mille"), (100, "cent"),
(80, "quatre-vingts"), (60, "soixante"),
(50, "cinquante"), (40, "quarante"),
(30, "trente")]
self.low_numwords = ["vingt", "dix-neuf", "dix-huit", "dix-sept",
"seize", "quinze", "quatorze", "treize", "douze",
"onze", "dix", "neuf", "huit", "sept", "six",
"cinq", "quatre", "trois", "deux", "un", "zéro"]
self.ords = {
"cinq": "cinquième",
"neuf": "neuvième",
}
def merge(self, curr, next):
ctext, cnum, ntext, nnum = curr + next
if cnum == 1:
if nnum < 1000000:
return next
else:
if (not (cnum - 80) % 100
or (not cnum % 100 and cnum < 1000))\
and nnum < 1000000 \
and ctext[-1] == "s":
ctext = ctext[:-1]
if cnum < 1000 and nnum != 1000 and \
ntext[-1] != "s" and not nnum % 100:
ntext += "s"
if nnum < cnum < 100:
if nnum % 10 == 1 and cnum != 80:
return ("%s et %s" % (ctext, ntext), cnum + nnum)
return ("%s-%s" % (ctext, ntext), cnum + nnum)
if nnum > cnum:
return ("%s %s" % (ctext, ntext), cnum * nnum)
return ("%s %s" % (ctext, ntext), cnum + nnum)
# Is this right for such things as 1001 - "mille unième" instead of
# "mille premier"?? "millième"??
def to_ordinal(self, value):
self.verify_ordinal(value)
if value == 1:
return "premier"
word = self.to_cardinal(value)
for src, repl in self.ords.items():
if word.endswith(src):
word = word[:-len(src)] + repl
break
else:
if word[-1] == "e":
word = word[:-1]
word = word + "ième"
return word
def to_ordinal_num(self, value):
self.verify_ordinal(value)
out = str(value)
out += "er" if value == 1 else "me"
return out
def to_currency(self, val, currency='EUR', cents=True, separator=' et',
adjective=False):
result = super(Num2Word_FR, self).to_currency(
val, currency=currency, cents=cents, separator=separator,
adjective=adjective)
return result

View File

@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import print_function, unicode_literals
from .lang_FR import Num2Word_FR
class Num2Word_FR_BE(Num2Word_FR):
def setup(self):
Num2Word_FR.setup(self)
self.mid_numwords = [(1000, "mille"), (100, "cent"), (90, "nonante"),
(80, "quatre-vingt"), (70, "septante"),
(60, "soixante"), (50, "cinquante"),
(40, "quarante"), (30, "trente")]
def merge(self, curr, next):
ctext, cnum, ntext, nnum = curr + next
if cnum == 1:
if nnum < 1000000:
return next
if cnum < 1000 and nnum != 1000 and\
ntext[-1] != "s" and not nnum % 100:
ntext += "s"
if nnum < cnum < 100:
if nnum % 10 == 1:
return ("%s et %s" % (ctext, ntext), cnum + nnum)
return ("%s-%s" % (ctext, ntext), cnum + nnum)
if nnum > cnum:
return ("%s %s" % (ctext, ntext), cnum * nnum)
return ("%s %s" % (ctext, ntext), cnum + nnum)

View File

@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import print_function, unicode_literals
from .lang_FR import Num2Word_FR
class Num2Word_FR_CH(Num2Word_FR):
def setup(self):
Num2Word_FR.setup(self)
self.mid_numwords = [(1000, "mille"), (100, "cent"), (90, "nonante"),
(80, "huitante"), (70, "septante"),
(60, "soixante"), (50, "cinquante"),
(40, "quarante"), (30, "trente")]
def merge(self, curr, next):
ctext, cnum, ntext, nnum = curr + next
if cnum == 1:
if nnum < 1000000:
return next
if cnum < 1000 and nnum != 1000 and\
ntext[-1] != "s" and not nnum % 100:
ntext += "s"
if nnum < cnum < 100:
if nnum % 10 == 1:
return ("%s et %s" % (ctext, ntext), cnum + nnum)
return ("%s-%s" % (ctext, ntext), cnum + nnum)
if nnum > cnum:
return ("%s %s" % (ctext, ntext), cnum * nnum)
return ("%s %s" % (ctext, ntext), cnum + nnum)

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import unicode_literals
from .lang_FR import Num2Word_FR
class Num2Word_FR_DZ(Num2Word_FR):
CURRENCY_FORMS = {
'DIN': (('dinard', 'dinards'), ('centime', 'centimes')),
}
def to_currency(self, val, currency='DIN', cents=True, separator=' et',
adjective=False):
result = super(Num2Word_FR, self).to_currency(
val, currency=currency, cents=cents, separator=separator,
adjective=adjective)
return result

View File

@@ -0,0 +1,327 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import print_function, unicode_literals
from .base import Num2Word_Base
from .compat import to_s
from .currency import parse_currency_parts
from .utils import get_digits, splitbyx
ZERO = (u'אפס',)
ONES = {
1: (u'אחת', u'אחד', u'אחת', u'אחד',
u'ראשונה', u'ראשון', u'ראשונות', u'ראשונים'),
2: (u'שתיים', u'שניים', u'שתי', u'שני',
u'שנייה', u'שני', u'שניות', u'שניים'),
3: (u'שלוש', u'שלושה', u'שלוש', u'שלושת',
u'שלישית', u'שלישי', u'שלישיות', u'שלישיים'),
4: (u'ארבע', u'ארבעה', u'ארבע', u'ארבעת',
u'רביעית', u'רביעי', u'רביעיות', u'רביעיים'),
5: (u'חמש', u'חמישה', u'חמש', u'חמשת',
u'חמישית', u'חמישי', u'חמישיות', u'חמישיים'),
6: (u'שש', u'שישה', u'שש', u'ששת',
u'שישית', u'שישי', u'שישיות', u'שישיים'),
7: (u'שבע', u'שבעה', u'שבע', u'שבעת',
u'שביעית', u'שביעי', u'שביעיות', u'שביעיים'),
8: (u'שמונה', u'שמונה', u'שמונה', u'שמונת',
u'שמינית', u'שמיני', u'שמיניות', u'שמיניים'),
9: (u'תשע', u'תשעה', u'תשע', u'תשעת',
u'תשיעית', u'תשיעי', u'תשיעיות', u'תשיעיים'),
}
TENS = {
0: (u'עשר', u'עשרה', u'עשר', u'עשרת',
u'עשירית', u'עשירי', u'עשיריות', u'עשיריים'),
1: (u'עשרה', u'עשר'),
2: (u'שתים עשרה', u'שנים עשר'),
}
TWENTIES = {
2: (u'עשרים',),
3: (u'שלושים',),
4: (u'ארבעים',),
5: (u'חמישים',),
6: (u'שישים',),
7: (u'שבעים',),
8: (u'שמונים',),
9: (u'תשעים',),
}
HUNDREDS = {
1: (u'מאה', u'מאת'),
2: (u'מאתיים',),
3: (u'מאות',)
}
THOUSANDS = {
1: (u'אלף',),
2: (u'אלפיים',),
3: (u'אלפים', 'אלפי'),
}
LARGE = {
1: (u'מיליון', u'מיליוני'),
2: (u'מיליארד', u'מיליארדי'),
3: (u'טריליון', u'טריליוני'),
4: (u'קוודריליון', u'קוודריליוני'),
5: (u'קווינטיליון', u'קווינטיליוני'),
6: (u'סקסטיליון', u'סקסטיליוני'),
7: (u'ספטיליון', u'ספטיליוני'),
8: (u'אוקטיליון', u'אוקטיליוני'),
9: (u'נוניליון', u'נוניליוני'),
10: (u'דסיליון', u'דסיליוני'),
11: (u'אונדסיליון', u'אונדסיליוני'),
12: (u'דואודסיליון', u'דואודסיליוני'),
13: (u'טרדסיליון', u'טרדסיליוני'),
14: (u'קווטואורדסיליון', u'קווטואורדסיליוני'),
15: (u'קווינדסיליון', u'קווינדסיליוני'),
16: (u'סקסדסיליון', u'סקסדסיליוני'),
17: (u'ספטנדסיליון', u'ספטנדסיליוני'),
18: (u'אוקטודסיליון', u'אוקטודסיליוני'),
19: (u'נובמדסיליון', u'נובמדסיליוני'),
20: (u'ויגינטיליון', u'ויגינטיליוני')
}
AND = u'ו'
DEF = u'ה'
MAXVAL = int('1' + '0'*66)
def chunk2word(n, i, x, gender='f', construct=False,
ordinal=False, plural=False):
words = []
n1, n2, n3 = get_digits(x)
if n3 > 0:
if construct and n == 100:
words.append(HUNDREDS[n3][1])
elif n3 <= 2:
words.append(HUNDREDS[n3][0])
else:
words.append(ONES[n3][0] + ' ' + HUNDREDS[3][0])
if n2 > 1:
words.append(TWENTIES[n2][0])
if i == 0 or x >= 11:
male = gender == 'm' or i > 0
cop = (2*(construct and i == 0)+4*ordinal+2*plural) * (n < 11)
if n2 == 1:
if n1 == 0:
words.append(TENS[n1][male + cop])
elif n1 == 2:
words.append(TENS[n1][male])
else:
words.append(ONES[n1][male] + ' ' + TENS[1][male])
elif n1 > 0:
words.append(ONES[n1][male + cop])
construct_last = construct and (n % 1000 ** i == 0)
if i == 1:
if x >= 11:
words[-1] = words[-1] + ' ' + THOUSANDS[1][0]
elif n1 == 0:
words.append(TENS[0][3] + ' ' + THOUSANDS[3][construct_last])
elif n1 <= 2:
words.append(THOUSANDS[n1][0])
else:
words.append(ONES[n1][3] + ' ' + THOUSANDS[3][construct_last])
elif i > 1:
if x >= 11:
words[-1] = words[-1] + ' ' + LARGE[i - 1][construct_last]
elif n1 == 0:
words.append(TENS[0][1 + 2*construct_last] + ' ' +
LARGE[i - 1][construct_last])
elif n1 == 1:
words.append(LARGE[i - 1][0])
else:
words.append(ONES[n1][1 + 2*(construct_last or x == 2)] + ' ' +
LARGE[i - 1][construct_last])
return words
def int2word(n, gender='f', construct=False,
ordinal=False, definite=False, plural=False):
assert n == int(n)
assert not construct or not ordinal
assert ordinal or (not definite and not plural)
if n >= MAXVAL:
raise OverflowError('abs(%s) must be less than %s.' % (n, MAXVAL))
if n == 0:
if ordinal:
return DEF + ZERO[0]
return ZERO[0]
words = []
chunks = list(splitbyx(str(n), 3))
i = len(chunks)
for x in chunks:
i -= 1
if x == 0:
continue
words += chunk2word(n, i, x, gender=gender, construct=construct,
ordinal=ordinal, plural=plural)
# https://hebrew-academy.org.il/2017/01/30/%D7%95-%D7%94%D7%97%D7%99%D7%91%D7%95%D7%A8-%D7%91%D7%9E%D7%A1%D7%A4%D7%A8%D7%99%D7%9D # noqa
if len(words) > 1:
words[-1] = AND + words[-1]
if ordinal and (n >= 11 or definite):
words[0] = DEF + words[0]
return ' '.join(words)
class Num2Word_HE(Num2Word_Base):
CURRENCY_FORMS = {
'ILS': ((u'שקל', u'שקלים'), (u'אגורה', u'אגורות')),
'EUR': ((u'אירו', u'אירו'), (u'סנט', u'סנטים')),
'USD': ((u'דולר', u'דולרים'), (u'סנט', u'סנטים')),
}
CURRENCY_GENDERS = {
'ILS': ('m', 'f'),
'EUR': ('m', 'm'),
'USD': ('m', 'm'),
}
def __init__(self, makaf='-'):
super(Num2Word_HE, self).__init__()
self.makaf = makaf
def setup(self):
super(Num2Word_HE, self).setup()
self.negword = u'מינוס'
self.pointword = u'נקודה'
self.MAXVAL = MAXVAL
def to_cardinal_float(self, value, gender='f'):
try:
float(value) == value
except (ValueError, TypeError, AssertionError, AttributeError):
raise TypeError(self.errmsg_nonnum % value)
pre, post = self.float2tuple(float(value))
post = str(post)
post = '0'*(self.precision - len(post)) + post
out = [self.to_cardinal(pre, gender=gender)]
if self.precision:
out.append(self.title(self.pointword))
for i in range(self.precision):
curr = int(post[i])
out.append(to_s(self.to_cardinal(curr)))
return ' '.join(out)
def to_cardinal(self, value, gender='f', construct=False):
try:
assert int(value) == value
except (ValueError, TypeError, AssertionError):
# https://hebrew-academy.org.il/2019/12/03/%D7%A2%D7%9C-%D7%94%D7%91%D7%A2%D7%AA-%D7%94%D7%9E%D7%A1%D7%A4%D7%A8-%D7%94%D7%9E%D7%A2%D7%95%D7%A8%D7%91 # noqa
return self.to_cardinal_float(value, gender=gender)
out = ""
if value < 0:
value = abs(value)
out = "%s " % self.negword.strip()
if value >= self.MAXVAL:
raise OverflowError(self.errmsg_toobig % (value, self.MAXVAL))
return out + int2word(int(value), gender=gender, construct=construct)
def to_ordinal(self, value, gender='m', definite=False, plural=False):
self.verify_ordinal(value)
if value >= self.MAXVAL:
raise OverflowError(self.errmsg_toobig % (value, self.MAXVAL))
return int2word(int(value), gender=gender, ordinal=True,
definite=definite, plural=plural)
def pluralize(self, n, forms, currency=None, prefer_singular=False):
assert n == int(n)
form = 1
if n == 1 or prefer_singular and (
abs(n) >= 11 or n == 0 or currency != 'ILS'):
form = 0
return forms[form]
def to_currency(self, val, currency='ILS', cents=True,
separator=AND, adjective=False,
prefer_singular=False, prefer_singular_cents=False):
left, right, is_negative = parse_currency_parts(val)
if not separator.startswith(' '):
separator = ' ' + separator
try:
cr1, cr2 = self.CURRENCY_FORMS[currency]
except KeyError:
raise NotImplementedError(
'Currency code "%s" not implemented for "%s"' %
(currency, self.__class__.__name__))
minus_str = "%s " % self.negword.strip() if is_negative else ""
try:
gender1, gender2 = self.CURRENCY_GENDERS[currency]
except KeyError:
gender1 = gender2 = ''
money_str = self.to_cardinal(left, gender=gender1,
construct=left == 2)
if cents:
cents_str = self.to_cardinal(right, gender=gender2,
construct=right == 2)
else:
cents_str = self._cents_terse(right, currency)
sep_parts = separator.split()
if sep_parts and sep_parts[-1] == AND:
separator += self.makaf
strings = [
minus_str,
money_str,
self.pluralize(left, cr1, currency=currency,
prefer_singular=prefer_singular),
separator,
cents_str,
self.pluralize(right, cr2, currency=currency,
prefer_singular=prefer_singular_cents)
]
if left == 1:
strings[1], strings[2] = strings[2], strings[1]
if right == 1:
strings[4], strings[5] = strings[5], strings[4]
# In Hebrew the separator is along with the following word
return u'%s%s %s%s%s %s' % tuple(strings)

View File

@@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import division, print_function, unicode_literals
from . import lang_EU
ZERO = 'nulla'
class Num2Word_HU(lang_EU.Num2Word_EU):
GIGA_SUFFIX = "illiárd"
MEGA_SUFFIX = "illió"
def setup(self):
super(Num2Word_HU, self).setup()
self.negword = "mínusz "
self.pointword = "egész"
self.mid_numwords = [(1000, "ezer"), (100, "száz"), (90, "kilencven"),
(80, "nyolcvan"), (70, "hetven"), (60, "hatvan"),
(50, "ötven"), (40, "negyven"), (30, "harminc")]
low_numwords = ["kilenc", "nyolc", "hét", "hat", "öt", "négy", "három",
"kettő", "egy"]
self.low_numwords = (['tizen' + w for w in low_numwords]
+ ['tíz']
+ low_numwords)
self.low_numwords = (['huszon' + w for w in low_numwords]
+ ['húsz']
+ self.low_numwords
+ [ZERO])
self.partial_ords = {
'nulla': 'nullad',
'egy': 'egyed',
'kettő': 'ketted',
'három': 'harmad',
'négy': 'negyed',
'öt': 'ötöd',
'hat': 'hatod',
'hét': 'heted',
'nyolc': 'nyolcad',
'kilenc': 'kilenced',
'tíz': 'tized',
'húsz': 'huszad',
'harminc': 'harmincad',
'negyven': 'negyvened',
'ötven': 'ötvened',
'hatvan': 'hatvanad',
'hetven': 'hetvened',
'nyolcvan': 'nyolcvanad',
'kilencven': 'kilencvened',
'száz': 'század',
'ezer': 'ezred',
'illió': 'milliomod',
'illiárd': 'milliárdod'
}
def to_cardinal(self, value, zero=ZERO):
if int(value) != value:
return self.to_cardinal_float(value)
elif value < 0:
out = self.negword + self.to_cardinal(-value)
elif value == 0:
out = zero
elif zero == '' and value == 2:
out = 'két'
elif value < 30:
out = self.cards[value]
elif value < 100:
out = self.tens_to_cardinal(value)
elif value < 1000:
out = self.hundreds_to_cardinal(value)
elif value < 10**6:
out = self.thousands_to_cardinal(value)
else:
out = self.big_number_to_cardinal(value)
return out
def tens_to_cardinal(self, value):
try:
return self.cards[value]
except KeyError:
return self.cards[value // 10 * 10] + self.to_cardinal(value % 10)
def hundreds_to_cardinal(self, value):
hundreds = value // 100
prefix = "száz"
if hundreds != 1:
prefix = self.to_cardinal(hundreds, zero="") + prefix
postfix = self.to_cardinal(value % 100, zero="")
return prefix + postfix
def thousands_to_cardinal(self, value):
thousands = value // 1000
prefix = "ezer"
if thousands != 1:
prefix = self.to_cardinal(thousands, zero="") + prefix
postfix = self.to_cardinal(value % 1000, zero="")
return prefix + ('' if value <= 2000 or not postfix else '-') + postfix
def big_number_to_cardinal(self, value):
digits = len(str(value))
digits = digits if digits % 3 != 0 else digits - 2
exp = 10 ** (digits // 3 * 3)
rest = self.to_cardinal(value % exp, '')
return (self.to_cardinal(value // exp, '') + self.cards[exp]
+ ('-' + rest if rest else ''))
def to_ordinal(self, value):
if value < 0:
return self.negword + self.to_ordinal(-value)
if value == 1:
return 'első'
elif value == 2:
return 'második'
else:
out = self.to_cardinal(value)
for card_word, ord_word in self.partial_ords.items():
if out[-len(card_word):] == card_word:
out = out[:-len(card_word)] + ord_word
break
return out + 'ik'
def to_ordinal_num(self, value):
self.verify_ordinal(value)
return str(value) + '.'
def to_year(self, val, suffix=None, longval=True):
# suffix is prefix here
prefix = ''
if val < 0 or suffix is not None:
val = abs(val)
prefix = (suffix + ' ' if suffix is not None else 'i. e. ')
return prefix + self.to_cardinal(val)
def to_currency(self, val, currency='HUF', cents=True, separator=',',
adjective=False):
return super(Num2Word_HU, self).to_currency(
val, currency, cents, separator, adjective)
def to_cardinal_float(self, value):
if abs(value) != value:
return self.negword + self.to_cardinal_float(-value)
left, right = str(value).split('.')
return (self.to_cardinal(int(left))
+ ' egész '
+ self.to_cardinal(int(right))
+ ' ' + self.partial_ords[self.cards[10 ** len(right)]])

View File

@@ -0,0 +1,204 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import print_function, unicode_literals
class Num2Word_ID():
BASE = {0: [],
1: ["satu"],
2: ["dua"],
3: ["tiga"],
4: ["empat"],
5: ["lima"],
6: ["enam"],
7: ["tujuh"],
8: ["delapan"],
9: ["sembilan"]}
TENS_TO = {3: "ribu",
6: "juta",
9: "miliar",
12: "triliun",
15: "kuadriliun",
18: "kuantiliun",
21: "sekstiliun",
24: "septiliun",
27: "oktiliun",
30: "noniliun",
33: "desiliun"}
errmsg_floatord = "Cannot treat float number as ordinal"
errmsg_negord = "Cannot treat negative number as ordinal"
errmsg_toobig = "Number is too large to convert to words (abs(%s) > %s)."
MAXVAL = 10 ** 36
def split_by_koma(self, number):
return str(number).split('.')
def split_by_3(self, number):
"""
starting here, it groups the number by three from the tail
'1234567' -> (('1',),('234',),('567',))
:param number:str
:rtype:tuple
"""
blocks = ()
length = len(number)
if length < 3:
blocks += ((number,),)
else:
len_of_first_block = length % 3
if len_of_first_block > 0:
first_block = number[0:len_of_first_block],
blocks += first_block,
for i in range(len_of_first_block, length, 3):
next_block = (number[i:i + 3],),
blocks += next_block
return blocks
def spell(self, blocks):
"""
it adds the list of spelling to the blocks
(
('1',),('034',)) -> (('1',['satu']),('234',['tiga', 'puluh', 'empat'])
)
:param blocks: tuple
:rtype: tuple
"""
word_blocks = ()
first_block = blocks[0]
if len(first_block[0]) == 1:
if first_block[0] == '0':
spelling = ['nol']
else:
spelling = self.BASE[int(first_block[0])]
elif len(first_block[0]) == 2:
spelling = self.puluh(first_block[0])
else:
spelling = (
self.ratus(first_block[0][0]) + self.puluh(first_block[0][1:3])
)
word_blocks += (first_block[0], spelling),
for block in blocks[1:]:
spelling = self.ratus(block[0][0]) + self.puluh(block[0][1:3])
block += spelling,
word_blocks += block,
return word_blocks
def ratus(self, number):
# it is used to spell
if number == '1':
return ['seratus']
elif number == '0':
return []
else:
return self.BASE[int(number)] + ['ratus']
def puluh(self, number):
# it is used to spell
if number[0] == '1':
if number[1] == '0':
return ['sepuluh']
elif number[1] == '1':
return ['sebelas']
else:
return self.BASE[int(number[1])] + ['belas']
elif number[0] == '0':
return self.BASE[int(number[1])]
else:
return (
self.BASE[int(number[0])] + ['puluh']
+ self.BASE[int(number[1])]
)
def spell_float(self, float_part):
# spell the float number
word_list = []
for n in float_part:
if n == '0':
word_list += ['nol']
continue
word_list += self.BASE[int(n)]
return ' '.join(['', 'koma'] + word_list)
def join(self, word_blocks, float_part):
"""
join the words by first join lists in the tuple
:param word_blocks: tuple
:rtype: str
"""
word_list = []
length = len(word_blocks) - 1
first_block = word_blocks[0],
start = 0
if length == 1 and first_block[0][0] == '1':
word_list += ['seribu']
start = 1
for i in range(start, length + 1, 1):
word_list += word_blocks[i][1]
if not word_blocks[i][1]:
continue
if i == length:
break
word_list += [self.TENS_TO[(length - i) * 3]]
return ' '.join(word_list) + float_part
def to_cardinal(self, number):
if number >= self.MAXVAL:
raise OverflowError(self.errmsg_toobig % (number, self.MAXVAL))
minus = ''
if number < 0:
minus = 'min '
float_word = ''
n = self.split_by_koma(abs(number))
if len(n) == 2:
float_word = self.spell_float(n[1])
return minus + self.join(self.spell(self.split_by_3(n[0])), float_word)
def to_ordinal(self, number):
self.verify_ordinal(number)
out_word = self.to_cardinal(number)
if out_word == "satu":
return "pertama"
return "ke" + out_word
def to_ordinal_num(self, number):
self.verify_ordinal(number)
return "ke-" + str(number)
def to_currency(self, value):
return self.to_cardinal(value) + " rupiah"
def to_year(self, value):
return self.to_cardinal(value)
def verify_ordinal(self, value):
if not value == int(value):
raise TypeError(self.errmsg_floatord % value)
if not abs(value) == value:
raise TypeError(self.errmsg_negord % value)

View File

@@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import division, print_function, unicode_literals
from . import lang_EU
# Genders
KK = 0 # Karlkyn (male)
KVK = 1 # Kvenkyn (female)
HK = 2 # Hvorugkyn (neuter)
GENDERS = {
"einn": ("einn", "ein", "eitt"),
"tveir": ("tveir", "tvær", "tvö"),
"þrír": ("þrír", "þrjár", "þrjú"),
"fjórir": ("fjórir", "fjórar", "fjögur"),
}
PLURALS = {
"hundrað": ("hundrað", "hundruð"),
}
class Num2Word_IS(lang_EU.Num2Word_EU):
GIGA_SUFFIX = "illjarður"
MEGA_SUFFIX = "illjón"
def setup(self):
lows = ["okt", "sept", "sext", "kvint", "kvaðr", "tr", "b", "m"]
self.high_numwords = self.gen_high_numwords([], [], lows)
self.negword = "mínus "
self.pointword = "komma"
# All words should be excluded, title case is not used in Icelandic
self.exclude_title = ["og", "komma", "mínus"]
self.mid_numwords = [(1000, "þúsund"), (100, "hundrað"),
(90, "níutíu"), (80, "áttatíu"), (70, "sjötíu"),
(60, "sextíu"), (50, "fimmtíu"), (40, "fjörutíu"),
(30, "þrjátíu")]
self.low_numwords = ["tuttugu", "nítján", "átján", "sautján",
"sextán", "fimmtán", "fjórtán", "þrettán",
"tólf", "ellefu", "tíu", "níu", "átta",
"sjö", "sex", "fimm", "fjórir", "þrír",
"tveir", "einn", "núll"]
self.ords = {"einn": "fyrsti",
"tveir": "annar",
"þrír": "þriðji",
"fjórir": "fjórði",
"fimm": "fimmti",
"sex": "sjötti",
"sjö": "sjöundi",
"átta": "áttundi",
"níu": "níundi",
"tíu": "tíundi",
"ellefu": "ellefti",
"tólf": "tólfti"}
def pluralize(self, n, noun):
form = 0 if (n % 10 == 1 and n % 100 != 11) else 1
if form == 0:
return noun
elif self.GIGA_SUFFIX in noun:
return noun.replace(self.GIGA_SUFFIX, "illjarðar")
elif self.MEGA_SUFFIX in noun:
return noun.replace(self.MEGA_SUFFIX, "illjónir")
elif noun not in PLURALS:
return noun
return PLURALS[noun][form]
def genderize(self, adj, noun):
last = adj.split()[-1]
if last not in GENDERS:
return adj
gender = KK
if "hund" in noun or "þús" in noun:
gender = HK
elif "illjarð" in noun:
gender = KK
elif "illjón" in noun:
gender = KVK
return adj.replace(last, GENDERS[last][gender])
def merge(self, lpair, rpair):
ltext, lnum = lpair
rtext, rnum = rpair
if lnum == 1 and rnum < 100:
return (rtext, rnum)
elif lnum < rnum:
rtext = self.pluralize(lnum, rtext)
ltext = self.genderize(ltext, rtext)
return ("%s %s" % (ltext, rtext), lnum * rnum)
elif lnum > rnum and rnum in self.cards:
rtext = self.pluralize(lnum, rtext)
ltext = self.genderize(ltext, rtext)
return ("%s og %s" % (ltext, rtext), lnum + rnum)
return ("%s %s" % (ltext, rtext), lnum + rnum)
def to_ordinal(self, value):
raise NotImplementedError
def to_ordinal_num(self, value):
raise NotImplementedError
def to_year(self, val, suffix=None, longval=True):
raise NotImplementedError
def to_currency(self, val, longval=True):
raise NotImplementedError

View File

@@ -0,0 +1,241 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018-2019, Filippo Costa. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import unicode_literals
from .lang_EU import Num2Word_EU
# Globals
# -------
ZERO = "zero"
CARDINAL_WORDS = [
ZERO, "uno", "due", "tre", "quattro", "cinque", "sei", "sette", "otto",
"nove", "dieci", "undici", "dodici", "tredici", "quattordici", "quindici",
"sedici", "diciassette", "diciotto", "diciannove"
]
ORDINAL_WORDS = [
ZERO, "primo", "secondo", "terzo", "quarto", "quinto", "sesto", "settimo",
"ottavo", "nono", "decimo", "undicesimo", "dodicesimo", "tredicesimo",
"quattordicesimo", "quindicesimo", "sedicesimo", "diciassettesimo",
"diciottesimo", "diciannovesimo"
]
# The script can extrapolate the missing numbers from the base forms.
STR_TENS = {2: "venti", 3: "trenta", 4: "quaranta", 6: "sessanta"}
# These prefixes are used for extremely big numbers.
EXPONENT_PREFIXES = [
ZERO, "m", "b", "tr", "quadr", "quint", "sest", "sett", "ott", "nov", "dec"
]
GENERIC_DOLLARS = ('dollaro', 'dollari')
GENERIC_CENTS = ('centesimo', 'centesimi')
CURRENCIES_UNA = ('GBP')
# Main class
# ==========
class Num2Word_IT(Num2Word_EU):
CURRENCY_FORMS = {
'EUR': (('euro', 'euro'), GENERIC_CENTS),
'USD': (GENERIC_DOLLARS, GENERIC_CENTS),
'GBP': (('sterlina', 'sterline'), ('penny', 'penny')),
'CNY': (('yuan', 'yuan'), ('fen', 'fen')),
}
MINUS_PREFIX_WORD = "meno "
FLOAT_INFIX_WORD = " virgola "
def setup(self):
Num2Word_EU.setup(self)
def __init__(self):
pass
def float_to_words(self, float_number, ordinal=False):
if ordinal:
prefix = self.to_ordinal(int(float_number))
else:
prefix = self.to_cardinal(int(float_number))
float_part = str(float_number).split('.')[1]
postfix = " ".join(
# Drops the trailing zero and comma
[self.to_cardinal(int(c)) for c in float_part]
)
return prefix + Num2Word_IT.FLOAT_INFIX_WORD + postfix
def tens_to_cardinal(self, number):
tens = number // 10
units = number % 10
if tens in STR_TENS:
prefix = STR_TENS[tens]
else:
prefix = CARDINAL_WORDS[tens][:-1] + "anta"
postfix = omitt_if_zero(CARDINAL_WORDS[units])
return phonetic_contraction(prefix + postfix)
def hundreds_to_cardinal(self, number):
hundreds = number // 100
prefix = "cento"
if hundreds != 1:
prefix = CARDINAL_WORDS[hundreds] + prefix
postfix = omitt_if_zero(self.to_cardinal(number % 100))
return phonetic_contraction(prefix + postfix)
def thousands_to_cardinal(self, number):
thousands = number // 1000
if thousands == 1:
prefix = "mille"
else:
prefix = self.to_cardinal(thousands) + "mila"
postfix = omitt_if_zero(self.to_cardinal(number % 1000))
# "mille" and "mila" don't need any phonetic contractions
return prefix + postfix
def big_number_to_cardinal(self, number):
digits = [c for c in str(number)]
length = len(digits)
if length >= 66:
raise NotImplementedError("The given number is too large.")
# This is how many digits come before the "illion" term.
# cento miliardi => 3
# dieci milioni => 2
# un miliardo => 1
predigits = length % 3 or 3
multiplier = digits[:predigits]
exponent = digits[predigits:]
# Default infix string: "milione", "biliardo", "sestilione", ecc.
infix = exponent_length_to_string(len(exponent))
if multiplier == ["1"]:
prefix = "un "
else:
prefix = self.to_cardinal(int("".join(multiplier)))
# Plural form ~~~~~~~~~~~
infix = " " + infix[:-1] + "i"
# Read as: Does the value of exponent equal 0?
if set(exponent) != set("0"):
postfix = self.to_cardinal(int("".join(exponent)))
if " e " in postfix:
infix += ", "
else:
infix += " e "
else:
postfix = ""
return prefix + infix + postfix
def to_cardinal(self, number):
if number < 0:
string = Num2Word_IT.MINUS_PREFIX_WORD + self.to_cardinal(-number)
elif isinstance(number, float):
string = self.float_to_words(number)
elif number < 20:
string = CARDINAL_WORDS[int(number)]
elif number < 100:
string = self.tens_to_cardinal(int(number))
elif number < 1000:
string = self.hundreds_to_cardinal(int(number))
elif number < 1000000:
string = self.thousands_to_cardinal(int(number))
else:
string = self.big_number_to_cardinal(number)
return accentuate(string)
def to_ordinal(self, number):
tens = number % 100
# Italian grammar is poorly defined here ¯\_(ツ)_/¯:
# centodecimo VS centodieciesimo VS centesimo decimo?
is_outside_teens = not 10 < tens < 20
if number < 0:
return Num2Word_IT.MINUS_PREFIX_WORD + self.to_ordinal(-number)
elif number % 1 != 0:
return self.float_to_words(number, ordinal=True)
elif number < 20:
return ORDINAL_WORDS[int(number)]
elif is_outside_teens and tens % 10 == 3:
# Gets rid of the accent
return self.to_cardinal(number)[:-1] + "eesimo"
elif is_outside_teens and tens % 10 == 6:
return self.to_cardinal(number) + "esimo"
else:
string = self.to_cardinal(number)[:-1]
if string[-3:] == "mil":
string += "l"
return string + "esimo"
def to_currency(self, val, currency='EUR', cents=True, separator=' e',
adjective=False):
result = super(Num2Word_IT, self).to_currency(
val, currency=currency, cents=cents, separator=separator,
adjective=adjective)
# Handle exception. In italian language is "un euro",
# "un dollaro" etc. (not "uno euro", "uno dollaro").
# There is an exception, some currencies need "una":
# e.g. "una sterlina"
if currency in CURRENCIES_UNA:
list_result = result.split(" ")
if list_result[0] == "uno":
list_result[0] = list_result[0].replace("uno", "una")
result = " ".join(list_result)
result = result.replace("uno", "un")
return result
# Utils
# =====
def phonetic_contraction(string):
return (string
.replace("oo", "o") # ex. "centootto"
.replace("ao", "o") # ex. "settantaotto"
.replace("io", "o") # ex. "ventiotto"
.replace("au", "u") # ex. "trentauno"
.replace("iu", "u") # ex. "ventiunesimo"
)
def exponent_length_to_string(exponent_length):
# We always assume `exponent` to be a multiple of 3. If it's not true, then
# Num2Word_IT.big_number_to_cardinal did something wrong.
prefix = EXPONENT_PREFIXES[exponent_length // 6]
if exponent_length % 6 == 0:
return prefix + "ilione"
else:
return prefix + "iliardo"
def accentuate(string):
# This is inefficient: it may do several rewritings when deleting
# half-sentence accents. However, it is the easiest method and speed is
# not crucial (duh), so...
return " ".join(
# Deletes half-sentence accents and accentuates the last "tre"
[w.replace("tré", "tre")[:-3] + "tré"
# We shouldn't accentuate a single "tre": is has to be a composite
# word. ~~~~~~~~~~
if w[-3:] == "tre" and len(w) > 3
# Deletes half-sentence accents anyway
# ~~~~~~~~~~~~~~~~~~~~~~
else w.replace("tré", "tre")
for w in string.split()
])
def omitt_if_zero(number_to_string):
return "" if number_to_string == ZERO else number_to_string

View File

@@ -0,0 +1,590 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA
from __future__ import division, print_function, unicode_literals
from .base import Num2Word_Base
from .compat import strtype, to_s
from .currency import parse_currency_parts, prefix_currency
def select_text(text, reading=False, prefer=None):
"""Select the correct text from the Japanese number, reading and
alternatives"""
# select kanji number or kana reading
if reading:
text = text[1]
else:
text = text[0]
# select the preferred one or the first one from multiple alternatives
if not isinstance(text, strtype):
common = set(text) & set(prefer or set())
if len(common) == 1:
text = common.pop()
else:
text = text[0]
return text
def rendaku_merge_pairs(lpair, rpair):
"""Merge lpair < rpair while applying semi-irregular rendaku rules"""
ltext, lnum = lpair
rtext, rnum = rpair
if lnum > rnum:
raise ValueError
if rpair == ("ひゃく", 100):
if lpair == ("さん", 3):
rtext = "びゃく"
elif lpair == ("ろく", 6):
ltext = "ろっ"
rtext = "ぴゃく"
elif lpair == ("はち", 8):
ltext = "はっ"
rtext = "ぴゃく"
elif rpair == ("せん", 1000):
if lpair == ("さん", 3):
rtext = "ぜん"
elif lpair == ("はち", 8):
ltext = "はっ"
elif rpair == ("ちょう", 10**12):
if lpair == ("いち", 1):
ltext = "いっ"
elif lpair == ("はち", 8):
ltext = "はっ"
elif lpair == ("じゅう", 10):
ltext = "じゅっ"
elif rpair == ("けい", 10**16):
if lpair == ("いち", 1):
ltext = "いっ"
elif lpair == ("ろく", 6):
ltext = "ろっ"
elif lpair == ("はち", 8):
ltext = "はっ"
elif lpair == ("じゅう", 10):
ltext = "じゅっ"
elif lpair == ("ひゃく", 100):
ltext = "ひゃっ"
return ("%s%s" % (ltext, rtext), lnum * rnum)
# Source: https://www.sljfaq.org/afaq/era-list.html
# if there are multiple eras for the same year, use the last one
ERA_START = [
(645, ("大化", "たいか")),
(650, ("白雉", "はくち")),
(686, ("朱鳥", "しゅちょう")),
(701, ("大宝", "たいほう")),
(704, ("慶雲", "けいうん")),
(708, ("和銅", "わどう")),
(715, ("霊亀", "れいき")),
(717, ("養老", "ようろう")),
(724, ("神亀", "じんき")),
(729, ("天平", "てんぴょう")),
(749, ("天平感宝", "てんぴょうかんぽう")),
(749, ("天平勝宝", "てんぴょうしょうほう")),
(757, ("天平宝字", "てんぴょうじょうじ")),
(765, ("天平神護", "てんぴょうじんご")),
(767, ("神護景雲", "じんごけいうん")),
(770, ("宝亀", "ほうき")),
(781, ("天応", "てんおう")),
(782, ("延暦", "えんりゃく")),
(806, ("大同", "だいどう")),
(810, ("弘仁", "こうにん")),
(823, ("天長", "てんちょう")),
(834, ("承和", "じょうわ")),
(848, ("嘉祥", "かしょう")),
(851, ("仁寿", "にんじゅ")),
(855, ("斉衡", "さいこう")),
(857, ("天安", "てんあん")),
(859, ("貞観", "じょうがん")),
(877, ("元慶", "がんぎょう")),
(885, ("仁和", "にんな")),
(889, ("寛平", "かんぴょう")),
(898, ("昌泰", "しょうたい")),
(901, ("延喜", "えんぎ")),
(923, ("延長", "えんちょう")),
(931, ("承平", "じょうへい")),
(938, ("天慶", "てんぎょう")),
(947, ("天暦", "てんりゃく")),
(957, ("天徳", "てんとく")),
(961, ("応和", "おうわ")),
(964, ("康保", "こうほう")),
(968, ("安和", "あんな")),
(970, ("天禄", "てんろく")),
(974, ("天延", "てんえん")),
(976, ("貞元", "じょうげん")),
(979, ("天元", "てんげん")),
(983, ("永観", "えいかん")),
(985, ("寛和", "かんな")),
(987, ("永延", "えいえん")),
(989, ("永祚", "えいそ")),
(990, ("正暦", "しょうりゃく")),
(995, ("長徳", "ちょうとく")),
(999, ("長保", "ちょうほう")),
(1004, ("寛弘", "かんこう")),
(1013, ("長和", "ちょうわ")),
(1017, ("寛仁", "かんにん")),
(1021, ("治安", "じあん")),
(1024, ("万寿", "まんじゅ")),
(1028, ("長元", "ちょうげん")),
(1037, ("長暦", "ちょうりゃく")),
(1040, ("長久", "ちょうきゅう")),
(1045, ("寛徳", "かんとく")),
(1046, ("永承", "えいしょう")),
(1053, ("天喜", "てんぎ")),
(1058, ("康平", "こうへい")),
(1065, ("治暦", "じりゃく")),
(1069, ("延久", "えんきゅう")),
(1074, ("承保", "じょうほう")),
(1078, ("承暦", "じょうりゃく")),
(1081, ("永保", "えいほう")),
(1084, ("応徳", "おうとく")),
(1087, ("寛治", "かんじ")),
(1095, ("嘉保", "かほう")),
(1097, ("永長", "えいちょう")),
(1098, ("承徳", "じょうとく")),
(1099, ("康和", "こうわ")),
(1104, ("長治", "ちょうじ")),
(1106, ("嘉承", "かじょう")),
(1108, ("天仁", "てんにん")),
(1110, ("天永", "てんねい")),
(1113, ("永久", "えいきゅう")),
(1118, ("元永", "げんえい")),
(1120, ("保安", "ほうあん")),
(1124, ("天治", "てんじ")),
(1126, ("大治", "だいじ")),
(1131, ("天承", "てんしょう")),
(1132, ("長承", "ちょうしょう")),
(1135, ("保延", "ほうえん")),
(1141, ("永治", "えいじ")),
(1142, ("康治", "こうじ")),
(1144, ("天養", "てんよう")),
(1145, ("久安", "きゅうあん")),
(1151, ("仁平", "にんぺい")),
(1154, ("久寿", "きゅうじゅ")),
(1156, ("保元", "ほうげん")),
(1159, ("平治", "へいじ")),
(1160, ("永暦", "えいりゃく")),
(1161, ("応保", "おうほう")),
(1163, ("長寛", "ちょうかん")),
(1165, ("永万", "えいまん")),
(1166, ("仁安", "にんあん")),
(1169, ("嘉応", "かおう")),
(1171, ("承安", "しょうあん")),
(1175, ("安元", "あんげん")),
(1177, ("治承", "じしょう")),
(1181, ("養和", "ようわ")),
(1182, ("寿永", "じゅえい")),
(1184, ("元暦", "げんりゃく")),
(1185, ("文治", "ぶんじ")),
(1190, ("建久", "けんきゅう")),
(1199, ("正治", "しょうじ")),
(1201, ("建仁", "けんにん")),
(1204, ("元久", "げんきゅう")),
(1206, ("建永", "けんえい")),
(1207, ("承元", "じょうげん")),
(1211, ("建暦", "けんりゃく")),
(1214, ("建保", "けんぽう")),
(1219, ("承久", "じょうきゅう")),
(1222, ("貞応", "じょうおう")),
(1225, ("元仁", "げんにん")),
(1225, ("嘉禄", "かろく")),
(1228, ("安貞", "あんてい")),
(1229, ("寛喜", "かんき")),
(1232, ("貞永", "じょうえい")),
(1233, ("天福", "てんぷく")),
(1235, ("文暦", "ぶんりゃく")),
(1235, ("嘉禎", "かてい")),
(1239, ("暦仁", "りゃくにん")),
(1239, ("延応", "えんおう")),
(1240, ("仁治", "にんじ")),
(1243, ("寛元", "かんげん")),
(1247, ("宝治", "ほうじ")),
(1249, ("建長", "けんちょう")),
(1256, ("康元", "こうげん")),
(1257, ("正嘉", "しょうか")),
(1259, ("正元", "しょうげん")),
(1260, ("文応", "ぶんおう")),
(1261, ("弘長", "こうちょう")),
(1264, ("文永", "ぶんえい")),
(1275, ("健治", "けんじ")),
(1278, ("弘安", "こうあん")),
(1288, ("正応", "しょうおう")),
(1293, ("永仁", "えいにん")),
(1299, ("正安", "しょうあん")),
(1303, ("乾元", "けんげん")),
(1303, ("嘉元", "かげん")),
(1307, ("徳治", "とくじ")),
(1308, ("延慶", "えんきょう")),
(1311, ("応長", "おうちょう")),
(1312, ("正和", "しょうわ")),
(1317, ("文保", "ぶんぽう")),
(1319, ("元応", "げんおう")),
(1321, ("元亨", "げんこう")),
(1325, ("正中", "しょうちゅ")),
(1326, ("嘉暦", "かりゃく")),
(1329, ("元徳", "げんとく")),
(1331, ("元弘", "げんこう")),
(1332, ("正慶", "しょうけい")),
(1334, ("建武", "けんむ")),
(1336, ("延元", "えいげん")),
(1338, ("暦応", "りゃくおう")),
(1340, ("興国", "こうこく")),
(1342, ("康永", "こうえい")),
(1345, ("貞和", "じょうわ")),
(1347, ("正平", "しょうへい")),
(1350, ("観応", "かんおう")),
(1352, ("文和", "ぶんな")),
(1356, ("延文", "えんぶん")),
(1361, ("康安", "こうあん")),
(1362, ("貞治", "じょうじ")),
(1368, ("応安", "おうあん")),
(1370, ("建徳", "けんとく")),
(1372, ("文中", "ぶんちゅう")),
(1375, ("永和", "えいわ")),
(1375, ("天授", "てんじゅ")),
(1379, ("康暦", "こうりゃく")),
(1381, ("永徳", "えいとく")),
(1381, ("弘和", "こうわ")),
(1384, ("至徳", "しとく")),
(1384, ("元中", "げんちゅう")),
(1387, ("嘉慶", "かけい")),
(1389, ("康応", "こうおう")),
(1390, ("明徳", "めいとく")),
(1394, ("応永", "おうえい")),
(1428, ("正長", "しょうちょう")),
(1429, ("永享", "えいきょう")),
(1441, ("嘉吉", "かきつ")),
(1444, ("文安", "ぶんあん")),
(1449, ("宝徳", "ほうとく")),
(1452, ("享徳", "きょうとく")),
(1455, ("康正", "こうしょう")),
(1457, ("長禄", "ちょうろく")),
(1461, ("寛正", "かんしょう")),
(1466, ("文正", "ぶんしょう")),
(1467, ("応仁", "おうにん")),
(1469, ("文明", "ぶんめい")),
(1487, ("長享", "ちょうきょう")),
(1489, ("延徳", "えんとく")),
(1492, ("明応", "めいおう")),
(1501, ("文亀", "ぶんき")),
(1504, ("永正", "えいしょう")),
(1521, ("大永", "だいえい")),
(1528, ("享禄", "きょうろく")),
(1532, ("天文", "てんぶん")),
(1555, ("弘治", "こうじ")),
(1558, ("永禄", "えいろく")),
(1570, ("元亀", "げんき")),
(1573, ("天正", "てんしょう")),
(1593, ("文禄", "ぶんろく")),
(1596, ("慶長", "けいちょう")),
(1615, ("元和", "げんな")),
(1624, ("寛永", "かんえい")),
(1645, ("正保", "しょうほう")),
(1648, ("慶安", "けいあん")),
(1652, ("承応", "じょうおう")),
(1655, ("明暦", "めいれき")),
(1658, ("万治", "まんじ")),
(1661, ("寛文", "かんぶん")),
(1673, ("延宝", "えんぽう")),
(1681, ("天和", "てんな")),
(1684, ("貞享", "じょうきょう")),
(1688, ("元禄", "げんろく")),
(1704, ("宝永", "ほうえい")),
(1711, ("正徳", "しょうとく")),
(1716, ("享保", "きょうほう")),
(1736, ("元文", "げんぶん")),
(1741, ("寛保", "かんぽう")),
(1744, ("延享", "えんきょう")),
(1748, ("寛延", "かんえん")),
(1751, ("宝暦", "ほうれき")),
(1764, ("明和", "めいわ")),
(1773, ("安永", "あんえい")),
(1781, ("天明", "てんめい")),
(1801, ("寛政", "かんせい")),
(1802, ("享和", "きょうわ")),
(1804, ("文化", "ぶんか")),
(1818, ("文政", "ぶんせい")),
(1831, ("天保", "てんぽう")),
(1845, ("弘化", "こうか")),
(1848, ("嘉永", "かえい")),
(1855, ("安政", "あんせい")),
(1860, ("万延", "まんえい")),
(1861, ("文久", "ぶんきゅう")),
(1864, ("元治", "げんじ")),
(1865, ("慶応", "けいおう")),
(1868, ("明治", "めいじ")),
(1912, ("大正", "たいしょう")),
(1926, ("昭和", "しょうわ")),
(1989, ("平成", "へいせい")),
(2019, ("令和", "れいわ")),
]
class Num2Word_JA(Num2Word_Base):
CURRENCY_FORMS = {
'JPY': (('', 'えん'), ()),
}
def set_high_numwords(self, high):
max = 4 * len(high)
for word, n in zip(high, range(max, 0, -4)):
self.cards[10 ** n] = word
def setup(self):
self.negword = "マイナス"
self.pointword = ("", "てん")
self.exclude_title = ["", "マイナス"]
self.high_numwords = [
("", "まん"), # 10**4 man
("", "おく"), # 10**8 oku
("", "ちょう"), # 10**12 chō
("", "けい"), # 10**16 kei
("", "がい"), # 10**20 gai
("", ""), # 10**24 shi
("", "じょう"), # 10**28 jō
("", "こう"), # 10**32 kō
("", "かん"), # 10**36 kan
("", "せい"), # 10**40 sei
("", "さい"), # 10**44 sai
("", "ごく"), # 10**48 goku
]
self.high_numwords.reverse()
self.mid_numwords = [
(1000, ("", "せん")),
(100, ("", "ひゃく")),
]
self.low_numwords = [
("", "じゅう"), # 10 jū
("", "きゅう"), # 9 kyū
("", "はち"), # 8 hachi
("", ("なな", "しち")), # 7 nana, shichi
("", "ろく"), # 6 roku
("", ""), # 5 go
("", ("よん", "")), # 4 yon, shi
("", "さん"), # 3 san
("", ""), # 2 ni
("", "いち"), # 1 ichi
# both are alternatives, 零 doesn't map to ゼロ or to れい
(("", ""), ("ゼロ", "れい")), # 0 ZERO, rei
]
def merge(self, lpair, rpair):
ltext, lnum = lpair
rtext, rnum = rpair
fmt = "%s%s"
# ignore lpair if lnum is 1 and rnum is less than 10000
if lnum == 1 and rnum < 10000:
return rpair
# rnum is added to lnum
elif lnum > rnum:
return (fmt % (ltext, rtext), lnum + rnum)
# rnum is multiplied by lnum
elif lnum < rnum:
return rendaku_merge_pairs(lpair, rpair)
def _ordinal_suffix(self, reading, counter):
if reading:
if counter == "":
return "ばんめ"
else:
raise NotImplementedError(
"Reading not implemented for %s" % counter)
else:
return counter + ""
def to_ordinal(self, value, reading=False, prefer=None, counter=""):
self.verify_ordinal(value)
base = self.to_cardinal(value, reading=reading, prefer=prefer)
return "%s%s" % (base, self._ordinal_suffix(reading, counter))
def to_ordinal_num(self, value, reading=False, counter=""):
return "%s%s" % (value, self._ordinal_suffix(reading, counter))
def to_year(self, val, suffix=None, longval=True, reading=False,
prefer=None, era=True):
year = val
# Gregorian calendar
if not era:
prefix = ""
if year < 0:
year = abs(year)
prefix = "きげんぜん" if reading else "紀元前"
year_words = self.to_cardinal(year, reading=reading, prefer=prefer)
if reading and year % 10 == 9:
year_words = year_words[:-3] + ""
return "%s%s%s" % (prefix, year_words, "ねん" if reading else "")
# Era calendar (default)
min_year = ERA_START[0][0]
last_era_idx = len(ERA_START) - 1
if year < min_year:
raise ValueError(
"Can't convert years less than %s to era" % min_year)
first = 0
last = last_era_idx
era_idx = None
while era_idx is None:
mid = (first + last) // 2
if mid == last_era_idx or (ERA_START[mid][0] <= year and
ERA_START[mid + 1][0] > year):
era_idx = mid
# if an era lasting less than a year is preferred, choose it
if prefer:
i = mid - 1
while i >= 0 and ERA_START[i][0] == year:
# match kanji or hiragana
if set(ERA_START[i][1]) & set(prefer):
era_idx = i
break
i -= 1
# ends up at the last index where year >= ERA_START[mid][0]
if year < ERA_START[mid][0]:
last = mid - 1
else:
first = mid + 1
era = ERA_START[era_idx]
era_name = era[1][0]
era_year = year - era[0] + 1
fmt = "%s%s"
if reading == "arabic":
era_year_words = str(era_year)
elif reading:
era_name = era[1][1]
era_year_words = (self.to_cardinal(era_year, reading=True,
prefer=prefer)
if era_year != 1 else "がん")
if era_year % 10 == 9:
era_year_words = era_year_words[:-3] + ""
fmt = "%s%sねん"
else:
era_year_words = (self.to_cardinal(era_year, reading=False,
prefer=prefer)
if era_year != 1 else "")
return fmt % (era_name, era_year_words)
def to_currency(self, val, currency="JPY", cents=False, separator="",
adjective=False, reading=False, prefer=None):
left, right, is_negative = parse_currency_parts(
val, is_int_with_cents=cents)
try:
cr1, cr2 = self.CURRENCY_FORMS[currency]
if (cents or abs(val) != left) and not cr2:
raise ValueError('Decimals not supported for "%s"' % currency)
except KeyError:
raise NotImplementedError(
'Currency code "%s" not implemented for "%s"' %
(currency, self.__class__.__name__))
if adjective and currency in self.CURRENCY_ADJECTIVES:
cr1 = prefix_currency(self.CURRENCY_ADJECTIVES[currency], cr1)
minus_str = self.negword if is_negative else ""
return '%s%s%s%s%s' % (
minus_str,
self.to_cardinal(left, reading=reading, prefer=prefer),
cr1[1] if reading else cr1[0],
self.to_cardinal(right, reading=reading, prefer=prefer)
if cr2 else '',
(cr2[1] if reading else cr2[0]) if cr2 else '',
)
def splitnum(self, value, reading, prefer):
for elem in self.cards:
if elem > value:
continue
out = []
if value == 0:
div, mod = 1, 0
else:
div, mod = divmod(value, elem)
if div == 1:
out.append((select_text(self.cards[1], reading, prefer), 1))
else:
if div == value: # The system tallies, eg Roman Numerals
return [(
div * select_text(self.cards[elem], reading, prefer),
div * elem)]
out.append(self.splitnum(div, reading, prefer))
out.append((select_text(self.cards[elem], reading, prefer), elem))
if mod:
out.append(self.splitnum(mod, reading, prefer))
return out
def to_cardinal(self, value, reading=False, prefer=None):
try:
assert int(value) == value
except (ValueError, TypeError, AssertionError):
return self.to_cardinal_float(value, reading=reading,
prefer=prefer)
out = ""
if value < 0:
value = abs(value)
out = self.negword
if value >= self.MAXVAL:
raise OverflowError(self.errmsg_toobig % (value, self.MAXVAL))
val = self.splitnum(value, reading, prefer)
words, _ = self.clean(val)
return self.title(out + words)
def to_cardinal_float(self, value, reading=False, prefer=None):
prefer = prefer or ["れい"]
try:
float(value) == value
except (ValueError, TypeError, AssertionError):
raise TypeError(self.errmsg_nonnum % value)
pre, post = self.float2tuple(float(value))
post = str(post)
post = '0' * (self.precision - len(post)) + post
out = [self.to_cardinal(pre, reading=reading, prefer=prefer)]
if self.precision:
out.append(self.title(self.pointword[1 if reading else 0]))
for i in range(self.precision):
curr = int(post[i])
out.append(to_s(
self.to_cardinal(curr, reading=reading, prefer=prefer)))
return "".join(out)

Some files were not shown because too many files have changed in this diff Show More