From 6cae2a550d0d09d26554b9c0c0fbab2859ca45ec Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 23 Sep 2011 16:45:40 +0000 Subject: [PATCH] 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 --- .../humanize/locale/en/LC_MESSAGES/django.po | 187 ++++++++++++++---- .../contrib}/humanize/models.py | 0 .../contrib/humanize/templatetags/humanize.py | 93 ++++++--- .../contrib}/humanize/tests.py | 14 +- docs/ref/contrib/humanize.txt | 2 +- tests/regressiontests/humanize/__init__.py | 0 6 files changed, 225 insertions(+), 71 deletions(-) rename {tests/regressiontests => django/contrib}/humanize/models.py (100%) rename {tests/regressiontests => django/contrib}/humanize/tests.py (93%) delete mode 100644 tests/regressiontests/humanize/__init__.py diff --git a/django/contrib/humanize/locale/en/LC_MESSAGES/django.po b/django/contrib/humanize/locale/en/LC_MESSAGES/django.po index 360bdd94e68..435aacca0c6 100644 --- a/django/contrib/humanize/locale/en/LC_MESSAGES/django.po +++ b/django/contrib/humanize/locale/en/LC_MESSAGES/django.po @@ -4,14 +4,27 @@ msgid "" msgstr "" "Project-Id-Version: Django\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" "Last-Translator: Django team\n" "Language-Team: English \n" +"Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\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 msgid "th" msgstr "" @@ -28,148 +41,248 @@ msgstr "" msgid "rd" msgstr "" -#: templatetags/humanize.py:78 +#: templatetags/humanize.py:57 #, python-format msgid "%(value).1f million" msgid_plural "%(value).1f million" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:79 +#: templatetags/humanize.py:58 #, python-format msgid "%(value)s million" msgid_plural "%(value)s million" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:84 +#: templatetags/humanize.py:61 #, python-format msgid "%(value).1f billion" msgid_plural "%(value).1f billion" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:85 +#: templatetags/humanize.py:62 #, python-format msgid "%(value)s billion" msgid_plural "%(value)s billion" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:90 +#: templatetags/humanize.py:65 #, python-format msgid "%(value).1f trillion" msgid_plural "%(value).1f trillion" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:91 +#: templatetags/humanize.py:66 #, python-format msgid "%(value)s trillion" msgid_plural "%(value)s trillion" msgstr[0] "" 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" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "two" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "three" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "four" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "five" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "six" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "seven" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "eight" msgstr "" -#: templatetags/humanize.py:108 +#: templatetags/humanize.py:147 msgid "nine" msgstr "" -#: templatetags/humanize.py:131 -msgid "today" -msgstr "" - -#: templatetags/humanize.py:133 -msgid "tomorrow" -msgstr "" - -#: templatetags/humanize.py:135 -msgid "yesterday" -msgstr "" - -#: templatetags/humanize.py:160 +#: templatetags/humanize.py:199 #, python-format msgctxt "naturaltime" msgid "%(delta)s ago" msgstr "" -#: templatetags/humanize.py:163 templatetags/humanize.py:185 +#: templatetags/humanize.py:202 templatetags/humanize.py:224 msgid "now" msgstr "" -#: templatetags/humanize.py:166 +#: templatetags/humanize.py:205 #, python-format msgid "a second ago" msgid_plural "%(count)s seconds ago" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:171 +#: templatetags/humanize.py:210 #, python-format msgid "a minute ago" msgid_plural "%(count)s minutes ago" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:176 +#: templatetags/humanize.py:215 #, python-format msgid "an hour ago" msgid_plural "%(count)s hours ago" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:182 +#: templatetags/humanize.py:221 #, python-format msgctxt "naturaltime" msgid "%(delta)s from now" msgstr "" -#: templatetags/humanize.py:188 +#: templatetags/humanize.py:227 #, python-format msgid "a second from now" msgid_plural "%(count)s seconds from now" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:193 +#: templatetags/humanize.py:232 #, python-format msgid "a minute from now" msgid_plural "%(count)s minutes from now" msgstr[0] "" msgstr[1] "" -#: templatetags/humanize.py:198 +#: templatetags/humanize.py:237 #, python-format msgid "an hour from now" msgid_plural "%(count)s hours from now" diff --git a/tests/regressiontests/humanize/models.py b/django/contrib/humanize/models.py similarity index 100% rename from tests/regressiontests/humanize/models.py rename to django/contrib/humanize/models.py diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py index 0da7e8e4533..d071c3a5be6 100644 --- a/django/contrib/humanize/templatetags/humanize.py +++ b/django/contrib/humanize/templatetags/humanize.py @@ -4,7 +4,6 @@ from datetime import date, datetime, timedelta from django import template from django.conf import settings from django.template import defaultfilters -from django.utils.datetime_safe import datetime, date from django.utils.encoding import force_unicode from django.utils.formats import number_format from django.utils.translation import pgettext, ungettext, ugettext as _ @@ -13,6 +12,7 @@ from django.utils.tzinfo import LocalTimezone register = template.Library() + def ordinal(value): """ 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) except (TypeError, ValueError): 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 - return u"%d%s" % (value, t[0]) - return u'%d%s' % (value, t[value % 10]) + return u"%d%s" % (value, suffixes[0]) + return u"%d%s" % (value, suffixes[value % 10]) ordinal.is_safe = True register.filter(ordinal) @@ -51,16 +51,65 @@ def intcomma(value, use_l10n=True): intcomma.is_safe = True 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): """ - Converts a large integer to a friendly text representation. Works best for - numbers over 1 million. For example, 1000000 becomes '1.0 million', 1200000 - becomes '1.2 million' and '1200000000' becomes '1.2 billion'. + Converts a large integer to a friendly text representation. Works best + for numbers over 1 million. For example, 1000000 becomes '1.0 million', + 1200000 becomes '1.2 million' and '1200000000' becomes '1.2 billion'. """ try: value = int(value) except (TypeError, ValueError): return value + if value < 1000000: return value @@ -69,27 +118,17 @@ def intword(value): Use the i18n enabled defaultfilters.floatformat if possible """ if settings.USE_L10N: - return defaultfilters.floatformat(value, 1), string_formatted - return value, float_formatted + value = defaultfilters.floatformat(value, 1) + template = string_formatted + else: + template = float_formatted + return template % {'value': value} - if value < 1000000000: - new_value = value / 1000000.0 - new_value, value_string = _check_for_i18n(new_value, - ungettext('%(value).1f million', '%(value).1f million', new_value), - ungettext('%(value)s million', '%(value)s million', 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} + for exponent, converters in intword_converters.items(): + large_number = 10 ** exponent + if value < large_number * 1000: + new_value = value / float(large_number) + return _check_for_i18n(new_value, *converters(new_value)) return value intword.is_safe = False register.filter(intword) diff --git a/tests/regressiontests/humanize/tests.py b/django/contrib/humanize/tests.py similarity index 93% rename from tests/regressiontests/humanize/tests.py rename to django/contrib/humanize/tests.py index ee80910df00..611314881fe 100644 --- a/tests/regressiontests/humanize/tests.py +++ b/django/contrib/humanize/tests.py @@ -1,5 +1,5 @@ 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.test import TestCase @@ -22,8 +22,8 @@ class HumanizeTests(TestCase): msg="%s test failed, produced '%s', should've produced '%s'" % (method, rendered, result)) def test_ordinal(self): - test_list = ('1','2','3','4','11','12', - '13','101','102','103','111', + test_list = ('1', '2', '3', '4', '11', '12', + '13', '101', '102', '103', '111', 'something else', None) result_list = ('1st', '2nd', '3rd', '4th', '11th', '12th', '13th', '101st', '102nd', '103rd', @@ -55,10 +55,12 @@ class HumanizeTests(TestCase): def test_intword(self): test_list = ('100', '1000000', '1200000', '1290000', '1000000000', '2000000000', '6000000000000', - None) + '1300000000000000', '3500000000000000000000', + '8100000000000000000000000000000000', None) result_list = ('100', '1.0 million', '1.2 million', '1.3 million', '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') def test_i18n_intcomma(self): @@ -72,7 +74,7 @@ class HumanizeTests(TestCase): def test_i18n_intword(self): test_list = ('100', '1000000', '1200000', '1290000', - '1000000000','2000000000','6000000000000') + '1000000000', '2000000000', '6000000000000') result_list = ('100', '1,0 Million', '1,2 Millionen', '1,3 Millionen', '1,0 Milliarde', '2,0 Milliarden', '6,0 Billionen') with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True): diff --git a/docs/ref/contrib/humanize.txt b/docs/ref/contrib/humanize.txt index 2f544c7ef76..292d98a9412 100644 --- a/docs/ref/contrib/humanize.txt +++ b/docs/ref/contrib/humanize.txt @@ -65,7 +65,7 @@ Examples: * ``1200000`` becomes ``1.2 million``. * ``1200000000`` becomes ``1.2 billion``. -Values up to 1000000000000000 (one quadrillion) are supported. +Values up to 10^100 (Googol) are supported. :ref:`Format localization ` will be respected if enabled, e.g. with the ``'de'`` language: diff --git a/tests/regressiontests/humanize/__init__.py b/tests/regressiontests/humanize/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000