Opened 4 years ago
Last modified 4 years ago
#32272 new Bug
gettext_lazy inconsistent error when nested
Reported by: | John Bazik | Owned by: | nobody |
---|---|---|---|
Component: | Internationalization | Version: | 3.1 |
Severity: | Normal | Keywords: | |
Cc: | Claude Paroz | Triage Stage: | Accepted |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
When gettext_lazy is called with a lazy object, it returns a nested lazy object which has inconsistent behavior, depending on whether USE_I18N is set or not.
>>> 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.
Change History (4)
comment:2 by , 4 years ago
Cc: | added |
---|
comment:3 by , 4 years ago
Component: | Utilities → Internationalization |
---|---|
Triage Stage: | Unreviewed → Accepted |
comment:4 by , 4 years ago
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.
comment:5 by , 4 years ago
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.
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:Here
gettext_lazy
(and_
) don't actually uselazy
, so I'm not 100% sure how the equivalent line from the report comes up:However, you've hit it in the wild, so I must presume it does 🤔
I wonder if forcing a string from
gettext()
in the thetrans_null
case makes sense?Resolves the reported issue but I don't know if it would have other consequences.