diff --git a/README.rst b/README.rst index 606b1ca..813e87f 100644 --- a/README.rst +++ b/README.rst @@ -4,9 +4,11 @@ num2words - Convert numbers to words in multiple languages .. image:: https://travis-ci.org/savoirfairelinux/num2words.svg?branch=master :target: https://travis-ci.org/savoirfairelinux/num2words -``num2words`` is a library that converts numbers like ``42`` to words like ``forty-two``. It -supports multiple languages (English, French, Spanish, German and Lithuanian) and can even generate -ordinal numbers like ``forty-second`` (altough this last feature is a bit buggy at the moment). +``num2words`` is a library that converts numbers like ``42`` to words like +``forty-two``. It supports multiple languages (English, Arabic, Danish, French, +German, Hebrew, Italian, Latvian, Norwegian, Polish, Portuguese, Russian, +Spanish and Lithuanian) and can even generate ordinal numbers like +``forty-second`` (altough this last feature is a bit buggy at the moment). The project is hosted on https://github.com/savoirfairelinux/num2words @@ -40,29 +42,32 @@ There's only one function to use:: Besides the numerical argument, there's two optional arguments. -**ordinal:** A boolean flag indicating to return an ordinal number instead of a cardinal one. +**ordinal:** A boolean flag indicating to return an ordinal number instead of a +cardinal one. **lang:** The language in which to convert the number. Supported values are: -* ``en`` (English, default) * ``ar`` (Arabic) -* ``fr`` (French) * ``de`` (German) -* ``es`` (Spanish) -* ``lt`` (Lithuanian) -* ``lv`` (Latvian) +* ``dk`` (Danish) +* ``en`` (English, default) * ``en_GB`` (British English) * ``en_IN`` (Indian English) -* ``no`` (Norwegian) -* ``pl`` (Polish) -* ``ru`` (Russian) -* ``dk`` (Danish) -* ``pt_BR`` (Brazilian Portuguese) +* ``es`` (Spanish) +* ``fr`` (French) +* ``fr_CH`` (Swiss French) +* ``fr_DZ`` (Argelia French) * ``he`` (Hebrew) * ``it`` (Italian) +* ``lt`` (Lithuanian) +* ``lv`` (Latvian) +* ``no`` (Norwegian) +* ``pl`` (Polish) +* ``pt_BR`` (Brazilian Portuguese) +* ``ru`` (Russian) -You can supply values like ``fr_FR``, the code will be -correctly interpreted. If you supply an unsupported language, ``NotImplementedError`` is raised. +You can supply values like ``fr_FR``, the code will be correctly interpreted. If +you supply an unsupported language, ``NotImplementedError`` is raised. Therefore, if you want to call ``num2words`` with a fallback, you can do:: try: @@ -73,12 +78,12 @@ Therefore, if you want to call ``num2words`` with a fallback, you can do:: History ------- -``num2words`` is based on an old library, ``pynum2word`` created by Taro Ogawa in 2003. -Unfortunately, the library stopped being maintained and the author can't be reached. There was -another developer, Marius Grigaitis, who in 2011 added Lithuanian support, but didn't take over -maintenance of the project. +``num2words`` is based on an old library, ``pynum2word`` created by Taro Ogawa +in 2003. Unfortunately, the library stopped being maintained and the author +can't be reached. There was another developer, Marius Grigaitis, who in 2011 +added Lithuanian support, but didn't take over maintenance of the project. -I am thus basing myself on Marius Grigaitis' improvements and re-publishing ``pynum2word`` as -``num2words``. +I am thus basing myself on Marius Grigaitis' improvements and re-publishing +``pynum2word`` as ``num2words``. Virgil Dupras, Savoir-faire Linux diff --git a/num2words/__init__.py b/num2words/__init__.py index 6589569..e8feb46 100644 --- a/num2words/__init__.py +++ b/num2words/__init__.py @@ -22,6 +22,7 @@ from . import lang_EN_GB from . import lang_EN_IN from . import lang_FR from . import lang_FR_CH +from . import lang_FR_DZ from . import lang_DE from . import lang_ES from . import lang_LT @@ -47,6 +48,7 @@ CONVERTER_CLASSES = { 'en_IN': lang_EN_IN.Num2Word_EN_IN(), 'fr': lang_FR.Num2Word_FR(), 'fr_CH': lang_FR_CH.Num2Word_FR_CH(), + 'fr_DZ': lang_FR_DZ.Num2Word_FR_DZ(), 'de': lang_DE.Num2Word_DE(), 'es': lang_ES.Num2Word_ES(), 'es_CO': lang_ES_CO.Num2Word_ES_CO(), diff --git a/num2words/base.py b/num2words/base.py index 272d5bb..e1e4383 100644 --- a/num2words/base.py +++ b/num2words/base.py @@ -111,13 +111,7 @@ class Num2Word_Base(object): return self.title(out + words) - def to_cardinal_float(self, value): - try: - float(value) == value - except (ValueError, TypeError, AssertionError): - raise TypeError(self.errmsg_nonnum % value) - - value = float(value) + def float2tuple(self, value): pre = int(value) post = abs(value - pre) * 10**self.precision if abs(round(post) - post) < 0.01: @@ -127,6 +121,18 @@ class Num2Word_Base(object): 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): + raise TypeError(self.errmsg_nonnum % value) + + pre, post = self.float2tuple(float(value)) + post = str(post) post = '0' * (self.precision - len(post)) + post @@ -213,12 +219,17 @@ class Num2Word_Base(object): #//CHECK: generalise? Any others like pounds/shillings/pence? def to_splitnum(self, val, hightxt="", lowtxt="", jointxt="", - divisor=100, longval=True, cents = True): + divisor=100, longval=True, cents=True): out = [] - try: - high, low = val - except TypeError: - high, low = divmod(val, divisor) + + 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)) @@ -230,6 +241,7 @@ class Num2Word_Base(object): out.append(self.title(jointxt)) elif hightxt: out.append(hightxt) + if low: if cents: out.append(self.to_cardinal(low)) @@ -237,6 +249,7 @@ class Num2Word_Base(object): out.append("%02d" % low) if lowtxt and longval: out.append(self.title(self.inflect(low, lowtxt))) + return " ".join(out) diff --git a/num2words/lang_FR.py b/num2words/lang_FR.py index 1209eab..b151f12 100644 --- a/num2words/lang_FR.py +++ b/num2words/lang_FR.py @@ -21,6 +21,8 @@ from .lang_EU import Num2Word_EU class Num2Word_FR(Num2Word_EU): 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." @@ -49,15 +51,14 @@ class Num2Word_FR(Num2Word_EU): else: if (not (cnum - 80)%100 or not cnum%100) and ctext[-1] == "s": ctext = ctext[:-1] - if (cnum<1000 and nnum != 1000 and ntext[-1] != "s" - and not nnum%100): + 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) - elif nnum > cnum: + if nnum > cnum: return ("%s %s"%(ctext, ntext), cnum * nnum) return ("%s %s"%(ctext, ntext), cnum + nnum) @@ -83,15 +84,15 @@ class Num2Word_FR(Num2Word_EU): def to_ordinal_num(self, value): self.verify_ordinal(value) out = str(value) - out += {"1" : "er" }.get(out[-1], "me") + out += {"1" : "er"}.get(out[-1], "me") return out def to_currency(self, val, longval=True, old=False): hightxt = "Euro/s" if old: - hightxt="franc/s" + hightxt = "franc/s" return self.to_splitnum(val, hightxt=hightxt, lowtxt="centime/s", - jointxt="et",longval=longval) + jointxt="et", longval=longval) n2w = Num2Word_FR() to_card = n2w.to_cardinal @@ -99,10 +100,10 @@ to_ord = n2w.to_ordinal to_ordnum = n2w.to_ordinal_num def main(): - for val in [ 1, 11, 12, 21, 31, 33, 71, 80, 81, 91, 99, 100, 101, 102, 155, - 180, 300, 308, 832, 1000, 1001, 1061, 1100, 1500, 1701, 3000, - 8280, 8291, 150000, 500000, 1000000, 2000000, 2000001, - -21212121211221211111, -2.121212, -1.0000100]: + for val in [1, 11, 12, 21, 31, 33, 71, 80, 81, 91, 99, 100, 101, 102, 155, + 180, 300, 308, 832, 1000, 1001, 1061, 1100, 1500, 1701, 3000, + 8280, 8291, 150000, 500000, 1000000, 2000000, 2000001, + -21212121211221211111, -2.121212, -1.0000100]: n2w.test(val) n2w.test(1325325436067876801768700107601001012212132143210473207540327057320957032975032975093275093275093270957329057320975093272950730) diff --git a/num2words/lang_FR_CH.py b/num2words/lang_FR_CH.py index b6d40a0..032c86f 100644 --- a/num2words/lang_FR_CH.py +++ b/num2words/lang_FR_CH.py @@ -16,27 +16,17 @@ # MA 02110-1301 USA from __future__ import unicode_literals, print_function -from .lang_EU import Num2Word_EU +from .lang_FR import Num2Word_FR -class Num2Word_FR_CH(Num2Word_EU): + +class Num2Word_FR_CH(Num2Word_FR): def 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." - self.exclude_title = ["et", "virgule", "moins"] + Num2Word_FR.setup(self) + self.mid_numwords = [(1000, "mille"), (100, "cent"), (90, "nonante"), - (80, "huitante"), (70, "septante"), (60, "soixante"), + (80, "huitante"), (70, "septante"), (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): @@ -45,59 +35,29 @@ class Num2Word_FR_CH(Num2Word_EU): if cnum == 1: if nnum < 1000000: return next + if cnum < 1000 and nnum != 1000 and ntext[-1] != "s" and not nnum % 100: - ntext += "s" + 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) - elif nnum > cnum: + 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 += {"1" : "er" }.get(out[-1], "me") - return out - - def to_currency(self, val, longval=True, old=False): - hightxt = "Euro/s" - if old: - hightxt="franc/s" - return self.to_splitnum(val, hightxt=hightxt, lowtxt="centime/s", - jointxt="et",longval=longval) - n2w = Num2Word_FR_CH() to_card = n2w.to_cardinal to_ord = n2w.to_ordinal to_ordnum = n2w.to_ordinal_num def main(): - for val in [ 1, 11, 12, 21, 31, 33, 71, 80, 81, 91, 99, 100, 101, 102, 155, - 180, 300, 308, 832, 1000, 1001, 1061, 1100, 1500, 1701, 3000, - 8280, 8291, 150000, 500000, 1000000, 2000000, 2000001, - -21212121211221211111, -2.121212, -1.0000100]: + for val in [1, 11, 12, 21, 31, 33, 71, 80, 81, 91, 99, 100, 101, 102, 155, + 180, 300, 308, 832, 1000, 1001, 1061, 1100, 1500, 1701, 3000, + 8280, 8291, 150000, 500000, 1000000, 2000000, 2000001, + -21212121211221211111, -2.121212, -1.0000100]: n2w.test(val) n2w.test(1325325436067876801768700107601001012212132143210473207540327057320957032975032975093275093275093270957329057320975093272950730) diff --git a/num2words/lang_FR_DZ.py b/num2words/lang_FR_DZ.py new file mode 100644 index 0000000..5fddbf8 --- /dev/null +++ b/num2words/lang_FR_DZ.py @@ -0,0 +1,48 @@ +# 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): + def to_currency(self, val, longval=True, cents=True, jointxt="virgule"): + return self.to_splitnum(val, hightxt="dinard/s", lowtxt="centime/s", + jointxt=jointxt, longval=longval, cents=cents) + + +n2w = Num2Word_FR_DZ() +to_card = n2w.to_cardinal +to_ord = n2w.to_ordinal +to_ordnum = n2w.to_ordinal_num +to_year = n2w.to_year +to_currency = n2w.to_currency + +def main(): + for val in [1, 11, 12, 21, 31, 33, 71, 80, 81, 91, 99, 100, 101, 102, 155, + 180, 300, 308, 832, 1000, 1001, 1061, 1100, 1500, 1701, 3000, + 8280, 8291, 150000, 500000, 1000000, 2000000, 2000001, + -21212121211221211111, -2.121212, -1.0000100]: + n2w.test(val) + + n2w.test(1325325436067876801768700107601001012212132143210473207540327057320957032975032975093275093275093270957329057320975093272950730) + for val in [1, 120, 1000, 1120, 1800, 1976, 2000, 2010, 2099, 2171]: + print(val, "is", n2w.to_currency(val)) + print(val, "is", n2w.to_year(val)) + + +if __name__ == "__main__": + main() diff --git a/tests/test_fr_dz.py b/tests/test_fr_dz.py new file mode 100644 index 0000000..2463740 --- /dev/null +++ b/tests/test_fr_dz.py @@ -0,0 +1,27 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2015, 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 unittest import TestCase + +from num2words import num2words +from num2words.lang_FR_DZ import to_currency + +class Num2WordsPLTest(TestCase): + def test_currency(self): + self.assertEqual(to_currency(1234.12), "mille deux cent trente-quatre dinards virgule douze centimes") + self.assertEqual(to_currency(45689.89), "quarante-cinq mille six cent quatre-vingt-neuf dinards virgule quatre-vingt-neuf centimes") diff --git a/tests/test_pl.py b/tests/test_pl.py index b70b9a6..e21a852 100644 --- a/tests/test_pl.py +++ b/tests/test_pl.py @@ -22,28 +22,28 @@ from num2words import num2words from num2words.lang_PL import to_currency class Num2WordsPLTest(TestCase): - def test_cardinal(self): - self.assertEqual(num2words(100, lang='pl'), "sto") - self.assertEqual(num2words(101, lang='pl'), "sto jeden") - self.assertEqual(num2words(110, lang='pl'), "sto dziesięć") - self.assertEqual(num2words(115, lang='pl'), "sto piętnaście") - self.assertEqual(num2words(123, lang='pl'), "sto dwadzieścia trzy") - self.assertEqual(num2words(1000, lang='pl'), "tysiąc") - self.assertEqual(num2words(1001, lang='pl'), "tysiąc jeden") - self.assertEqual(num2words(2012, lang='pl'), "dwa tysiące dwanaście") - self.assertEqual(num2words(12519.85, lang='pl'), "dwanaście tysięcy pięćset dziewiętnaście przecinek osiemdziesiąt pięć") - self.assertEqual(num2words(123.50, lang='pl'), "sto dwadzieścia trzy przecinek pięć") - self.assertEqual(num2words(1234567890, lang='pl'), "miliard dwieście trzydzieści cztery miliony pięćset sześćdziesiąt siedem tysięcy osiemset dziewięćdzisiąt") - self.assertEqual(num2words(215461407892039002157189883901676, lang='pl'), "dwieście piętnaście kwintylionów czterysta sześćdziesiąt jeden kwadryliardów czterysta siedem kwadrylionów osiemset dziewięćdzisiąt dwa tryliardy trzydzieści dziewięć trylionów dwa biliardy sto pięćdziesiąt siedem bilionów sto osiemdziesiąt dziewięć miliardów osiemset osiemdziesiąt trzy miliony dziewęćset jeden tysięcy sześćset siedemdziesiąt sześć") - self.assertEqual(num2words(719094234693663034822824384220291, lang='pl'), "siedemset dziewiętnaście kwintylionów dziewięćdzisiąt cztery kwadryliardy dwieście trzydzieści cztery kwadryliony sześćset dziewięćdzisiąt trzy tryliardy sześćset sześćdziesiąt trzy tryliony trzydzieści cztery biliardy osiemset dwadzieścia dwa biliony osiemset dwadzieścia cztery miliardy trzysta osiemdziesiąt cztery miliony dwieście dwadzieścia tysięcy dwieście dziewięćdzisiąt jeden") + def test_cardinal(self): + self.assertEqual(num2words(100, lang='pl'), "sto") + self.assertEqual(num2words(101, lang='pl'), "sto jeden") + self.assertEqual(num2words(110, lang='pl'), "sto dziesięć") + self.assertEqual(num2words(115, lang='pl'), "sto piętnaście") + self.assertEqual(num2words(123, lang='pl'), "sto dwadzieścia trzy") + self.assertEqual(num2words(1000, lang='pl'), "tysiąc") + self.assertEqual(num2words(1001, lang='pl'), "tysiąc jeden") + self.assertEqual(num2words(2012, lang='pl'), "dwa tysiące dwanaście") + self.assertEqual(num2words(12519.85, lang='pl'), "dwanaście tysięcy pięćset dziewiętnaście przecinek osiemdziesiąt pięć") + self.assertEqual(num2words(123.50, lang='pl'), "sto dwadzieścia trzy przecinek pięć") + self.assertEqual(num2words(1234567890, lang='pl'), "miliard dwieście trzydzieści cztery miliony pięćset sześćdziesiąt siedem tysięcy osiemset dziewięćdzisiąt") + self.assertEqual(num2words(215461407892039002157189883901676, lang='pl'), "dwieście piętnaście kwintylionów czterysta sześćdziesiąt jeden kwadryliardów czterysta siedem kwadrylionów osiemset dziewięćdzisiąt dwa tryliardy trzydzieści dziewięć trylionów dwa biliardy sto pięćdziesiąt siedem bilionów sto osiemdziesiąt dziewięć miliardów osiemset osiemdziesiąt trzy miliony dziewęćset jeden tysięcy sześćset siedemdziesiąt sześć") + self.assertEqual(num2words(719094234693663034822824384220291, lang='pl'), "siedemset dziewiętnaście kwintylionów dziewięćdzisiąt cztery kwadryliardy dwieście trzydzieści cztery kwadryliony sześćset dziewięćdzisiąt trzy tryliardy sześćset sześćdziesiąt trzy tryliony trzydzieści cztery biliardy osiemset dwadzieścia dwa biliony osiemset dwadzieścia cztery miliardy trzysta osiemdziesiąt cztery miliony dwieście dwadzieścia tysięcy dwieście dziewięćdzisiąt jeden") - def test_currency(self): - self.assertEqual(to_currency(1.0, 'EUR'), "jeden euro, zero centów") - self.assertEqual(to_currency(1.0, 'PLN'), "jeden złoty, zero groszy") - self.assertEqual(to_currency(1234.56, 'EUR'), "tysiąc dwieście trzydzieści cztery euro, pięćdziesiąt sześć centów") - self.assertEqual(to_currency(1234.56, 'PLN'), "tysiąc dwieście trzydzieści cztery złote, pięćdziesiąt sześć groszy") - self.assertEqual(to_currency(10111, 'EUR', seperator=' i'), "sto jeden euro i jedenaście centów") - self.assertEqual(to_currency(10121, 'PLN', seperator=' i'), "sto jeden złotych i dwadzieścia jeden groszy") - self.assertEqual(to_currency(-1251985, cents = False), "minus dwanaście tysięcy pięćset dziewiętnaście euro, 85 centów") - self.assertEqual(to_currency(123.50, 'PLN', seperator=' i'), "sto dwadzieścia trzy złote i pięćdziesiąt groszy") - self.assertEqual(to_currency(1950, cents = False), "dziewiętnaście euro, 50 centów") + def test_currency(self): + self.assertEqual(to_currency(1.0, 'EUR'), "jeden euro, zero centów") + self.assertEqual(to_currency(1.0, 'PLN'), "jeden złoty, zero groszy") + self.assertEqual(to_currency(1234.56, 'EUR'), "tysiąc dwieście trzydzieści cztery euro, pięćdziesiąt sześć centów") + self.assertEqual(to_currency(1234.56, 'PLN'), "tysiąc dwieście trzydzieści cztery złote, pięćdziesiąt sześć groszy") + self.assertEqual(to_currency(10111, 'EUR', seperator=' i'), "sto jeden euro i jedenaście centów") + self.assertEqual(to_currency(10121, 'PLN', seperator=' i'), "sto jeden złotych i dwadzieścia jeden groszy") + self.assertEqual(to_currency(-1251985, cents = False), "minus dwanaście tysięcy pięćset dziewiętnaście euro, 85 centów") + self.assertEqual(to_currency(123.50, 'PLN', seperator=' i'), "sto dwadzieścia trzy złote i pięćdziesiąt groszy") + self.assertEqual(to_currency(1950, cents = False), "dziewiętnaście euro, 50 centów")