diff --git a/django/utils/functional.py b/django/utils/functional.py
index 085a8fc..61c3026 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -153,6 +153,8 @@ def lazy(func, *resultclasses):
         __hash__ = object.__hash__
 
         def __mod__(self, rhs):
+            if hasattr(self, '_mod_key') and self._mod_key in rhs:
+                self.__args = self.__args[:-1] + (rhs[self._mod_key],)
             if self._delegate_bytes and not six.PY3:
                 return bytes(self) % rhs
             elif self._delegate_text:
@@ -170,7 +172,11 @@ def lazy(func, *resultclasses):
     @wraps(func)
     def __wrapper__(*args, **kw):
         # Creates the proxy object, instead of the actual value.
-        return __proxy__(args, kw)
+        proxy = __proxy__(args, kw)
+        if (func.__name__ in ('ngettext', 'ungettext', 'npgettext') and
+                isinstance(args[-1], six.string_types):
+            proxy._mod_key = args[-1]
+        return proxy
 
     return __wrapper__
 
diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
index bd38981..ea6e8ab 100644
--- a/docs/ref/utils.txt
+++ b/docs/ref/utils.txt
@@ -747,9 +747,12 @@ For a complete discussion on the usage of the following see the
 
 .. function:: ngettext_lazy(singular, plural, number)
 .. function:: ungettext_lazy(singular, plural, number)
-.. function:: npgettext_lazy(singular, plural, number)
+.. function:: npgettext_lazy(context, singular, plural, number)
 
-    Same as the non-lazy versions above, but using lazy execution.
+    Same as the non-lazy versions above, but using lazy execution. As most of
+    the time, the ``number`` argument is not known at the time you call a lazy
+    function, you can also pass a dictionary key name in ``number`` which will
+    have to match a dictionary key at string interpolation time.
 
     See :ref:`lazy translations documentation <lazy-translations>`.
 
diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index 65c6fe2..c2429f3 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -408,6 +408,26 @@ convert them to strings, because they should be converted as late as possible
 (so that the correct locale is in effect). This necessitates the use of the
 helper function described next.
 
+Lazy translations and plural
+----------------------------
+
+.. versionadded:: 1.5
+
+When using lazy translation for a plural string (``[u]n[p]gettext_lazy``), you
+generally don't know the ``number`` argument at the time of the string
+definition. Therefore, you are authorized to pass a dictionary key name in place
+of an integer as the ``number`` argument. Then, when the string is effectively
+translated with a placeholders dictionary, the ``number`` argument will get
+substituted by the value of the key in the dictionary::
+
+    class MyForm(forms.Form):
+        error_message = ungettext_lazy("You only provide %(num)d argument",
+             "You only provide %(num)d arguments", 'num')
+
+        def clean(self):
+            ...
+            raise forms.ValidationError(self.error_message % {'num': number})
+
 Joining strings: string_concat()
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo
index f825e39..d94e903 100644
Binary files a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo and b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.mo differ
diff --git a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
index a471d38..6a192a3 100644
--- a/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
+++ b/tests/regressiontests/i18n/other/locale/de/LC_MESSAGES/django.po
@@ -35,6 +35,12 @@ msgid "May"
 msgstr "Kann"
 
 #: models.py:11
+msgid "%(num)d good result"
+msgid_plural "%(num)d good results"
+msgstr[0] "%(num)d gutes Resultat"
+msgstr[1] "%(num)d guten Resultate"
+
+#: models.py:11
 msgctxt "search"
 msgid "%d result"
 msgid_plural "%d results"
@@ -75,4 +81,4 @@ msgstr "Es gibt %(num_comments)s Kommentare"
 #: models.py:23
 msgctxt "other comment count"
 msgid "There are %(num_comments)s comments"
-msgstr "Andere: Es gibt %(num_comments)s Kommentare"
\ No newline at end of file
+msgstr "Andere: Es gibt %(num_comments)s Kommentare"
diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
index 1346383..7932c62 100644
--- a/tests/regressiontests/i18n/tests.py
+++ b/tests/regressiontests/i18n/tests.py
@@ -22,7 +22,7 @@ from django.utils.safestring import mark_safe, SafeBytes, SafeString, SafeText
 from django.utils import six
 from django.utils.six import PY3
 from django.utils.translation import (ugettext, ugettext_lazy, activate,
-    deactivate, gettext_lazy, pgettext, npgettext, to_locale,
+    deactivate, gettext_lazy, ungettext_lazy, pgettext, npgettext, to_locale,
     get_language_info, get_language, get_language_from_request, trans_real)
 
 
@@ -50,7 +50,6 @@ extended_locale_paths = settings.LOCALE_PATHS + (
 )
 
 class TranslationTests(TestCase):
-
     def test_override(self):
         activate('de')
         with translation.override('pl'):
@@ -90,6 +89,16 @@ class TranslationTests(TestCase):
         self.assertEqual(six.text_type(s2), "test")
 
     @override_settings(LOCALE_PATHS=extended_locale_paths)
+    def test_ungettext_lazy(self):
+        s1 = ungettext_lazy("%(num)d good result", "%(num)d good results", 4)
+        s2 = ungettext_lazy("%(num)d good result", "%(num)d good results", 'num')
+        with translation.override('de'):
+            # s1 form not very useful, as number arg is fixed at definition time
+            self.assertEqual(s1 % {'num': 1}, "1 guten Resultate")
+            self.assertEqual(s2 % {'num': 1}, "1 gutes Resultat")
+            self.assertEqual(s2 % {'num': 4}, "4 guten Resultate")
+
+    @override_settings(LOCALE_PATHS=extended_locale_paths)
     def test_pgettext(self):
         trans_real._active = local()
         trans_real._translations = {}
