Browse Source

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
Jannis Leidel 13 years ago
parent
commit
6cae2a550d

+ 150 - 37
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 <en@li.org>\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"

+ 0 - 0
tests/regressiontests/humanize/models.py → django/contrib/humanize/models.py


+ 66 - 27
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)

+ 8 - 6
tests/regressiontests/humanize/tests.py → 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):

+ 1 - 1
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 <format-localization>` will be respected if enabled,
 e.g. with the ``'de'`` language:

+ 0 - 0
tests/regressiontests/humanize/__init__.py