{{{
>>> from django import __version__
>>> print(__version__)
3.1
>>> from django.utils.functional import lazy
>>> from django.utils.translation.trans_real import gettext as
gettext_real
>>> from django.utils.translation.trans_null import gettext as
gettext_noop
>>> gettext_lazy_real = lazy(gettext_real, str)
>>> gettext_lazy_noop = lazy(gettext_noop, str)
>>> r1 = gettext_lazy_real('Real')
>>> r2 = gettext_lazy_real(r1)
>>> n1 = gettext_lazy_noop('Noop')
>>> n2 = gettext_lazy_noop(n1)
>>> print(str(r1))
Real
>>> print(str(r2))
Real
>>> print(str(n1))
Noop
>>> print(str(n2))
Traceback (most recent call last):
File "<console>", line 1, in <module>
TypeError: __str__ returned non-string (type __proxy__)
}}}
I discovered this problem while working with version 2.2.
I've run across this twice now. The first time, I created a pull request
for another project to avoid the nesting. However, now that I've seen it
again, I think that because it works in the case of USE_I18N=True, it's
easy for developers to miss the problem, especially so since testing
USE_I18N has some quirks. Here's my pull request:
https://github.com/django-cms/django-cms/pull/6896
I think the best fix would be to disallow nesting by simply returning
already-nested objects, but I'm not sure of all the subtleties in that
code.
--
Ticket URL: <https://code.djangoproject.com/ticket/32272>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* keywords: => claudep
--
Ticket URL: <https://code.djangoproject.com/ticket/32272#comment:1>
* cc: Claude Paroz (added)
* keywords: claudep =>
--
Ticket URL: <https://code.djangoproject.com/ticket/32272#comment:2>
* component: Utilities => Internationalization
* stage: Unreviewed => Accepted
Comment:
I'll accept this, since there is a difference in behaviour.
Given the code in `trans_null.py`, I'm not quite sure how the issue
arrises:
{{{
def gettext(message):
return message
gettext_noop = gettext_lazy = _ = gettext
}}}
Here `gettext_lazy` (and `_`) don't actually use `lazy`, so I'm not 100%
sure how the equivalent line from the report comes up:
{{{
gettext_lazy_noop = lazy(gettext_noop, str)
}}}
However, you've hit it in the wild, so I must presume it does 🤔
I wonder if forcing a string from `gettext()` in the the `trans_null` case
makes sense?
{{{
diff --git a/django/utils/translation/trans_null.py
b/django/utils/translation/trans_null.py
index a687572b69..f590ff23cd 100644
--- a/django/utils/translation/trans_null.py
+++ b/django/utils/translation/trans_null.py
@@ -6,7 +6,7 @@ from django.conf import settings
def gettext(message):
- return message
+ return str(message)
gettext_noop = gettext_lazy = _ = gettext
}}}
Resolves the reported issue but I don't know if it would have other
consequences.
--
Ticket URL: <https://code.djangoproject.com/ticket/32272#comment:3>
Comment (by John Bazik):
Although {{{gettext_lazy}}} is, as you mention, set in {{{trans_null.py}}}
{{{
gettext_noop = gettext_lazy = _ = gettext
}}}
It is also set in {{{translation/__init__.py}}}, and that's what gets
imported.
{{{
_trans = Trans()
def gettext(message):
return _trans.gettext(message)
gettext_lazy = lazy(gettext, str)
}}}
In {{{class Trans}}}, at the top of that file, {{{_trans.gettext}}} is set
to {{{trans_null.gettext}}}, which is this
{{{
def gettext(message):
return message
}}}
I skipped all that in my example to keep it simple. Coercing the message,
as you suggest, looks like a good solution to me.
--
Ticket URL: <https://code.djangoproject.com/ticket/32272#comment:4>
Comment (by John Bazik):
Here is another solution.
If {{{gettext_lazy}}} was defined in {{{trans_real.py}}} (it is not now),
then it could be set in {{{translation/__init__.py}}} like this
{{{
def gettext_lazy(message):
return _trans.gettext_lazy(message)
}}}
Then {{{lazy}}} would never get called when it isn't needed.
That would require moving some or all of the lazy stuff from
{{{__init__.py}}} to {{{trans_real.py}}} which is more work. And there's
a lot going on in {{{trans_real.py}}} already.
Another possibility is to apply {{{lazy}}} in {{{Trans.__getattr__}}}, in
{{{__init__.py}}}, where USE_I18N is tested.
--
Ticket URL: <https://code.djangoproject.com/ticket/32272#comment:5>