From 77df034a706d9cecbf8582d59197d99de70ca331 Mon Sep 17 00:00:00 2001 From: Sergio Zholudov Date: Fri, 8 Sep 2017 17:37:38 +0300 Subject: [PATCH 01/11] Ukrainian language --- num2words/lang_UK.py | 316 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 num2words/lang_UK.py diff --git a/num2words/lang_UK.py b/num2words/lang_UK.py new file mode 100644 index 0000000..14d04b6 --- /dev/null +++ b/num2words/lang_UK.py @@ -0,0 +1,316 @@ +# -*- encoding: 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 +u""" +>>> from textwrap import fill + +>>> ' '.join([str(i) for i in splitby3('1')]) +u'1' +>>> ' '.join([str(i) for i in splitby3('1123')]) +u'1 123' +>>> ' '.join([str(i) for i in splitby3('1234567890')]) +u'1 234 567 890' + +>>> print(' '.join([n2w(i) for i in range(10)])) +нуль один два три чотири п'ять шiсть сiмь вiсiм дев'ять + +>>> print(fill(' '.join([n2w(i+10) for i in range(10)]))) +десять одинадцять дванадцять тринадцять чотирнадцять п'ятнадцять +шiстнадцять сiмнадцять вiсiмнадцять дев'ятнадцять + +>>> print(fill(' '.join([n2w(i*10) for i in range(10)]))) +нуль десять двадцять тридцять сорок п'ятдесят шiстдесят сiмдесят +вiсiмдесят дев'яносто + +>>> print(n2w(100)) +сто +>>> print(n2w(101)) +сто один +>>> print(n2w(110)) +сто десять +>>> print(n2w(115)) +сто п'ятнадцять +>>> print(n2w(123)) +сто двадцять три +>>> print(n2w(1000)) +тисяча +>>> print(n2w(1001)) +тисяча один +>>> print(n2w(2012)) +двi тисячi дванадцять + +>>> print(n2w(12519.85)) +дванадцять тисяч п'ятсот дев'ятнадцять кома вiсiмдесят п'ять + +>>> print(fill(n2w(1234567890))) +мiльярд двiстi тридцать чотири мiльйона п'ятсот шiстдесят сiмь тисяч +вiсiмсот дев'яносто + +>>> print(fill(n2w(215461407892039002157189883901676))) +двiстi п'ятнадцять нонiльйонiв чотириста шiстдесят один октильйон +чотириста сiм септильйонiв вiсiмсот дев'яносто два секстильйони +тридцять дев'ять квiнтильйонiв два квадрильйони сто п'ятдесят сiм +трильйонiв сто вiсiмдесят дев'ять мiльярдiв вiсiмсот вiсiмдесят три +мiльйона дев'ятсот одна тисяча шiстсот сiмдесят шiсть + +>>> print(fill(n2w(719094234693663034822824384220291))) +сiмсот дев'ятнадцять нонiльйонiв дев'яносто чотири октильйони двiстi +тридцять чотири септильйони шiстсот дев'яносто три секстильйони +шiстсот шiстдесят три квiнтильйони тридцять чотири квадрильйони +вiсiмсот двадцять два трильйони вiсiмсот двадцять чотири мiльярди +триста вiсiмдесят чотири мiльйона двiстi двадцять тисяч двiстi +дев'яносто один + +>>> print(to_currency(1.0, 'EUR')) +один євро, нуль центiв + +>>> print(to_currency(1.0, 'UAH')) +одна гривня, нуль копiйок + +>>> print(to_currency(1234.56, 'EUR')) +тисяча двiстi тридцять чотири євро, п'ятдесят шiсть центiв + +>>> print(to_currency(1234.56, 'UAH')) +тисяча двiстi тридцять чотири гривнi, п'ятдесят шiсть копiйок + +>>> print(to_currency(10111, 'EUR', seperator=u' та')) +сто один євро та одинадцять центiв + +>>> print(to_currency(10121, 'UAH', seperator=u' та')) +сто одна гривня та двадцять одна копiйка + +>>> print(to_currency(10122, 'UAH', seperator=u' та')) +сто одна гривня та двадцять одна копiйка + +>>> print(to_currency(10121, 'EUR', seperator=u' та')) +сто один євро та двадцять один цент + +>>> print(to_currency(-1251985, cents = False)) +мiнус дванадцять тисяч п'ятьсот дев'ятнадцять євро, 85 центiв +""" +from __future__ import unicode_literals + +ZERO = (u'нуль',) + +ONES_FEMININE = { + 1: (u'одна',), + 2: (u'двi',), + 3: (u'три',), + 4: (u'чотири',), + 5: (u'п\'ять',), + 6: (u'шiсть',), + 7: (u'сiмь',), + 8: (u'вiсiмь',), + 9: (u'дев\'ять',), +} + +ONES = { + 1: (u'один',), + 2: (u'два',), + 3: (u'три',), + 4: (u'чотири',), + 5: (u'п\'ять',), + 6: (u'шiсть',), + 7: (u'сiм',), + 8: (u'вiсiм',), + 9: (u'дев\'ять',), +} + +TENS = { + 0: (u'десять',), + 1: (u'одинадцять',), + 2: (u'дванадцять',), + 3: (u'тринадцять',), + 4: (u'чотирнадцять',), + 5: (u'п\'ятнадцять',), + 6: (u'шiстнадцять',), + 7: (u'сiмнадцять',), + 8: (u'вiсiмнадцять',), + 9: (u'дев\'ятнадцять',), +} + +TWENTIES = { + 2: (u'двадцять',), + 3: (u'тридцять',), + 4: (u'сорок',), + 5: (u'п\'ятдесят',), + 6: (u'шiстдесят',), + 7: (u'сiмдесят',), + 8: (u'вiсiмдесят',), + 9: (u'дев\'яносто',), +} + +HUNDREDS = { + 1: (u'сто',), + 2: (u'двiстi',), + 3: (u'триста',), + 4: (u'чотириста',), + 5: (u'п\'ятсот',), + 6: (u'шiстсот',), + 7: (u'сiмсот',), + 8: (u'вiсiмсот',), + 9: (u'дев\'ятсот',), +} + +THOUSANDS = { + 1: (u'тисяча', u'тисячi', u'тисяч'), # 10^3 + 2: (u'мiльйон', u'мiльйони', u'мiльйонiв'), # 10^6 + 3: (u'мiльярд', u'мiльярди', u'мiльярдiв'), # 10^9 + 4: (u'трильйон', u'трильйони', u'трильйонiв'), # 10^12 + 5: (u'квадрильйон', u'квадрильйони', u'квадрильйонiв'), # 10^15 + 6: (u'квiнтильйон', u'квiнтильйони', u'квiнтильйонiв'), # 10^18 + 7: (u'секстильйон', u'секстильйони', u'секстильйонiв'), # 10^21 + 8: (u'септильйон', u'септильйони', u'септильйонiв'), # 10^24 + 9: (u'октильйон', u'октильйони', u'октильйонiв'), #10^27 + 10: (u'нонiльйон', u'нонiльйони', u'нонiльйонiв'), # 10^30 +} + +CURRENCIES = { + 'UAH': ( + (u'гривня', u'гривнi', u'гривень'), (u'копiйка', u'копiйки', u'копiйок') + ), + 'EUR': ( + (u'евро', u'евро', u'евро'), (u'цент', u'центи', u'центiв') + ), +} + + +def splitby3(n): + length = len(n) + if length > 3: + start = length % 3 + if start > 0: + yield int(n[:start]) + for i in range(start, length, 3): + yield int(n[i:i+3]) + else: + yield int(n) + + +def get_digits(n): + return [int(x) for x in reversed(list(('%03d' % n)[-3:]))] + + +def pluralize(n, forms): + #form = 0 if n==1 else 1 if (n % 10 > 1 and n % 10 < 5 and (n % 100 < 10 or n % 100 > 20)) else 2 + if (n % 100 < 10 or n % 100 > 20): + if n % 10 == 1: + form = 0 + elif (n % 10 > 1 and n % 10 < 5): + form = 1 + else: + form = 2 + else: + form = 2 + + return forms[form] + + +def int2word(n, feminine=True): + if n < 0: + return ' '.join([u'мiнус', int2word(abs(n))]) + + if n == 0: + return ZERO[0] + + words = [] + chunks = list(splitby3(str(n))) + i = len(chunks) + for x in chunks: + i -= 1 + 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): + elif n1 > 0: + ones = ONES_FEMININE if i == 1 or feminine and i == 0 else ONES + words.append(ones[n1][0]) + + + if i > 0 and ((n1+n2+n3) > 0): + words.append(pluralize(x, THOUSANDS[i])) + + return ' '.join(words) + + +def n2w(n): + n = str(n).replace(',', '.') + if '.' in n: + left, right = n.split('.') + return u'%s кома %s' % (int2word(int(left)), int2word(int(right))) + else: + return int2word(int(n)) + + +def to_currency(n, currency='EUR', cents=True, seperator=','): + if type(n) == int: + if n < 0: + minus = True + else: + minus = False + + n = abs(n) + left = n / 100 + right = n % 100 + else: + n = str(n).replace(',', '.') + if '.' in n: + left, right = n.split('.') + else: + left, right = n, 0 + left, right = int(left), int(right) + minus = False + cr1, cr2 = CURRENCIES[currency] + + if minus: + minus_str = "мiнус " + else: + minus_str = "" + + if cents: + cents_feminine = currency == 'UAH' + cents_str = int2word(right, cents_feminine) + else: + cents_str = "%02d" % right + + return u'%s%s %s%s %s %s' % ( + minus_str, + int2word(left), + pluralize(left, cr1), + seperator, + cents_str, + pluralize(right, cr2) + ) + + +class Num2Word_UK(object): + def to_cardinal(self, number): + return n2w(number) + + def to_ordinal(self, number): + raise NotImplementedError() + + +if __name__ == '__main__': + import doctest + doctest.testmod() From 8b12fa7dc352d29e65e06e4ddfe658f4471f741f Mon Sep 17 00:00:00 2001 From: Sergio Zholudov Date: Fri, 8 Sep 2017 17:44:27 +0300 Subject: [PATCH 02/11] Updated __init__.py, added Ukrainian --- num2words/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/num2words/__init__.py b/num2words/__init__.py index 3fc0c37..aa956a3 100644 --- a/num2words/__init__.py +++ b/num2words/__init__.py @@ -39,6 +39,7 @@ from . import lang_ES_VE from . import lang_ES_CO from . import lang_VN from . import lang_TR +from . import lang_UK CONVERTER_CLASSES = { @@ -64,7 +65,8 @@ CONVERTER_CLASSES = { 'he': lang_HE.Num2Word_HE(), 'it': lang_IT.Num2Word_IT(), 'vi_VN': lang_VN.Num2Word_VN(), - 'tr': lang_TR.Num2Word_TR() + 'tr': lang_TR.Num2Word_TR(), + 'uk': lang_UK.Num2Word_UK() } def num2words(number, ordinal=False, lang='en'): From 1d580f1be7974889077eb245a01ee3f3ac2f4ede Mon Sep 17 00:00:00 2001 From: Sergio Zholudov Date: Fri, 8 Sep 2017 17:47:42 +0300 Subject: [PATCH 03/11] Updated README.rst, added Ukrainian --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 6eb5588..ee61dd6 100644 --- a/README.rst +++ b/README.rst @@ -70,6 +70,7 @@ cardinal one. * ``ru`` (Russian) * ``tr`` (Turkish) * ``vn`` (Vietnamese) +* ``uk`` (Ukrainian) You can supply values like ``fr_FR``, the code will be correctly interpreted. If you supply an unsupported language, ``NotImplementedError`` is raised. From 90b85a366cdc827949a5b3ac3a3fd4ef3e818fc5 Mon Sep 17 00:00:00 2001 From: Sergio Zholudov Date: Mon, 11 Sep 2017 12:43:30 +0300 Subject: [PATCH 04/11] Tests Ukrainian language --- tests/test_uk.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/test_uk.py diff --git a/tests/test_uk.py b/tests/test_uk.py new file mode 100644 index 0000000..e5523fb --- /dev/null +++ b/tests/test_uk.py @@ -0,0 +1,28 @@ +# 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 unittest import TestCase + +from num2words import num2words + +class Num2WordsENTest(TestCase): + def test_and_join_199(self): + self.assertEqual(num2words(187), "сто вісімдесят сім") + + def test_cardinal_for_float_number(self): + self.assertEqual(num2words(12.40), "дванадцять кома сорок") + self.assertEqual(num2words(17.31), "сімнадцять кома тридцять один") + self.assertEqual(num2words(14.13), "чотирнадцять кома тринадцять") + self.assertEqual(num2words(12.31), "дванадцять кома тридцять один") From 279b63ed50da15f0d6ba6027d023c0d8373adda2 Mon Sep 17 00:00:00 2001 From: Sergio Zholudov Date: Mon, 11 Sep 2017 13:01:59 +0300 Subject: [PATCH 05/11] update test_uk --- tests/test_uk.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_uk.py b/tests/test_uk.py index e5523fb..0dff2aa 100644 --- a/tests/test_uk.py +++ b/tests/test_uk.py @@ -19,10 +19,10 @@ from num2words import num2words class Num2WordsENTest(TestCase): def test_and_join_199(self): - self.assertEqual(num2words(187), "сто вісімдесят сім") + self.assertEqual(num2words(187,lang='uk'), "сто вісімдесят сім") def test_cardinal_for_float_number(self): - self.assertEqual(num2words(12.40), "дванадцять кома сорок") - self.assertEqual(num2words(17.31), "сімнадцять кома тридцять один") - self.assertEqual(num2words(14.13), "чотирнадцять кома тринадцять") - self.assertEqual(num2words(12.31), "дванадцять кома тридцять один") + self.assertEqual(num2words(12.40,lang='uk'), "дванадцять кома сорок") + self.assertEqual(num2words(17.31,lang='uk'), "сімнадцять кома тридцять один") + self.assertEqual(num2words(14.13,lang='uk'), "чотирнадцять кома тринадцять") + self.assertEqual(num2words(12.31,lang='uk'), "дванадцять кома тридцять один") From b93c71659752437b44787bbbae8a0d7b682a5793 Mon Sep 17 00:00:00 2001 From: Sergio Zholudov Date: Mon, 11 Sep 2017 13:38:00 +0300 Subject: [PATCH 06/11] update test_uk --- tests/test_uk.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_uk.py b/tests/test_uk.py index 0dff2aa..62a65fb 100644 --- a/tests/test_uk.py +++ b/tests/test_uk.py @@ -19,10 +19,10 @@ from num2words import num2words class Num2WordsENTest(TestCase): def test_and_join_199(self): - self.assertEqual(num2words(187,lang='uk'), "сто вісімдесят сім") + self.assertEqual(num2words(187,lang='uk'), "сто вiсiмдесят сiм") def test_cardinal_for_float_number(self): - self.assertEqual(num2words(12.40,lang='uk'), "дванадцять кома сорок") - self.assertEqual(num2words(17.31,lang='uk'), "сімнадцять кома тридцять один") + self.assertEqual(num2words(12.40,lang='uk'), "дванадцять кома чотири") + self.assertEqual(num2words(17.31,lang='uk'), "сiмнадцять кома тридцять один") self.assertEqual(num2words(14.13,lang='uk'), "чотирнадцять кома тринадцять") self.assertEqual(num2words(12.31,lang='uk'), "дванадцять кома тридцять один") From 88eb77c92710f7d26f1c89698f75c0d0d0adccab Mon Sep 17 00:00:00 2001 From: Sergio Zholudov Date: Mon, 11 Sep 2017 13:40:56 +0300 Subject: [PATCH 07/11] update test_uk.py --- tests/test_uk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_uk.py b/tests/test_uk.py index 62a65fb..b45d9b3 100644 --- a/tests/test_uk.py +++ b/tests/test_uk.py @@ -23,6 +23,6 @@ class Num2WordsENTest(TestCase): def test_cardinal_for_float_number(self): self.assertEqual(num2words(12.40,lang='uk'), "дванадцять кома чотири") - self.assertEqual(num2words(17.31,lang='uk'), "сiмнадцять кома тридцять один") + self.assertEqual(num2words(17.31,lang='uk'), "сiмнадцять кома тридцять одна") self.assertEqual(num2words(14.13,lang='uk'), "чотирнадцять кома тринадцять") - self.assertEqual(num2words(12.31,lang='uk'), "дванадцять кома тридцять один") + self.assertEqual(num2words(12.31,lang='uk'), "дванадцять кома тридцять одна") From 71419ffa0e4648e33c4856fc4ac525b87b4adf80 Mon Sep 17 00:00:00 2001 From: Sergio Zholudov Date: Mon, 11 Sep 2017 13:43:39 +0300 Subject: [PATCH 08/11] update lang_uk.py --- num2words/lang_UK.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/num2words/lang_UK.py b/num2words/lang_UK.py index 14d04b6..06d7c79 100644 --- a/num2words/lang_UK.py +++ b/num2words/lang_UK.py @@ -112,8 +112,8 @@ ONES_FEMININE = { 4: (u'чотири',), 5: (u'п\'ять',), 6: (u'шiсть',), - 7: (u'сiмь',), - 8: (u'вiсiмь',), + 7: (u'сiм',), + 8: (u'вiсiм',), 9: (u'дев\'ять',), } From 865a311437f8a320b8f6f21399f0c38d7f00c61c Mon Sep 17 00:00:00 2001 From: Sergio Zholudov Date: Mon, 11 Sep 2017 13:48:18 +0300 Subject: [PATCH 09/11] Ukrainian language test update --- tests/test_uk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_uk.py b/tests/test_uk.py index b45d9b3..fc5b669 100644 --- a/tests/test_uk.py +++ b/tests/test_uk.py @@ -17,7 +17,7 @@ from unittest import TestCase from num2words import num2words -class Num2WordsENTest(TestCase): +class Num2WordsUKTest(TestCase): def test_and_join_199(self): self.assertEqual(num2words(187,lang='uk'), "сто вiсiмдесят сiм") From 23b1961fe53602aeaa6dac28483cd189892e1131 Mon Sep 17 00:00:00 2001 From: Sergio Zholudov Date: Mon, 11 Sep 2017 14:32:16 +0300 Subject: [PATCH 10/11] updated test_uk --- tests/test_uk.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_uk.py b/tests/test_uk.py index fc5b669..5ddc7a4 100644 --- a/tests/test_uk.py +++ b/tests/test_uk.py @@ -1,3 +1,4 @@ +# -*- encoding: utf-8 -*- # Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved. # This library is free software; you can redistribute it and/or From 28f3d3bcff7077c7e45b37ef3211a61d02a2d72a Mon Sep 17 00:00:00 2001 From: Sergio Zholudov Date: Mon, 11 Sep 2017 15:05:11 +0300 Subject: [PATCH 11/11] update uk_test (utf-8 issue resolved) --- tests/test_uk.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_uk.py b/tests/test_uk.py index 5ddc7a4..26760ae 100644 --- a/tests/test_uk.py +++ b/tests/test_uk.py @@ -20,10 +20,10 @@ from num2words import num2words class Num2WordsUKTest(TestCase): def test_and_join_199(self): - self.assertEqual(num2words(187,lang='uk'), "сто вiсiмдесят сiм") + self.assertEqual(num2words(187,lang='uk'), u"сто вiсiмдесят сiм") def test_cardinal_for_float_number(self): - self.assertEqual(num2words(12.40,lang='uk'), "дванадцять кома чотири") - self.assertEqual(num2words(17.31,lang='uk'), "сiмнадцять кома тридцять одна") - self.assertEqual(num2words(14.13,lang='uk'), "чотирнадцять кома тринадцять") - self.assertEqual(num2words(12.31,lang='uk'), "дванадцять кома тридцять одна") + self.assertEqual(num2words(12.40,lang='uk'), u"дванадцять кома чотири") + self.assertEqual(num2words(17.31,lang='uk'), u"сiмнадцять кома тридцять одна") + self.assertEqual(num2words(14.13,lang='uk'), u"чотирнадцять кома тринадцять") + self.assertEqual(num2words(12.31,lang='uk'), u"дванадцять кома тридцять одна")