Opened 2 years ago

Closed 2 years ago

#19132 closed Cleanup/optimization (fixed)

bug in use of mark_safe and i18n _

Reported by: flagzeta@… Owned by: nobody
Component: Documentation Version: 1.4
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I am using a form which defines this field:

terms = forms.BooleanField(
        error_messages={'required': _('You must accept the terms and conditions')},
        label="",
        help_text=_(mark_safe("I understand and accept the <a href='/terms_and_conditions' target='_blank'>terms of use</a> and <a href='/privacy' target='_blank'>privacy policy</a> of this site."))
    )

unfortunately running "./manage.py makemessages --all" does not seem to pickup the help_text named argument.

---

If i change this to:

terms = forms.BooleanField(
        error_messages={'required': _('You must accept the terms and conditions')},
        label="",
        help_text=mark_safe(_("I understand and accept the <a href='/terms_and_conditions' target='_blank'>terms of use</a> and <a href='/privacy' target='_blank'>privacy policy</a> of this site."))
    )

the message in the html is left untranslated.

Change History (10)

comment:1 Changed 2 years ago by lukeplant

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Resolution set to invalid
  • Status changed from new to closed

You haven't told us what _ is defined as.

I'm guessing are probably using ugettext, and need to read the docs about lazy translation:

https://docs.djangoproject.com/en/dev/topics/i18n/translation/#lazy-translation

comment:2 Changed 2 years ago by claudep

  • Component changed from Translations to Internationalization
  • Resolution invalid deleted
  • Status changed from closed to reopened
  • Triage Stage changed from Unreviewed to Accepted

No, even with ugettext_lazy, this does not work. mark_safe is triggering lazy string translation.

>>> from django.utils.translation import ugettext_lazy
>>> from django.utils.safestring import mark_safe
>>> mark_safe(ugettext_lazy('Home'))
u'Home'
>>> mark_safe(ugettext_lazy('Home')).__class__
<class 'django.utils.safestring.SafeText'>

I don't know currently if we can do something to prevent this, but a temporary workaround would be to dynamically set the help_text property in the __init__ of the form.

comment:3 Changed 2 years ago by lukeplant

Then the reporter presumably needs mark_safe_lazy, which can be created easily using lazy in the same way that ugettext_lazy is created, I imagine. I think altering the behaviour of mark_safe here is likely to be too risky.

comment:4 Changed 2 years ago by claudep

  • Resolution set to invalid
  • Status changed from reopened to closed

Thanks Luke for the tip, this seems to work nicely:

from django.utils.functional import lazy
mark_safe_lazy = lazy(mark_safe, six.text_type)
terms = forms.BooleanField(..., help_text=mark_safe_lazy(_("My lazy string)))

comment:5 Changed 2 years ago by anonymous

Hi, yes i am using ugettext_lazy.

Following your tips i did this fix: (thanks for those)

from django.utils.translation import ugettext, ugettext_lazy as _
from django.utils.safestring import mark_safe

from django.utils.functional import lazy
from types import UnicodeType
mark_safe_lazy = lazy(mark_safe, UnicodeType)
...

 terms = forms.BooleanField(
        error_messages={'required': _('You must accept the terms and conditions')},
        label="",
        help_text=mark_safe_lazy(_("I understand and accept the <a href='/terms_and_conditions' target='_blank'>terms of use</a> and <a href='/privacy' target='_blank'>privacy policy</a> of this site."))
    )
...

This works now. Isn't this worth including in the documentation, or is it too edge case?

comment:6 Changed 2 years ago by lukeplant

Hmm, it's difficult to know where to put in the docs, because there are so many places it could apply - anywhere that you want to use HTML in a class or module level translatable string.

Perhaps we should make a note in the mark_safe docs, and possibly add the mark_safe_lazy function, I'm not sure. If it's worth documenting, it's really not much more work to add the function, and would make the documentation probably smaller overall.

comment:7 Changed 2 years ago by claudep

Maybe here?:

diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index aaf728b..6692c1a 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -427,6 +427,24 @@ In this case, the lazy translations in ``result`` will only be converted to
 strings when ``result`` itself is used in a string (usually at template
 rendering time).
 
+Other uses of lazy in delayed translations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For any other case where you would like to delay the translation, but have to
+pass the translatable string as argument to another function, you can wrap
+this function inside a lazy call yourself. For example::
+
+    from django.utils import six  # Python 3 compatibility
+    from django.utils.functional import lazy
+    from django.utils.safestring import mark_safe
+    from django.utils.translation import ugettext_lazy as _
+
+    mark_safe_lazy = lazy(mark_safe, six.text_type) 
+
+And then later::
+
+    lazy_string = mark_safe_lazy(_("<p>My <strong>string!</strong></p>"))
+

comment:8 Changed 2 years ago by lukeplant

LGTM, and as docs only fix it avoids the problem that we are technically in feature freeze for trunk.

comment:9 Changed 2 years ago by claudep

  • Component changed from Internationalization to Documentation
  • Resolution invalid deleted
  • Status changed from closed to reopened
  • Type changed from Bug to Cleanup/optimization

comment:10 Changed 2 years ago by Claude Paroz <claude@…>

  • Resolution set to fixed
  • Status changed from reopened to closed

In 0775ab295566ccb306b8ae6340d2690c3d0aa6af:

Fixed #19132 -- Added example for creating custom lazy function

Thanks flagzeta@… for the report and Luke Plant for his
expert assistance.

Note: See TracTickets for help on using tickets.
Back to Top