diff --git a/django/utils/functional.py b/django/utils/functional.py
index 085a8fc..4fff693 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -51,7 +51,7 @@ class Promise(object):
     """
     pass
 
-def lazy(func, *resultclasses):
+def lazy(func, *resultclasses, **kwargs):
     """
     Turns any callable into a lazy evaluated callable. You need to give result
     classes or types -- at least one is needed so that the automatic forcing of
@@ -59,8 +59,9 @@ def lazy(func, *resultclasses):
     function is evaluated on every access.
     """
 
+    mixin = kwargs.get('mixin', object)
     @total_ordering
-    class __proxy__(Promise):
+    class __proxy__(Promise, mixin):
         """
         Encapsulate a function call and act as a proxy for methods that are
         called on the result of that function. The function is not evaluated
@@ -80,6 +81,7 @@ def lazy(func, *resultclasses):
                 (func, self.__args, self.__kw) + resultclasses
             )
 
+        @classmethod
         def __prepare_class__(cls):
             cls.__dispatch = {}
             for resultclass in resultclasses:
@@ -106,8 +108,8 @@ def lazy(func, *resultclasses):
                     cls.__bytes__ = cls.__bytes_cast
                 else:
                     cls.__str__ = cls.__bytes_cast
-        __prepare_class__ = classmethod(__prepare_class__)
 
+        @classmethod
         def __promise__(cls, klass, funcname, method):
             # Builds a wrapper around some magic method and registers that magic
             # method for the given type and method name.
@@ -124,7 +126,6 @@ def lazy(func, *resultclasses):
                 cls.__dispatch[klass] = {}
             cls.__dispatch[klass][funcname] = method
             return __wrapper__
-        __promise__ = classmethod(__promise__)
 
         def __text_cast(self):
             return func(*self.__args, **self.__kw)
diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py
index f3cc634..8e87e93 100644
--- a/django/utils/translation/__init__.py
+++ b/django/utils/translation/__init__.py
@@ -80,11 +80,40 @@ def npgettext(context, singular, plural, number):
     return _trans.npgettext(context, singular, plural, number)
 
 gettext_lazy = lazy(gettext, str)
-ngettext_lazy = lazy(ngettext, str)
 ugettext_lazy = lazy(ugettext, six.text_type)
-ungettext_lazy = lazy(ungettext, six.text_type)
 pgettext_lazy = lazy(pgettext, six.text_type)
-npgettext_lazy = lazy(npgettext, six.text_type)
+
+
+def lazy_number(func, resultclasses, number=None, **kwargs):
+    class LazyNumberMixin(object):
+        def __mod__(self, rhs):
+            assert self._proxy____kw['number'], 'This object is not resolved yet'
+            return super(LazyNumberMixin, self).__mod__(rhs)
+
+        def resolve(self, number):
+            self._proxy____kw['number'] = number
+            return self
+
+    if number is not None:
+        kwargs['number'] = number
+        proxy = lazy(func, *resultclasses)(**kwargs)
+    else:
+        proxy = lazy(func, *resultclasses, mixin=LazyNumberMixin)(**kwargs)
+    return proxy
+
+def ngettext_lazy(singular, plural, number=None):
+    return lazy_number(
+        ngettext, [str], singular=singular, plural=plural, number=number)
+
+def ungettext_lazy(singular, plural, number=None):
+    return lazy_number(
+        ungettext, [six.text_type], singular=singular, plural=plural, number=number)
+
+def npgettext_lazy(context, singular, plural, number=None):
+    return lazy_number(
+        npgettext, [six.text_type], context=context, singular=singular, plural=plural, number=number)
+
+
 
 def activate(language):
     return _trans.activate(language)
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..63b5339 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,18 @@ msgid "May"
 msgstr "Kann"
 
 #: models.py:11
+msgid "%d good result"
+msgid_plural "%d good results"
+msgstr[0] "%d gutes Resultat"
+msgstr[1] "%d guten Resultate"
+
+#: 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 +87,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 4054f85..7e149a6 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,22 @@ class TranslationTests(TestCase):
         self.assertEqual(six.text_type(s2), "test")
 
     @override_settings(LOCALE_PATHS=extended_locale_paths)
+    def test_ungettext_lazy(self):
+        s = ungettext_lazy("%d good result", "%d good result")
+        with translation.override('de'):
+            self.assertEqual(s.resolve(1) % 1, "1 gutes Resultat")
+            self.assertEqual(s.resolve(4) % 4, "4 guten Resultate")
+
+        s1 = ungettext_lazy("%(num)d good result", "%(num)d good results", 4)
+        s2 = ungettext_lazy("%(num)d good result", "%(num)d good results")
+        s3 = ungettext_lazy("%(num)d good result", "%(num)d good results")
+        with translation.override('de'):
+            self.assertEqual(s1 % {'num': 4}, "4 guten Resultate")
+            self.assertEqual(s2.resolve(1) % {'num': 1}, "1 gutes Resultat")
+            self.assertEqual(s2.resolve(4) % {'num': 4}, "4 guten Resultate")
+            self.assertEqual(s3.resolve(5) % {'num': 5}, "5 guten Resultate")
+
+    @override_settings(LOCALE_PATHS=extended_locale_paths)
     def test_pgettext(self):
         trans_real._active = local()
         trans_real._translations = {}


