mirror of https://github.com/django/django.git
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
|
||||
elif self._delegate_text:
|
||||
return six.text_type(self) % rhs
|
||||
else:
|
||||
raise AssertionError('__mod__ not supported for non-string types')
|
||||
return self.__cast() % rhs
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
# 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)
|
||||
|
||||
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, 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):
|
||||
return _trans.activate(language)
|
||||
|
|
|
@ -35,6 +35,10 @@ Minor features
|
|||
:class:`~django.forms.URLField` use the new type attributes available in
|
||||
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
|
||||
=====================================
|
||||
|
||||
|
|
|
@ -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
|
||||
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()
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
Binary file not shown.
|
@ -41,6 +41,32 @@ msgid_plural "%d results"
|
|||
msgstr[0] "%d Resultat"
|
||||
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
|
||||
#, python-format
|
||||
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 import six
|
||||
from django.utils.six import PY3
|
||||
from django.utils.translation import (ugettext, ugettext_lazy, activate,
|
||||
deactivate, gettext_lazy, pgettext, npgettext, to_locale,
|
||||
get_language_info, get_language, get_language_from_request, trans_real)
|
||||
|
||||
from django.utils.translation import (activate, deactivate,
|
||||
get_language, get_language_from_request, get_language_info,
|
||||
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
|
||||
if can_run_extraction_tests:
|
||||
|
@ -95,6 +100,42 @@ class TranslationTests(TestCase):
|
|||
s2 = pickle.loads(pickle.dumps(s1))
|
||||
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)
|
||||
def test_pgettext(self):
|
||||
trans_real._active = local()
|
||||
|
|
Loading…
Reference in New Issue