#24257: The trans template tag fails to get a message when there is a % character in the string -------------------------------------+------------------------------------- Reporter: aboudreault | Owner: nobody Type: Bug | Status: new Component: | Version: 1.7 Internationalization | Severity: Normal | Resolution: Keywords: trans templatetag | Triage Stage: Accepted i18n | Has patch: 0 | Needs documentation: 0 Needs tests: 0 | Patch needs improvement: 0 Easy pickings: 0 | UI/UX: 0 -------------------------------------+-------------------------------------
Comment (by beck): This was the ticket I decided to tackle at the pycon sprints (claudep wish you were there so I could thank you in person for your patience on my previous pull... and this one too). This issue turned out to be quite difficult. I've tried multiple approaches. == Long story short == The simplest solution is to ensure that all translation messages that have a percent signs get python formatting. == Long story long == In an ideal world we would be able to use the template string (with it's curly brace variables) as our msgid. Since that is not possible, template copy is coerced into a python formatted / sprintf string. This is a source of much pain and confusion, especially with percent signs. Why? '''xgettext is awkward.''' xgettext will identify all python-format strings, example: {{{ echo 'gettext("%s");' | xgettext --language=python --output=- - #, python-format msgid "%s" msgstr "" }}} This is all good. It gets awkward when you pass it an invalid python fmt string, say: {{{ echo 'gettext("%s costs 10%");' | xgettext --language=python --output=- - msgid "%s costs 10%" msgstr "" }}} This is awkward because the single `%` is seen as invalid python format and so the message id is not marked as we would expect. '''sprintf is awkward.''' Since humans are bad parsers, when I look at the format, `% %s` I see a percent followed by a string specifier. What this actually means a percent sign specifier, with a whitespace conversion flag, followed by an `s` character. Example: {{{ >>> "% %s" % () '%s' >>> "% 10%s" % () ' %s' >>> }}} These two bits awkwardness in gettext and sprinf has caused some confusion as past developers have tried to shoehorn the template language into these technologies. Example: {{{ {% blocktrans with a=1 %}Blocktrans extraction shouldn't double escape this: %%, a={{ a }}{% endblocktrans %} }}} The correct msgid is: {{{ Blocktrans extraction shouldn't double escape this: %%%%, a=%(a)s }}} Not: {{{ Blocktrans extraction shouldn't double escape this: %%, a={{ a }} }}} == The solution == First get some robust tests that capture all corner cases of awkwardness. I've added a sample app to the i18n tests to make it easier to write tests that evaluate extraction and translation using the same gettext catalogs. I then refactored tests addressing extraction and translation when a `%` is involved. Second, knock out any special handling of percents and ensure valid python-formatting on strings. Technically not all template strings with a percent should be python- formatted. If the template was only `{% trans "10%" %}` this could go into the gettext catalog with msgid `10%` but such things is not possible with how `blocktrans` is rendered. When using `trans` and `blocktrans` with the same copy, the two should always extract the same msgid and render identically. The pull: https://github.com/django/django/pull/4549 -- Ticket URL: <https://code.djangoproject.com/ticket/24257#comment:4> Django <https://code.djangoproject.com/> The Web framework for perfectionists with deadlines. -- You received this message because you are subscribed to the Google Groups "Django updates" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-updates+unsubscr...@googlegroups.com. To post to this group, send email to django-updates@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/069.61e3a7b0d598f0fdecef7f67b4328aea%40djangoproject.com. For more options, visit https://groups.google.com/d/optout.