Fixed #16878 -- Improved intword filter to support numbers up to decillion and googol. Thanks to crodjer for the initial patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16897 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jannis Leidel 2011-09-23 16:45:40 +00:00
parent 33ad413c1c
commit 6cae2a550d
6 changed files with 225 additions and 71 deletions

View File

@ -4,14 +4,27 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Django\n" "Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-05-16 17:30+0200\n" "POT-Creation-Date: 2011-09-23 17:43+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n" "PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n" "Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n" "Language-Team: English <en@li.org>\n"
"Language: en\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: tests.py:101 templatetags/humanize.py:170
msgid "today"
msgstr ""
#: tests.py:101 templatetags/humanize.py:174
msgid "yesterday"
msgstr ""
#: tests.py:101 templatetags/humanize.py:172
msgid "tomorrow"
msgstr ""
#: templatetags/humanize.py:25 #: templatetags/humanize.py:25
msgid "th" msgid "th"
msgstr "" msgstr ""
@ -28,148 +41,248 @@ msgstr ""
msgid "rd" msgid "rd"
msgstr "" msgstr ""
#: templatetags/humanize.py:78 #: templatetags/humanize.py:57
#, python-format #, python-format
msgid "%(value).1f million" msgid "%(value).1f million"
msgid_plural "%(value).1f million" msgid_plural "%(value).1f million"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: templatetags/humanize.py:79 #: templatetags/humanize.py:58
#, python-format #, python-format
msgid "%(value)s million" msgid "%(value)s million"
msgid_plural "%(value)s million" msgid_plural "%(value)s million"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: templatetags/humanize.py:84 #: templatetags/humanize.py:61
#, python-format #, python-format
msgid "%(value).1f billion" msgid "%(value).1f billion"
msgid_plural "%(value).1f billion" msgid_plural "%(value).1f billion"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: templatetags/humanize.py:85 #: templatetags/humanize.py:62
#, python-format #, python-format
msgid "%(value)s billion" msgid "%(value)s billion"
msgid_plural "%(value)s billion" msgid_plural "%(value)s billion"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: templatetags/humanize.py:90 #: templatetags/humanize.py:65
#, python-format #, python-format
msgid "%(value).1f trillion" msgid "%(value).1f trillion"
msgid_plural "%(value).1f trillion" msgid_plural "%(value).1f trillion"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: templatetags/humanize.py:91 #: templatetags/humanize.py:66
#, python-format #, python-format
msgid "%(value)s trillion" msgid "%(value)s trillion"
msgid_plural "%(value)s trillion" msgid_plural "%(value)s trillion"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: templatetags/humanize.py:108 #: templatetags/humanize.py:69
#, python-format
msgid "%(value).1f quadrillion"
msgid_plural "%(value).1f quadrillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:70
#, python-format
msgid "%(value)s quadrillion"
msgid_plural "%(value)s quadrillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:73
#, python-format
msgid "%(value).1f quintillion"
msgid_plural "%(value).1f quintillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:74
#, python-format
msgid "%(value)s quintillion"
msgid_plural "%(value)s quintillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:77
#, python-format
msgid "%(value).1f sextillion"
msgid_plural "%(value).1f sextillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:78
#, python-format
msgid "%(value)s sextillion"
msgid_plural "%(value)s sextillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:81
#, python-format
msgid "%(value).1f septillion"
msgid_plural "%(value).1f septillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:82
#, python-format
msgid "%(value)s septillion"
msgid_plural "%(value)s septillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:85
#, python-format
msgid "%(value).1f octillion"
msgid_plural "%(value).1f octillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:86
#, python-format
msgid "%(value)s octillion"
msgid_plural "%(value)s octillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:89
#, python-format
msgid "%(value).1f nonillion"
msgid_plural "%(value).1f nonillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:90
#, python-format
msgid "%(value)s nonillion"
msgid_plural "%(value)s nonillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:93
#, python-format
msgid "%(value).1f decillion"
msgid_plural "%(value).1f decillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:94
#, python-format
msgid "%(value)s decillion"
msgid_plural "%(value)s decillion"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:97
#, python-format
msgid "%(value).1f googol"
msgid_plural "%(value).1f googol"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:98
#, python-format
msgid "%(value)s googol"
msgid_plural "%(value)s googol"
msgstr[0] ""
msgstr[1] ""
#: templatetags/humanize.py:147
msgid "one" msgid "one"
msgstr "" msgstr ""
#: templatetags/humanize.py:108 #: templatetags/humanize.py:147
msgid "two" msgid "two"
msgstr "" msgstr ""
#: templatetags/humanize.py:108 #: templatetags/humanize.py:147
msgid "three" msgid "three"
msgstr "" msgstr ""
#: templatetags/humanize.py:108 #: templatetags/humanize.py:147
msgid "four" msgid "four"
msgstr "" msgstr ""
#: templatetags/humanize.py:108 #: templatetags/humanize.py:147
msgid "five" msgid "five"
msgstr "" msgstr ""
#: templatetags/humanize.py:108 #: templatetags/humanize.py:147
msgid "six" msgid "six"
msgstr "" msgstr ""
#: templatetags/humanize.py:108 #: templatetags/humanize.py:147
msgid "seven" msgid "seven"
msgstr "" msgstr ""
#: templatetags/humanize.py:108 #: templatetags/humanize.py:147
msgid "eight" msgid "eight"
msgstr "" msgstr ""
#: templatetags/humanize.py:108 #: templatetags/humanize.py:147
msgid "nine" msgid "nine"
msgstr "" msgstr ""
#: templatetags/humanize.py:131 #: templatetags/humanize.py:199
msgid "today"
msgstr ""
#: templatetags/humanize.py:133
msgid "tomorrow"
msgstr ""
#: templatetags/humanize.py:135
msgid "yesterday"
msgstr ""
#: templatetags/humanize.py:160
#, python-format #, python-format
msgctxt "naturaltime" msgctxt "naturaltime"
msgid "%(delta)s ago" msgid "%(delta)s ago"
msgstr "" msgstr ""
#: templatetags/humanize.py:163 templatetags/humanize.py:185 #: templatetags/humanize.py:202 templatetags/humanize.py:224
msgid "now" msgid "now"
msgstr "" msgstr ""
#: templatetags/humanize.py:166 #: templatetags/humanize.py:205
#, python-format #, python-format
msgid "a second ago" msgid "a second ago"
msgid_plural "%(count)s seconds ago" msgid_plural "%(count)s seconds ago"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: templatetags/humanize.py:171 #: templatetags/humanize.py:210
#, python-format #, python-format
msgid "a minute ago" msgid "a minute ago"
msgid_plural "%(count)s minutes ago" msgid_plural "%(count)s minutes ago"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: templatetags/humanize.py:176 #: templatetags/humanize.py:215
#, python-format #, python-format
msgid "an hour ago" msgid "an hour ago"
msgid_plural "%(count)s hours ago" msgid_plural "%(count)s hours ago"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: templatetags/humanize.py:182 #: templatetags/humanize.py:221
#, python-format #, python-format
msgctxt "naturaltime" msgctxt "naturaltime"
msgid "%(delta)s from now" msgid "%(delta)s from now"
msgstr "" msgstr ""
#: templatetags/humanize.py:188 #: templatetags/humanize.py:227
#, python-format #, python-format
msgid "a second from now" msgid "a second from now"
msgid_plural "%(count)s seconds from now" msgid_plural "%(count)s seconds from now"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: templatetags/humanize.py:193 #: templatetags/humanize.py:232
#, python-format #, python-format
msgid "a minute from now" msgid "a minute from now"
msgid_plural "%(count)s minutes from now" msgid_plural "%(count)s minutes from now"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: templatetags/humanize.py:198 #: templatetags/humanize.py:237
#, python-format #, python-format
msgid "an hour from now" msgid "an hour from now"
msgid_plural "%(count)s hours from now" msgid_plural "%(count)s hours from now"

View File

@ -4,7 +4,6 @@ from datetime import date, datetime, timedelta
from django import template from django import template
from django.conf import settings from django.conf import settings
from django.template import defaultfilters from django.template import defaultfilters
from django.utils.datetime_safe import datetime, date
from django.utils.encoding import force_unicode from django.utils.encoding import force_unicode
from django.utils.formats import number_format from django.utils.formats import number_format
from django.utils.translation import pgettext, ungettext, ugettext as _ from django.utils.translation import pgettext, ungettext, ugettext as _
@ -13,6 +12,7 @@ from django.utils.tzinfo import LocalTimezone
register = template.Library() register = template.Library()
def ordinal(value): def ordinal(value):
""" """
Converts an integer to its ordinal as a string. 1 is '1st', 2 is '2nd', Converts an integer to its ordinal as a string. 1 is '1st', 2 is '2nd',
@ -22,10 +22,10 @@ def ordinal(value):
value = int(value) value = int(value)
except (TypeError, ValueError): except (TypeError, ValueError):
return value return value
t = (_('th'), _('st'), _('nd'), _('rd'), _('th'), _('th'), _('th'), _('th'), _('th'), _('th')) suffixes = (_('th'), _('st'), _('nd'), _('rd'), _('th'), _('th'), _('th'), _('th'), _('th'), _('th'))
if value % 100 in (11, 12, 13): # special case if value % 100 in (11, 12, 13): # special case
return u"%d%s" % (value, t[0]) return u"%d%s" % (value, suffixes[0])
return u'%d%s' % (value, t[value % 10]) return u"%d%s" % (value, suffixes[value % 10])
ordinal.is_safe = True ordinal.is_safe = True
register.filter(ordinal) register.filter(ordinal)
@ -51,16 +51,65 @@ def intcomma(value, use_l10n=True):
intcomma.is_safe = True intcomma.is_safe = True
register.filter(intcomma) register.filter(intcomma)
# A dictionary of standard large number to their converters
intword_converters = {
6: lambda number: (
ungettext('%(value).1f million', '%(value).1f million', number),
ungettext('%(value)s million', '%(value)s million', number),
),
9: lambda number: (
ungettext('%(value).1f billion', '%(value).1f billion', number),
ungettext('%(value)s billion', '%(value)s billion', number),
),
12: lambda number: (
ungettext('%(value).1f trillion', '%(value).1f trillion', number),
ungettext('%(value)s trillion', '%(value)s trillion', number),
),
15: lambda number: (
ungettext('%(value).1f quadrillion', '%(value).1f quadrillion', number),
ungettext('%(value)s quadrillion', '%(value)s quadrillion', number),
),
18: lambda number: (
ungettext('%(value).1f quintillion', '%(value).1f quintillion', number),
ungettext('%(value)s quintillion', '%(value)s quintillion', number),
),
21: lambda number: (
ungettext('%(value).1f sextillion', '%(value).1f sextillion', number),
ungettext('%(value)s sextillion', '%(value)s sextillion', number),
),
24: lambda number: (
ungettext('%(value).1f septillion', '%(value).1f septillion', number),
ungettext('%(value)s septillion', '%(value)s septillion', number),
),
27: lambda number: (
ungettext('%(value).1f octillion', '%(value).1f octillion', number),
ungettext('%(value)s octillion', '%(value)s octillion', number),
),
30: lambda number: (
ungettext('%(value).1f nonillion', '%(value).1f nonillion', number),
ungettext('%(value)s nonillion', '%(value)s nonillion', number),
),
33: lambda number: (
ungettext('%(value).1f decillion', '%(value).1f decillion', number),
ungettext('%(value)s decillion', '%(value)s decillion', number),
),
100: lambda number: (
ungettext('%(value).1f googol', '%(value).1f googol', number),
ungettext('%(value)s googol', '%(value)s googol', number),
),
}
def intword(value): def intword(value):
""" """
Converts a large integer to a friendly text representation. Works best for Converts a large integer to a friendly text representation. Works best
numbers over 1 million. For example, 1000000 becomes '1.0 million', 1200000 for numbers over 1 million. For example, 1000000 becomes '1.0 million',
becomes '1.2 million' and '1200000000' becomes '1.2 billion'. 1200000 becomes '1.2 million' and '1200000000' becomes '1.2 billion'.
""" """
try: try:
value = int(value) value = int(value)
except (TypeError, ValueError): except (TypeError, ValueError):
return value return value
if value < 1000000: if value < 1000000:
return value return value
@ -69,27 +118,17 @@ def intword(value):
Use the i18n enabled defaultfilters.floatformat if possible Use the i18n enabled defaultfilters.floatformat if possible
""" """
if settings.USE_L10N: if settings.USE_L10N:
return defaultfilters.floatformat(value, 1), string_formatted value = defaultfilters.floatformat(value, 1)
return value, float_formatted template = string_formatted
else:
template = float_formatted
return template % {'value': value}
if value < 1000000000: for exponent, converters in intword_converters.items():
new_value = value / 1000000.0 large_number = 10 ** exponent
new_value, value_string = _check_for_i18n(new_value, if value < large_number * 1000:
ungettext('%(value).1f million', '%(value).1f million', new_value), new_value = value / float(large_number)
ungettext('%(value)s million', '%(value)s million', new_value)) return _check_for_i18n(new_value, *converters(new_value))
return value_string % {'value': new_value}
if value < 1000000000000:
new_value = value / 1000000000.0
new_value, value_string = _check_for_i18n(new_value,
ungettext('%(value).1f billion', '%(value).1f billion', new_value),
ungettext('%(value)s billion', '%(value)s billion', new_value))
return value_string % {'value': new_value}
if value < 1000000000000000:
new_value = value / 1000000000000.0
new_value, value_string = _check_for_i18n(new_value,
ungettext('%(value).1f trillion', '%(value).1f trillion', new_value),
ungettext('%(value)s trillion', '%(value)s trillion', new_value))
return value_string % {'value': new_value}
return value return value
intword.is_safe = False intword.is_safe = False
register.filter(intword) register.filter(intword)

View File

@ -1,5 +1,5 @@
from __future__ import with_statement from __future__ import with_statement
from datetime import timedelta, date, datetime, tzinfo from datetime import timedelta, date, datetime
from django.template import Template, Context, add_to_builtins, defaultfilters from django.template import Template, Context, add_to_builtins, defaultfilters
from django.test import TestCase from django.test import TestCase
@ -22,8 +22,8 @@ class HumanizeTests(TestCase):
msg="%s test failed, produced '%s', should've produced '%s'" % (method, rendered, result)) msg="%s test failed, produced '%s', should've produced '%s'" % (method, rendered, result))
def test_ordinal(self): def test_ordinal(self):
test_list = ('1','2','3','4','11','12', test_list = ('1', '2', '3', '4', '11', '12',
'13','101','102','103','111', '13', '101', '102', '103', '111',
'something else', None) 'something else', None)
result_list = ('1st', '2nd', '3rd', '4th', '11th', result_list = ('1st', '2nd', '3rd', '4th', '11th',
'12th', '13th', '101st', '102nd', '103rd', '12th', '13th', '101st', '102nd', '103rd',
@ -55,10 +55,12 @@ class HumanizeTests(TestCase):
def test_intword(self): def test_intword(self):
test_list = ('100', '1000000', '1200000', '1290000', test_list = ('100', '1000000', '1200000', '1290000',
'1000000000', '2000000000', '6000000000000', '1000000000', '2000000000', '6000000000000',
None) '1300000000000000', '3500000000000000000000',
'8100000000000000000000000000000000', None)
result_list = ('100', '1.0 million', '1.2 million', '1.3 million', result_list = ('100', '1.0 million', '1.2 million', '1.3 million',
'1.0 billion', '2.0 billion', '6.0 trillion', '1.0 billion', '2.0 billion', '6.0 trillion',
None) '1.3 quadrillion', '3.5 sextillion',
'8.1 decillion', None)
self.humanize_tester(test_list, result_list, 'intword') self.humanize_tester(test_list, result_list, 'intword')
def test_i18n_intcomma(self): def test_i18n_intcomma(self):
@ -72,7 +74,7 @@ class HumanizeTests(TestCase):
def test_i18n_intword(self): def test_i18n_intword(self):
test_list = ('100', '1000000', '1200000', '1290000', test_list = ('100', '1000000', '1200000', '1290000',
'1000000000','2000000000','6000000000000') '1000000000', '2000000000', '6000000000000')
result_list = ('100', '1,0 Million', '1,2 Millionen', '1,3 Millionen', result_list = ('100', '1,0 Million', '1,2 Millionen', '1,3 Millionen',
'1,0 Milliarde', '2,0 Milliarden', '6,0 Billionen') '1,0 Milliarde', '2,0 Milliarden', '6,0 Billionen')
with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True): with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True):

View File

@ -65,7 +65,7 @@ Examples:
* ``1200000`` becomes ``1.2 million``. * ``1200000`` becomes ``1.2 million``.
* ``1200000000`` becomes ``1.2 billion``. * ``1200000000`` becomes ``1.2 billion``.
Values up to 1000000000000000 (one quadrillion) are supported. Values up to 10^100 (Googol) are supported.
:ref:`Format localization <format-localization>` will be respected if enabled, :ref:`Format localization <format-localization>` will be respected if enabled,
e.g. with the ``'de'`` language: e.g. with the ``'de'`` language: