5
votes

In my Django v1.6.5 project running on Python v2.7.x, I have a Model that returns its configuration as a string. I need the returned string to be a gettext_lazy object, so I can evaluate it in any language required later.

from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _, string_concat
...

class MyModel(models.Model):

    key = models.CharField(...)
    value = models.CharField(...)

    @property
    def config_str(self):
        return _('some configuration')

This seems to work fine in these scenarios:

  1. Static string: (see above) - works!
  2. String concatenation: return string_concat(self.key, _(' equals '), self.value) - works!

What is not working, is using gettext_lazy with placeholders, a la:

return _('“%(key)s” equals “%(value)s”' % {key: self.key, value: self.value})

or using the .format() mechanism:

return _('“{key}” equals “{value}”').format(key=self.key, value=self.value)

When I do this, my .po file does contain:

#, python-format
msgid "“%(key)s” equals “%(value)s”" or
msgid "«{key}» equals «{value}»"

but even when I populate this Eg.:

msgstr "«%(key)s» est égal à «%(value)s»" or
msgstr "«{key}» est égal à «{value}»"

and I run compilemessages, the translation seems to be ignored. When I translate the promise returned by the model instance, I always get an EN string with the placeholders filled E.g., '“foo” equals “bar”'. Note, I get an EN string even when the first calling context is FR (for example). This tells me that the translations just aren't even occurring. It is my theory that by the time I eval the lazy object, gettext is looking for the literal string "“foo” equals “bar”" (for example) in the translation catalog rather than something with placeholders and named values.

With this in mind, I've also tried wrapping the whole format() in the lazy object like this:

return _('“{key}” equals “{value}”'.format(key=self.key, value=self.value))

But it seems to have made zero difference. =/

I can get by with string_concat() for now, but sometimes, the placeholders will need to be moved around in some translations, so I'd like to figure this out.

I'm beginning to think that one simply cannot use placeholders with gettext_lazy.

NOTE: I have reviewed django: Translation with variables inside, but a) that has no accepted answer and b) he's using gettext, not gettext_lazy.

2

2 Answers

2
votes

OK, the solution here is to provide an extra layer of laziness (Thanks, Django core dev: Florian Apolloner AKA “apollo13”).

Here's my modified function that WORKS:

from django.utils import six
from django.utils.functional import lazy

class MyModel(models.Model):

    key = models.CharField(...)
    value = models.CharField(...)

    @property
    def configuration_string(self):

        def wrapper():
            return _('“{key}” equals “{value}”').format(
                key=self.key,
                value=self.value
            )

        return lazy(
            wrapper,
            six.text_type
        )

The only thing is, where I use this, I must remember to eval the wrapper function as follows:

from django.utils.encoding import force_text

config = my_model_instance.configuration_string
# NOTE: Evaluate the lazy function wrapper inside the force_text()
config_str = force_text(config())

Now, in my case, I need to support cases where 3rd party devs write the function configuration_string returning either the lazy function wrapper, a lazily evaluated translation string or just a regular string, so I use:

import types
from django.utils.encoding import force_text
from django.functional import Promise

config = my_model_instance.configuration_string

if isinstance(config, types.FunctionType):
    config_str = force_text(config())
elif isinstance(config, Promise):
    config_str = force_text(config)
else:
    config_str = config

Thanks again to Apollo13 for guidance!

0
votes

I had a very similar problem and found that using gettext_noop instead of gettext_lazy worked for me available since Django 1.11.