Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django ugettext_lazy, interpolation and ChoiceField

I want a ChoiceField with these choices:

choices = [(1, '1 thing'),
           (2, '2 things'),
           (3, '3 things'),
           ...]

and I want to have it translated.

This does not work:

choices = [(i, ungettext_lazy('%s thing', '%s things', i) % i) for i in range(1,4)]

because as soon as the lazy object is interpolated, it becomes a unicode object - since ChoiceField.choices is evaluated at startup, its choices will be in the language active during Django's startup.

I could use ugettext_lazy('%s things' % i), but that would require a translation for each numeral, which is silly. What is the right way to do this?

like image 807
dgirardi Avatar asked Dec 03 '12 18:12

dgirardi


1 Answers

In the Django documentation, TranslationWorking with lazy translation objects, I see a remark which seems to address your concern here.

Using ugettext_lazy() and ungettext_lazy() to mark strings in models and utility functions is a common operation. When you're working with these objects elsewhere in your code, you should ensure that you don't accidentally convert them to strings, because they should be converted as late as possible (so that the correct locale is in effect). This necessitates the use of the helper function described next.

Then they present django.utils.functional.lazy(func, *resultclasses), which is not presently covered by the django.utils.functional module documentation. However, according to the django.utils.functional.py source code, it "Turns any callable into a lazy evaluated callable.… the function is evaluated on every access."

Modifying their example from Other uses of lazy in delayed translations to incorporate your code, the following code might work for you.

from django.utils import six  # Python 3 compatibility
from django.utils.functional import lazy
from django.utils.safestring import mark_safe

choices = [
    (i, lazy(
        mark_safe(ungettext_lazy('%s thing', '%s things', i) % i),
        six.text_type
    )# lazy()
    for i in range(1,4)
]

Also, the django.utils.functional module documentation does mention a function decorator allow_lazy(func, *resultclasses). This lets you write your own function which takes a lazy string as arguments. "It modifies the function so that if it's called with a lazy translation as the first argument, the function evaluation is delayed until it needs to be converted to a string." lazy(func, *resultclasses) is not a decorator, it modifies a callable.

N.B. I haven't tried this code in Django. I'm just passing along what I found in the documentation. Hopefully it will point you to something you can use.

like image 149
Jim DeLaHunt Avatar answered Sep 20 '22 23:09

Jim DeLaHunt