Fixed #19160 -- Made lazy plural translations usable.
Many thanks to Alexey Boriskin, Claude Paroz and Julien Phalip.
This commit is contained in:
parent
47ddd6a408
commit
3f1a0c0040
|
@ -157,8 +157,7 @@ def lazy(func, *resultclasses):
|
||||||
return bytes(self) % rhs
|
return bytes(self) % rhs
|
||||||
elif self._delegate_text:
|
elif self._delegate_text:
|
||||||
return six.text_type(self) % rhs
|
return six.text_type(self) % rhs
|
||||||
else:
|
return self.__cast() % rhs
|
||||||
raise AssertionError('__mod__ not supported for non-string types')
|
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
# Instances of this class are effectively immutable. It's just a
|
# Instances of this class are effectively immutable. It's just a
|
||||||
|
|
|
@ -85,11 +85,40 @@ def npgettext(context, singular, plural, number):
|
||||||
return _trans.npgettext(context, singular, plural, number)
|
return _trans.npgettext(context, singular, plural, number)
|
||||||
|
|
||||||
gettext_lazy = lazy(gettext, str)
|
gettext_lazy = lazy(gettext, str)
|
||||||
ngettext_lazy = lazy(ngettext, str)
|
|
||||||
ugettext_lazy = lazy(ugettext, six.text_type)
|
ugettext_lazy = lazy(ugettext, six.text_type)
|
||||||
ungettext_lazy = lazy(ungettext, six.text_type)
|
|
||||||
pgettext_lazy = lazy(pgettext, six.text_type)
|
pgettext_lazy = lazy(pgettext, six.text_type)
|
||||||
npgettext_lazy = lazy(npgettext, six.text_type)
|
|
||||||
|
def lazy_number(func, resultclass, number=None, **kwargs):
|
||||||
|
if isinstance(number, int):
|
||||||
|
kwargs['number'] = number
|
||||||
|
proxy = lazy(func, resultclass)(**kwargs)
|
||||||
|
else:
|
||||||
|
class NumberAwareString(resultclass):
|
||||||
|
def __mod__(self, rhs):
|
||||||
|
if isinstance(rhs, dict) and number:
|
||||||
|
try:
|
||||||
|
number_value = rhs[number]
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError('Your dictionary lacks key \'%s\'. '
|
||||||
|
'Please provide it, because it is required to '
|
||||||
|
'determine whether string is singular or plural.'
|
||||||
|
% number)
|
||||||
|
else:
|
||||||
|
number_value = rhs
|
||||||
|
kwargs['number'] = number_value
|
||||||
|
return func(**kwargs) % rhs
|
||||||
|
|
||||||
|
proxy = lazy(lambda **kwargs: NumberAwareString(), NumberAwareString)(**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):
|
def activate(language):
|
||||||
return _trans.activate(language)
|
return _trans.activate(language)
|
||||||
|
|
|
@ -35,6 +35,10 @@ Minor features
|
||||||
:class:`~django.forms.URLField` use the new type attributes available in
|
:class:`~django.forms.URLField` use the new type attributes available in
|
||||||
HTML5 (type='email', type='url').
|
HTML5 (type='email', type='url').
|
||||||
|
|
||||||
|
* The ``number`` argument for :ref:`lazy plural translations
|
||||||
|
<lazy-plural-translations>` can be provided at translation time rather than
|
||||||
|
at definition time.
|
||||||
|
|
||||||
Backwards incompatible changes in 1.6
|
Backwards incompatible changes in 1.6
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
|
@ -414,6 +414,41 @@ 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
|
(so that the correct locale is in effect). This necessitates the use of the
|
||||||
helper function described next.
|
helper function described next.
|
||||||
|
|
||||||
|
.. _lazy-plural-translations:
|
||||||
|
|
||||||
|
Lazy translations and plural
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
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 key name instead of an
|
||||||
|
integer as the ``number`` argument. Then ``number`` will be looked up in the
|
||||||
|
dictionary under that key during string interpolation. Here's example::
|
||||||
|
|
||||||
|
class MyForm(forms.Form):
|
||||||
|
error_message = ungettext_lazy("You only provided %(num)d argument",
|
||||||
|
"You only provided %(num)d arguments", 'num')
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
# ...
|
||||||
|
if error:
|
||||||
|
raise forms.ValidationError(self.error_message % {'num': number})
|
||||||
|
|
||||||
|
If the string contains exactly one unnamed placeholder, you can interpolate
|
||||||
|
directly with the ``number`` argument::
|
||||||
|
|
||||||
|
class MyForm(forms.Form):
|
||||||
|
error_message = ungettext_lazy("You provided %d argument",
|
||||||
|
"You provided %d arguments")
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
# ...
|
||||||
|
if error:
|
||||||
|
raise forms.ValidationError(self.error_message % number)
|
||||||
|
|
||||||
|
|
||||||
Joining strings: string_concat()
|
Joining strings: string_concat()
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -41,6 +41,32 @@ msgid_plural "%d results"
|
||||||
msgstr[0] "%d Resultat"
|
msgstr[0] "%d Resultat"
|
||||||
msgstr[1] "%d Resultate"
|
msgstr[1] "%d Resultate"
|
||||||
|
|
||||||
|
#: 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
|
||||||
|
msgctxt "Exclamation"
|
||||||
|
msgid "%d good result"
|
||||||
|
msgid_plural "%d good results"
|
||||||
|
msgstr[0] "%d gutes Resultat!"
|
||||||
|
msgstr[1] "%d guten Resultate!"
|
||||||
|
|
||||||
|
#: models.py:11
|
||||||
|
msgid "Hi %(name)s, %(num)d good result"
|
||||||
|
msgid_plural "Hi %(name)s, %(num)d good results"
|
||||||
|
msgstr[0] "Hallo %(name)s, %(num)d gutes Resultat"
|
||||||
|
msgstr[1] "Hallo %(name)s, %(num)d guten Resultate"
|
||||||
|
|
||||||
|
#: models.py:11
|
||||||
|
msgctxt "Greeting"
|
||||||
|
msgid "Hi %(name)s, %(num)d good result"
|
||||||
|
msgid_plural "Hi %(name)s, %(num)d good results"
|
||||||
|
msgstr[0] "Willkommen %(name)s, %(num)d gutes Resultat"
|
||||||
|
msgstr[1] "Willkommen %(name)s, %(num)d guten Resultate"
|
||||||
|
|
||||||
#: models.py:13
|
#: models.py:13
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The result was %(percent)s%%"
|
msgid "The result was %(percent)s%%"
|
||||||
|
|
|
@ -22,10 +22,15 @@ from django.utils._os import upath
|
||||||
from django.utils.safestring import mark_safe, SafeBytes, SafeString, SafeText
|
from django.utils.safestring import mark_safe, SafeBytes, SafeString, SafeText
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.six import PY3
|
from django.utils.six import PY3
|
||||||
from django.utils.translation import (ugettext, ugettext_lazy, activate,
|
from django.utils.translation import (activate, deactivate,
|
||||||
deactivate, gettext_lazy, pgettext, npgettext, to_locale,
|
get_language, get_language_from_request, get_language_info,
|
||||||
get_language_info, get_language, get_language_from_request, trans_real)
|
to_locale, trans_real,
|
||||||
|
gettext, gettext_lazy,
|
||||||
|
ugettext, ugettext_lazy,
|
||||||
|
ngettext, ngettext_lazy,
|
||||||
|
ungettext, ungettext_lazy,
|
||||||
|
pgettext, pgettext_lazy,
|
||||||
|
npgettext, npgettext_lazy)
|
||||||
|
|
||||||
from .commands.tests import can_run_extraction_tests, can_run_compilation_tests
|
from .commands.tests import can_run_extraction_tests, can_run_compilation_tests
|
||||||
if can_run_extraction_tests:
|
if can_run_extraction_tests:
|
||||||
|
@ -95,6 +100,42 @@ class TranslationTests(TestCase):
|
||||||
s2 = pickle.loads(pickle.dumps(s1))
|
s2 = pickle.loads(pickle.dumps(s1))
|
||||||
self.assertEqual(six.text_type(s2), "test")
|
self.assertEqual(six.text_type(s2), "test")
|
||||||
|
|
||||||
|
@override_settings(LOCALE_PATHS=extended_locale_paths)
|
||||||
|
def test_ungettext_lazy(self):
|
||||||
|
s0 = ungettext_lazy("%d good result", "%d good results")
|
||||||
|
s1 = ngettext_lazy(str("%d good result"), str("%d good results"))
|
||||||
|
s2 = npgettext_lazy('Exclamation', '%d good result', '%d good results')
|
||||||
|
with translation.override('de'):
|
||||||
|
self.assertEqual(s0 % 1, "1 gutes Resultat")
|
||||||
|
self.assertEqual(s0 % 4, "4 guten Resultate")
|
||||||
|
self.assertEqual(s1 % 1, str("1 gutes Resultat"))
|
||||||
|
self.assertEqual(s1 % 4, str("4 guten Resultate"))
|
||||||
|
self.assertEqual(s2 % 1, "1 gutes Resultat!")
|
||||||
|
self.assertEqual(s2 % 4, "4 guten Resultate!")
|
||||||
|
|
||||||
|
s3 = ungettext_lazy("Hi %(name)s, %(num)d good result", "Hi %(name)s, %(num)d good results", 4)
|
||||||
|
s4 = ungettext_lazy("Hi %(name)s, %(num)d good result", "Hi %(name)s, %(num)d good results", 'num')
|
||||||
|
s5 = ngettext_lazy(str("Hi %(name)s, %(num)d good result"), str("Hi %(name)s, %(num)d good results"), 4)
|
||||||
|
s6 = ngettext_lazy(str("Hi %(name)s, %(num)d good result"), str("Hi %(name)s, %(num)d good results"), 'num')
|
||||||
|
s7 = npgettext_lazy('Greeting', "Hi %(name)s, %(num)d good result", "Hi %(name)s, %(num)d good results", 4)
|
||||||
|
s8 = npgettext_lazy('Greeting', "Hi %(name)s, %(num)d good result", "Hi %(name)s, %(num)d good results", 'num')
|
||||||
|
with translation.override('de'):
|
||||||
|
self.assertEqual(s3 % {'num': 4, 'name': 'Jim'}, "Hallo Jim, 4 guten Resultate")
|
||||||
|
self.assertEqual(s4 % {'name': 'Jim', 'num': 1}, "Hallo Jim, 1 gutes Resultat")
|
||||||
|
self.assertEqual(s4 % {'name': 'Jim', 'num': 5}, "Hallo Jim, 5 guten Resultate")
|
||||||
|
with six.assertRaisesRegex(self, KeyError, 'Your dictionary lacks key.*'):
|
||||||
|
s4 % {'name': 'Jim'}
|
||||||
|
self.assertEqual(s5 % {'num': 4, 'name': 'Jim'}, str("Hallo Jim, 4 guten Resultate"))
|
||||||
|
self.assertEqual(s6 % {'name': 'Jim', 'num': 1}, str("Hallo Jim, 1 gutes Resultat"))
|
||||||
|
self.assertEqual(s6 % {'name': 'Jim', 'num': 5}, str("Hallo Jim, 5 guten Resultate"))
|
||||||
|
with six.assertRaisesRegex(self, KeyError, 'Your dictionary lacks key.*'):
|
||||||
|
s6 % {'name': 'Jim'}
|
||||||
|
self.assertEqual(s7 % {'num': 4, 'name': 'Jim'}, "Willkommen Jim, 4 guten Resultate")
|
||||||
|
self.assertEqual(s8 % {'name': 'Jim', 'num': 1}, "Willkommen Jim, 1 gutes Resultat")
|
||||||
|
self.assertEqual(s8 % {'name': 'Jim', 'num': 5}, "Willkommen Jim, 5 guten Resultate")
|
||||||
|
with six.assertRaisesRegex(self, KeyError, 'Your dictionary lacks key.*'):
|
||||||
|
s8 % {'name': 'Jim'}
|
||||||
|
|
||||||
@override_settings(LOCALE_PATHS=extended_locale_paths)
|
@override_settings(LOCALE_PATHS=extended_locale_paths)
|
||||||
def test_pgettext(self):
|
def test_pgettext(self):
|
||||||
trans_real._active = local()
|
trans_real._active = local()
|
||||||
|
|
Loading…
Reference in New Issue