Issue-1411 - Leave all nice_number formatting to language
parent
9b5f4dd09c
commit
c45d228d91
|
@ -16,7 +16,10 @@
|
||||||
from mycroft.util.lang.format_en import *
|
from mycroft.util.lang.format_en import *
|
||||||
from mycroft.util.lang.format_pt import *
|
from mycroft.util.lang.format_pt import *
|
||||||
from mycroft.util.lang.format_it import *
|
from mycroft.util.lang.format_it import *
|
||||||
from mycroft.util.lang.format_fr import *
|
|
||||||
|
from mycroft.util.lang.format_fr import nice_number_fr
|
||||||
|
from mycroft.util.lang.format_fr import nice_time_fr
|
||||||
|
from mycroft.util.lang.format_fr import pronounce_number_fr
|
||||||
|
|
||||||
|
|
||||||
def nice_number(number, lang="en-us", speech=True, denominators=None):
|
def nice_number(number, lang="en-us", speech=True, denominators=None):
|
||||||
|
@ -32,29 +35,16 @@ def nice_number(number, lang="en-us", speech=True, denominators=None):
|
||||||
Returns:
|
Returns:
|
||||||
(str): The formatted string.
|
(str): The formatted string.
|
||||||
"""
|
"""
|
||||||
result = _convert_to_mixed_fraction(number, denominators)
|
|
||||||
if not result:
|
|
||||||
# Give up, just represent as a 3 decimal number
|
|
||||||
return str(round(number, 3))
|
|
||||||
|
|
||||||
if not speech:
|
|
||||||
whole, num, den = result
|
|
||||||
if num == 0:
|
|
||||||
# TODO: Number grouping? E.g. "1,000,000"
|
|
||||||
return str(whole)
|
|
||||||
else:
|
|
||||||
return '{} {}/{}'.format(whole, num, den)
|
|
||||||
|
|
||||||
# Convert to spoken representation in appropriate language
|
# Convert to spoken representation in appropriate language
|
||||||
lang_lower = str(lang).lower()
|
lang_lower = str(lang).lower()
|
||||||
if lang_lower.startswith("en"):
|
if lang_lower.startswith("en"):
|
||||||
return nice_number_en(result)
|
return nice_number_en(number, speech, denominators)
|
||||||
elif lang_lower.startswith("pt"):
|
elif lang_lower.startswith("pt"):
|
||||||
return nice_number_pt(result)
|
return nice_number_pt(number, speech, denominators)
|
||||||
elif lang_lower.startswith("it"):
|
elif lang_lower.startswith("it"):
|
||||||
return nice_number_it(result)
|
return nice_number_it(number, speech, denominators)
|
||||||
elif lang_lower.startswith("fr"):
|
elif lang_lower.startswith("fr"):
|
||||||
return nice_number_fr(result)
|
return nice_number_fr(number, speech, denominators)
|
||||||
|
|
||||||
# Default to the raw number for unsupported languages,
|
# Default to the raw number for unsupported languages,
|
||||||
# hopefully the STT engine will pronounce understandably.
|
# hopefully the STT engine will pronounce understandably.
|
||||||
|
@ -111,35 +101,3 @@ def pronounce_number(number, lang="en-us", places=2):
|
||||||
|
|
||||||
# Default to just returning the numeric value
|
# Default to just returning the numeric value
|
||||||
return str(number)
|
return str(number)
|
||||||
|
|
||||||
|
|
||||||
def _convert_to_mixed_fraction(number, denominators):
|
|
||||||
"""
|
|
||||||
Convert floats to components of a mixed fraction representation
|
|
||||||
|
|
||||||
Returns the closest fractional representation using the
|
|
||||||
provided denominators. For example, 4.500002 would become
|
|
||||||
the whole number 4, the numerator 1 and the denominator 2
|
|
||||||
|
|
||||||
Args:
|
|
||||||
number (float): number for convert
|
|
||||||
denominators (iter of ints): denominators to use, default [1 .. 20]
|
|
||||||
Returns:
|
|
||||||
whole, numerator, denominator (int): Integers of the mixed fraction
|
|
||||||
"""
|
|
||||||
int_number = int(number)
|
|
||||||
if int_number == number:
|
|
||||||
return int_number, 0, 1 # whole number, no fraction
|
|
||||||
|
|
||||||
frac_number = abs(number - int_number)
|
|
||||||
if not denominators:
|
|
||||||
denominators = range(1, 21)
|
|
||||||
|
|
||||||
for denominator in denominators:
|
|
||||||
numerator = abs(frac_number) * denominator
|
|
||||||
if (abs(numerator - round(numerator)) < 0.01): # 0.01 accuracy
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return int_number, int(round(numerator)), denominator
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright 2017 Mycroft AI Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
def convert_to_mixed_fraction(number, denominators):
|
||||||
|
"""
|
||||||
|
Convert floats to components of a mixed fraction representation
|
||||||
|
|
||||||
|
Returns the closest fractional representation using the
|
||||||
|
provided denominators. For example, 4.500002 would become
|
||||||
|
the whole number 4, the numerator 1 and the denominator 2
|
||||||
|
|
||||||
|
Args:
|
||||||
|
number (float): number for convert
|
||||||
|
denominators (iter of ints): denominators to use, default [1 .. 20]
|
||||||
|
Returns:
|
||||||
|
whole, numerator, denominator (int): Integers of the mixed fraction
|
||||||
|
"""
|
||||||
|
int_number = int(number)
|
||||||
|
if int_number == number:
|
||||||
|
return int_number, 0, 1 # whole number, no fraction
|
||||||
|
|
||||||
|
frac_number = abs(number - int_number)
|
||||||
|
if not denominators:
|
||||||
|
denominators = range(1, 21)
|
||||||
|
|
||||||
|
for denominator in denominators:
|
||||||
|
numerator = abs(frac_number) * denominator
|
||||||
|
if (abs(numerator - round(numerator)) < 0.01): # 0.01 accuracy
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return int_number, int(round(numerator)), denominator
|
|
@ -15,6 +15,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from mycroft.util.lang.format_common import convert_to_mixed_fraction
|
||||||
|
|
||||||
NUM_STRING_EN = {
|
NUM_STRING_EN = {
|
||||||
0: 'zero',
|
0: 'zero',
|
||||||
|
@ -71,18 +72,34 @@ FRACTION_STRING_EN = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def nice_number_en(mixed):
|
def nice_number_en(number, speech, denominators):
|
||||||
"""
|
"""
|
||||||
Helper for for nice_number
|
Helper for for nice_number
|
||||||
|
|
||||||
Convert (1 1 3) to spoken value like "1 and 1 third"
|
Convert (1 1 3) to spoken value like "1 and 1 third"
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mixed (int,int,int): the mixed number; whole, numerator, denominator
|
number (int or float): the float to format
|
||||||
Return:
|
speech (bool): format for speech (True) or display (False)
|
||||||
(str): spoken version of the number
|
denominators (iter of ints): denominators to use, default [1 .. 20]
|
||||||
|
Returns:
|
||||||
|
(str): The formatted string.
|
||||||
"""
|
"""
|
||||||
whole, num, den = mixed
|
|
||||||
|
result = convert_to_mixed_fraction(number, denominators)
|
||||||
|
if not result:
|
||||||
|
# Give up, just represent as a 3 decimal number
|
||||||
|
return str(round(number, 3))
|
||||||
|
|
||||||
|
whole, num, den = result
|
||||||
|
|
||||||
|
if not speech:
|
||||||
|
if num == 0:
|
||||||
|
# TODO: Number grouping? E.g. "1,000,000"
|
||||||
|
return str(whole)
|
||||||
|
else:
|
||||||
|
return '{} {}/{}'.format(whole, num, den)
|
||||||
|
|
||||||
if num == 0:
|
if num == 0:
|
||||||
return str(whole)
|
return str(whole)
|
||||||
den_str = FRACTION_STRING_EN[den]
|
den_str = FRACTION_STRING_EN[den]
|
||||||
|
|
|
@ -16,10 +16,10 @@
|
||||||
#
|
#
|
||||||
""" Format functions for french (fr)
|
""" Format functions for french (fr)
|
||||||
|
|
||||||
Todo:
|
|
||||||
* nice_number should leave number formatting to nice_number_fr
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from mycroft.util.lang.format_common import convert_to_mixed_fraction
|
||||||
|
|
||||||
NUM_STRING_FR = {
|
NUM_STRING_FR = {
|
||||||
0: 'zéro',
|
0: 'zéro',
|
||||||
1: 'un',
|
1: 'un',
|
||||||
|
@ -71,48 +71,73 @@ FRACTION_STRING_FR = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def nice_number_fr(result):
|
def nice_number_fr(number, speech, denominators):
|
||||||
"""
|
"""
|
||||||
Helper for nice_number
|
Helper for nice_number
|
||||||
|
|
||||||
Convert (1 1/3) to spoken value like "1 et 1 tiers"
|
Convert (1 1/3) to spoken value like "1 et 1 tiers"
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mixed (int,int,int): the mixed number; whole, numerator, denominator
|
number (int or float): the float to format
|
||||||
Return:
|
speech (bool): format for speech (True) or display (False)
|
||||||
(str): spoken version of the number
|
denominators (iter of ints): denominators to use, default [1 .. 20]
|
||||||
|
Returns:
|
||||||
|
(str): The formatted string.
|
||||||
"""
|
"""
|
||||||
|
strNumber = ""
|
||||||
|
whole = 0
|
||||||
|
num = 0
|
||||||
|
den = 0
|
||||||
|
|
||||||
|
result = convert_to_mixed_fraction(number, denominators)
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
# Give up, just represent as a 3 decimal number
|
||||||
|
whole = round(number, 3)
|
||||||
|
else:
|
||||||
whole, num, den = result
|
whole, num, den = result
|
||||||
|
|
||||||
|
if not speech:
|
||||||
if num == 0:
|
if num == 0:
|
||||||
# if the number is an integer, nothing to do
|
strNumber = '{:,}'.format(whole)
|
||||||
return str(whole)
|
strNumber = strNumber.replace(",", " ")
|
||||||
|
strNumber = strNumber.replace(".", ",")
|
||||||
|
return strNumber
|
||||||
|
else:
|
||||||
|
return '{} {}/{}'.format(whole, num, den)
|
||||||
|
else:
|
||||||
|
if num == 0:
|
||||||
|
# if the number is not a fraction, nothing to do
|
||||||
|
strNumber = str(whole)
|
||||||
|
strNumber = strNumber.replace(".", ",")
|
||||||
|
return strNumber
|
||||||
den_str = FRACTION_STRING_FR[den]
|
den_str = FRACTION_STRING_FR[den]
|
||||||
# if it is not an integer
|
# if it is not an integer
|
||||||
if whole == 0:
|
if whole == 0:
|
||||||
# if there is no whole number
|
# if there is no whole number
|
||||||
if num == 1:
|
if num == 1:
|
||||||
# if numerator is 1, return "un demi", for example
|
# if numerator is 1, return "un demi", for example
|
||||||
return_string = 'un {}'.format(den_str)
|
strNumber = 'un {}'.format(den_str)
|
||||||
else:
|
else:
|
||||||
# else return "quatre tiers", for example
|
# else return "quatre tiers", for example
|
||||||
return_string = '{} {}'.format(num, den_str)
|
strNumber = '{} {}'.format(num, den_str)
|
||||||
elif num == 1:
|
elif num == 1:
|
||||||
# if there is a whole number and numerator is 1
|
# if there is a whole number and numerator is 1
|
||||||
if den == 2:
|
if den == 2:
|
||||||
# if denominator is 2, return "1 et demi", for example
|
# if denominator is 2, return "1 et demi", for example
|
||||||
return_string = '{} et {}'.format(whole, den_str)
|
strNumber = '{} et {}'.format(whole, den_str)
|
||||||
else:
|
else:
|
||||||
# else return "1 et 1 tiers", for example
|
# else return "1 et 1 tiers", for example
|
||||||
return_string = '{} et 1 {}'.format(whole, den_str)
|
strNumber = '{} et 1 {}'.format(whole, den_str)
|
||||||
else:
|
else:
|
||||||
# else return "2 et 3 quart", for example
|
# else return "2 et 3 quart", for example
|
||||||
return_string = '{} et {} {}'.format(whole, num, den_str)
|
strNumber = '{} et {} {}'.format(whole, num, den_str)
|
||||||
if num > 1 and den != 3:
|
if num > 1 and den != 3:
|
||||||
# if the numerator is greater than 1 and the denominator
|
# if the numerator is greater than 1 and the denominator
|
||||||
# is not 3 ("tiers"), add an s for plural
|
# is not 3 ("tiers"), add an s for plural
|
||||||
return_string += 's'
|
strNumber += 's'
|
||||||
|
|
||||||
return return_string
|
return strNumber
|
||||||
|
|
||||||
|
|
||||||
def pronounce_number_fr(num, places=2):
|
def pronounce_number_fr(num, places=2):
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from mycroft.util.lang.format_common import convert_to_mixed_fraction
|
||||||
|
|
||||||
NUM_STRING_IT = {
|
NUM_STRING_IT = {
|
||||||
0: 'zero',
|
0: 'zero',
|
||||||
|
@ -70,7 +71,7 @@ FRACTION_STRING_IT = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def nice_number_it(mixed):
|
def nice_number_it(number, speech, denominators):
|
||||||
"""
|
"""
|
||||||
Helper for for nice_number adapted to italian
|
Helper for for nice_number adapted to italian
|
||||||
adapted to italian fron en version
|
adapted to italian fron en version
|
||||||
|
@ -78,12 +79,27 @@ def nice_number_it(mixed):
|
||||||
Convert (1 1 3) to spoken value like "1 e un terzo"
|
Convert (1 1 3) to spoken value like "1 e un terzo"
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mixed (int,int,int): the mixed number; whole, numerator, denominator
|
number (int or float): the float to format
|
||||||
Return:
|
speech (bool): format for speech (True) or display (False)
|
||||||
(str): spoken version of the number
|
denominators (iter of ints): denominators to use, default [1 .. 20]
|
||||||
|
Returns:
|
||||||
|
(str): The formatted string.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
whole, num, den = mixed
|
result = convert_to_mixed_fraction(number, denominators)
|
||||||
|
if not result:
|
||||||
|
# Give up, just represent as a 3 decimal number
|
||||||
|
return str(round(number, 3))
|
||||||
|
|
||||||
|
whole, num, den = result
|
||||||
|
|
||||||
|
if not speech:
|
||||||
|
if num == 0:
|
||||||
|
# TODO: Number grouping? E.g. "1,000,000"
|
||||||
|
return str(whole)
|
||||||
|
else:
|
||||||
|
return '{} {}/{}'.format(whole, num, den)
|
||||||
|
|
||||||
if num == 0:
|
if num == 0:
|
||||||
return str(whole)
|
return str(whole)
|
||||||
# denominatore
|
# denominatore
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from mycroft.util.lang.format_common import convert_to_mixed_fraction
|
||||||
|
|
||||||
FRACTION_STRING_PT = {
|
FRACTION_STRING_PT = {
|
||||||
2: 'meio',
|
2: 'meio',
|
||||||
|
@ -42,9 +43,23 @@ FRACTION_STRING_PT = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def nice_number_pt(result):
|
def nice_number_pt(number, speech, denominators):
|
||||||
""" Portuguese conversion for nice_number """
|
""" Portuguese conversion for nice_number """
|
||||||
|
|
||||||
|
result = convert_to_mixed_fraction(number, denominators)
|
||||||
|
if not result:
|
||||||
|
# Give up, just represent as a 3 decimal number
|
||||||
|
return str(round(number, 3))
|
||||||
|
|
||||||
whole, num, den = result
|
whole, num, den = result
|
||||||
|
|
||||||
|
if not speech:
|
||||||
|
if num == 0:
|
||||||
|
# TODO: Number grouping? E.g. "1,000,000"
|
||||||
|
return str(whole)
|
||||||
|
else:
|
||||||
|
return '{} {}/{}'.format(whole, num, den)
|
||||||
|
|
||||||
if num == 0:
|
if num == 0:
|
||||||
return str(whole)
|
return str(whole)
|
||||||
# denominador
|
# denominador
|
||||||
|
|
|
@ -23,10 +23,13 @@ from mycroft.util.format import pronounce_number
|
||||||
|
|
||||||
|
|
||||||
NUMBERS_FIXTURE_FR = {
|
NUMBERS_FIXTURE_FR = {
|
||||||
1.435634: '1.436',
|
1.435634: '1,436',
|
||||||
2: '2',
|
2: '2',
|
||||||
5.0: '5',
|
5.0: '5',
|
||||||
0.027: '0.027',
|
0.027: '0,027',
|
||||||
|
1234567890: '1234567890',
|
||||||
|
12345.67890: '12345,679',
|
||||||
|
0.027: '0,027',
|
||||||
0.5: 'un demi',
|
0.5: 'un demi',
|
||||||
1.333: '1 et 1 tiers',
|
1.333: '1 et 1 tiers',
|
||||||
2.666: '2 et 2 tiers',
|
2.666: '2 et 2 tiers',
|
||||||
|
@ -65,24 +68,37 @@ class TestNiceNumberFormat_fr(unittest.TestCase):
|
||||||
self.assertEqual(nice_number(5.5, lang="fr-fr",
|
self.assertEqual(nice_number(5.5, lang="fr-fr",
|
||||||
denominators=[1, 2, 3]),
|
denominators=[1, 2, 3]),
|
||||||
'5 et demi',
|
'5 et demi',
|
||||||
'should format 5.5 as 5 and a half not {}'.format(
|
'should format 5.5 as 5 et demi not {}'.format(
|
||||||
nice_number(5.5, lang="fr-fr",
|
nice_number(5.5, lang="fr-fr",
|
||||||
denominators=[1, 2, 3])))
|
denominators=[1, 2, 3])))
|
||||||
self.assertEqual(nice_number(2.333, denominators=[1, 2]),
|
self.assertEqual(nice_number(2.333, lang="fr-fr",
|
||||||
'2.333',
|
denominators=[1, 2]),
|
||||||
'should format 2.333 as 2.333 not {}'.format(
|
'2,333',
|
||||||
|
'should format 2.333 as 2,333 not {}'.format(
|
||||||
nice_number(2.333, lang="fr-fr",
|
nice_number(2.333, lang="fr-fr",
|
||||||
denominators=[1, 2])))
|
denominators=[1, 2])))
|
||||||
|
|
||||||
def test_no_speech_fr(self):
|
def test_no_speech_fr(self):
|
||||||
self.assertEqual(nice_number(6.777, speech=False),
|
self.assertEqual(nice_number(6.777, lang="fr-fr", speech=False),
|
||||||
'6 7/9',
|
'6 7/9',
|
||||||
'should format 6.777 as 6 7/9 not {}'.format(
|
'should format 6.777 as 6 7/9 not {}'.format(
|
||||||
nice_number(6.777, lang="fr-fr", speech=False)))
|
nice_number(6.777, lang="fr-fr", speech=False)))
|
||||||
self.assertEqual(nice_number(6.0, speech=False),
|
self.assertEqual(nice_number(6.0, lang="fr-fr", speech=False),
|
||||||
'6',
|
'6',
|
||||||
'should format 6.0 as 6 not {}'.format(
|
'should format 6.0 as 6 not {}'.format(
|
||||||
nice_number(6.0, lang="fr-fr", speech=False)))
|
nice_number(6.0, lang="fr-fr", speech=False)))
|
||||||
|
self.assertEqual(nice_number(1234567890, lang="fr-fr", speech=False),
|
||||||
|
'1 234 567 890',
|
||||||
|
'should format 1234567890 as'
|
||||||
|
'1 234 567 890 not {}'.format(
|
||||||
|
nice_number(1234567890, lang="fr-fr",
|
||||||
|
speech=False)))
|
||||||
|
self.assertEqual(nice_number(12345.6789, lang="fr-fr", speech=False),
|
||||||
|
'12 345,679',
|
||||||
|
'should format 12345.6789 as'
|
||||||
|
'12 345,679 not {}'.format(
|
||||||
|
nice_number(12345.6789, lang="fr-fr",
|
||||||
|
speech=False)))
|
||||||
|
|
||||||
|
|
||||||
# def pronounce_number(number, lang="en-us", places=2):
|
# def pronounce_number(number, lang="en-us", places=2):
|
||||||
|
|
Loading…
Reference in New Issue