Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #19160 -- Made lazy plural translations usable.

Many thanks to Alexey Boriskin, Claude Paroz and Julien Phalip.
  • Loading branch information...
commit 3f1a0c0040b9a950584f4b309a1b670b0e709de5 1 parent 47ddd6a
Aymeric Augustin authored January 30, 2013
3  django/utils/functional.py
@@ -157,8 +157,7 @@ def __mod__(self, rhs):
157 157
                 return bytes(self) % rhs
158 158
             elif self._delegate_text:
159 159
                 return six.text_type(self) % rhs
160  
-            else:
161  
-                raise AssertionError('__mod__ not supported for non-string types')
  160
+            return self.__cast() % rhs
162 161
 
163 162
         def __deepcopy__(self, memo):
164 163
             # Instances of this class are effectively immutable. It's just a
35  django/utils/translation/__init__.py
@@ -85,11 +85,40 @@ def npgettext(context, singular, plural, number):
85 85
     return _trans.npgettext(context, singular, plural, number)
86 86
 
87 87
 gettext_lazy = lazy(gettext, str)
88  
-ngettext_lazy = lazy(ngettext, str)
89 88
 ugettext_lazy = lazy(ugettext, six.text_type)
90  
-ungettext_lazy = lazy(ungettext, six.text_type)
91 89
 pgettext_lazy = lazy(pgettext, six.text_type)
92  
-npgettext_lazy = lazy(npgettext, six.text_type)
  90
+
  91
+def lazy_number(func, resultclass, number=None, **kwargs):
  92
+    if isinstance(number, int):
  93
+        kwargs['number'] = number
  94
+        proxy = lazy(func, resultclass)(**kwargs)
  95
+    else:
  96
+        class NumberAwareString(resultclass):
  97
+            def __mod__(self, rhs):
  98
+                if isinstance(rhs, dict) and number:
  99
+                    try:
  100
+                        number_value = rhs[number]
  101
+                    except KeyError:
  102
+                        raise KeyError('Your dictionary lacks key \'%s\'. '
  103
+                            'Please provide it, because it is required to '
  104
+                            'determine whether string is singular or plural.'
  105
+                            % number)
  106
+                else:
  107
+                    number_value = rhs
  108
+                kwargs['number'] = number_value
  109
+                return func(**kwargs) % rhs
  110
+
  111
+        proxy = lazy(lambda **kwargs: NumberAwareString(), NumberAwareString)(**kwargs)
  112
+    return proxy
  113
+
  114
+def ngettext_lazy(singular, plural, number=None):
  115
+    return lazy_number(ngettext, str, singular=singular, plural=plural, number=number)
  116
+
  117
+def ungettext_lazy(singular, plural, number=None):
  118
+    return lazy_number(ungettext, six.text_type, singular=singular, plural=plural, number=number)
  119
+
  120
+def npgettext_lazy(context, singular, plural, number=None):
  121
+    return lazy_number(npgettext, six.text_type, context=context, singular=singular, plural=plural, number=number)
93 122
 
94 123
 def activate(language):
95 124
     return _trans.activate(language)
4  docs/releases/1.6.txt
@@ -35,6 +35,10 @@ Minor features
35 35
   :class:`~django.forms.URLField` use the new type attributes available in
36 36
   HTML5 (type='email', type='url').
37 37
 
  38
+* The ``number`` argument for :ref:`lazy plural translations
  39
+  <lazy-plural-translations>` can be provided at translation time rather than
  40
+  at definition time.
  41
+
38 42
 Backwards incompatible changes in 1.6
39 43
 =====================================
40 44
 
35  docs/topics/i18n/translation.txt
@@ -414,6 +414,41 @@ convert them to strings, because they should be converted as late as possible
414 414
 (so that the correct locale is in effect). This necessitates the use of the
415 415
 helper function described next.
416 416
 
  417
+.. _lazy-plural-translations:
  418
+
  419
+Lazy translations and plural
  420
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  421
+
  422
+.. versionadded:: 1.6
  423
+
  424
+When using lazy translation for a plural string (``[u]n[p]gettext_lazy``), you
  425
+generally don't know the ``number`` argument at the time of the string
  426
+definition. Therefore, you are authorized to pass a key name instead of an
  427
+integer as the ``number`` argument. Then ``number`` will be looked up in the
  428
+dictionary under that key during string interpolation. Here's example::
  429
+
  430
+    class MyForm(forms.Form):
  431
+        error_message = ungettext_lazy("You only provided %(num)d argument",
  432
+            "You only provided %(num)d arguments", 'num')
  433
+
  434
+        def clean(self):
  435
+            # ...
  436
+            if error:
  437
+                raise forms.ValidationError(self.error_message % {'num': number})
  438
+
  439
+If the string contains exactly one unnamed placeholder, you can interpolate
  440
+directly with the ``number`` argument::
  441
+
  442
+    class MyForm(forms.Form):
  443
+        error_message = ungettext_lazy("You provided %d argument",
  444
+            "You provided %d arguments")
  445
+
  446
+        def clean(self):
  447
+            # ...
  448
+            if error:
  449
+                raise forms.ValidationError(self.error_message % number)
  450
+
  451
+
417 452
 Joining strings: string_concat()
418 453
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
419 454
 
BIN  tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo
Binary file not shown
26  tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
@@ -41,6 +41,32 @@ msgid_plural "%d results"
41 41
 msgstr[0] "%d Resultat"
42 42
 msgstr[1] "%d Resultate"
43 43
 
  44
+#: models.py:11
  45
+msgid "%d good result"
  46
+msgid_plural "%d good results"
  47
+msgstr[0] "%d gutes Resultat"
  48
+msgstr[1] "%d guten Resultate"
  49
+
  50
+#: models.py:11
  51
+msgctxt "Exclamation"
  52
+msgid "%d good result"
  53
+msgid_plural "%d good results"
  54
+msgstr[0] "%d gutes Resultat!"
  55
+msgstr[1] "%d guten Resultate!"
  56
+
  57
+#: models.py:11
  58
+msgid "Hi %(name)s, %(num)d good result"
  59
+msgid_plural "Hi %(name)s, %(num)d good results"
  60
+msgstr[0] "Hallo %(name)s, %(num)d gutes Resultat"
  61
+msgstr[1] "Hallo %(name)s, %(num)d guten Resultate"
  62
+
  63
+#: models.py:11
  64
+msgctxt "Greeting"
  65
+msgid "Hi %(name)s, %(num)d good result"
  66
+msgid_plural "Hi %(name)s, %(num)d good results"
  67
+msgstr[0] "Willkommen %(name)s, %(num)d gutes Resultat"
  68
+msgstr[1] "Willkommen %(name)s, %(num)d guten Resultate"
  69
+
44 70
 #: models.py:13
45 71
 #, python-format
46 72
 msgid "The result was %(percent)s%%"
49  tests/regressiontests/i18n/tests.py
@@ -22,10 +22,15 @@
22 22
 from django.utils.safestring import mark_safe, SafeBytes, SafeString, SafeText
23 23
 from django.utils import six
24 24
 from django.utils.six import PY3
25  
-from django.utils.translation import (ugettext, ugettext_lazy, activate,
26  
-    deactivate, gettext_lazy, pgettext, npgettext, to_locale,
27  
-    get_language_info, get_language, get_language_from_request, trans_real)
28  
-
  25
+from django.utils.translation import (activate, deactivate,
  26
+    get_language,  get_language_from_request, get_language_info,
  27
+    to_locale, trans_real,
  28
+    gettext, gettext_lazy,
  29
+    ugettext, ugettext_lazy,
  30
+    ngettext, ngettext_lazy,
  31
+    ungettext, ungettext_lazy,
  32
+    pgettext, pgettext_lazy,
  33
+    npgettext, npgettext_lazy)
29 34
 
30 35
 from .commands.tests import can_run_extraction_tests, can_run_compilation_tests
31 36
 if can_run_extraction_tests:
@@ -96,6 +101,42 @@ def test_lazy_pickle(self):
96 101
         self.assertEqual(six.text_type(s2), "test")
97 102
 
98 103
     @override_settings(LOCALE_PATHS=extended_locale_paths)
  104
+    def test_ungettext_lazy(self):
  105
+        s0 = ungettext_lazy("%d good result", "%d good results")
  106
+        s1 = ngettext_lazy(str("%d good result"), str("%d good results"))
  107
+        s2 = npgettext_lazy('Exclamation', '%d good result', '%d good results')
  108
+        with translation.override('de'):
  109
+            self.assertEqual(s0 % 1, "1 gutes Resultat")
  110
+            self.assertEqual(s0 % 4, "4 guten Resultate")
  111
+            self.assertEqual(s1 % 1, str("1 gutes Resultat"))
  112
+            self.assertEqual(s1 % 4, str("4 guten Resultate"))
  113
+            self.assertEqual(s2 % 1, "1 gutes Resultat!")
  114
+            self.assertEqual(s2 % 4, "4 guten Resultate!")
  115
+
  116
+        s3 = ungettext_lazy("Hi %(name)s, %(num)d good result", "Hi %(name)s, %(num)d good results", 4)
  117
+        s4 = ungettext_lazy("Hi %(name)s, %(num)d good result", "Hi %(name)s, %(num)d good results", 'num')
  118
+        s5 = ngettext_lazy(str("Hi %(name)s, %(num)d good result"), str("Hi %(name)s, %(num)d good results"), 4)
  119
+        s6 = ngettext_lazy(str("Hi %(name)s, %(num)d good result"), str("Hi %(name)s, %(num)d good results"), 'num')
  120
+        s7 = npgettext_lazy('Greeting', "Hi %(name)s, %(num)d good result", "Hi %(name)s, %(num)d good results", 4)
  121
+        s8 = npgettext_lazy('Greeting', "Hi %(name)s, %(num)d good result", "Hi %(name)s, %(num)d good results", 'num')
  122
+        with translation.override('de'):
  123
+            self.assertEqual(s3 % {'num': 4, 'name': 'Jim'}, "Hallo Jim, 4 guten Resultate")
  124
+            self.assertEqual(s4 % {'name': 'Jim', 'num': 1}, "Hallo Jim, 1 gutes Resultat")
  125
+            self.assertEqual(s4 % {'name': 'Jim', 'num': 5}, "Hallo Jim, 5 guten Resultate")
  126
+            with six.assertRaisesRegex(self, KeyError, 'Your dictionary lacks key.*'):
  127
+                s4 % {'name': 'Jim'}
  128
+            self.assertEqual(s5 % {'num': 4, 'name': 'Jim'}, str("Hallo Jim, 4 guten Resultate"))
  129
+            self.assertEqual(s6 % {'name': 'Jim', 'num': 1}, str("Hallo Jim, 1 gutes Resultat"))
  130
+            self.assertEqual(s6 % {'name': 'Jim', 'num': 5}, str("Hallo Jim, 5 guten Resultate"))
  131
+            with six.assertRaisesRegex(self, KeyError, 'Your dictionary lacks key.*'):
  132
+                s6 % {'name': 'Jim'}
  133
+            self.assertEqual(s7 % {'num': 4, 'name': 'Jim'}, "Willkommen Jim, 4 guten Resultate")
  134
+            self.assertEqual(s8 % {'name': 'Jim', 'num': 1}, "Willkommen Jim, 1 gutes Resultat")
  135
+            self.assertEqual(s8 % {'name': 'Jim', 'num': 5}, "Willkommen Jim, 5 guten Resultate")
  136
+            with six.assertRaisesRegex(self, KeyError, 'Your dictionary lacks key.*'):
  137
+                s8 % {'name': 'Jim'}
  138
+
  139
+    @override_settings(LOCALE_PATHS=extended_locale_paths)
99 140
     def test_pgettext(self):
100 141
         trans_real._active = local()
101 142
         trans_real._translations = {}

0 notes on commit 3f1a0c0

Please sign in to comment.
Something went wrong with that request. Please try again.