49

Previously you would use gettext as following:

_('Hey {},').format(username)

but what about new Python's f-string?

f'Hey {username}'
mdargacz
  • 1,267
  • 18
  • 32
  • 3
    ha this isn't going to work, you need to preserve the original Hey {} string. – Jean-François Fabre Apr 12 '18 at 13:23
  • 2
    `_(f'Hey {username}')` is equivalent to `_('Hey {},'.format(username))`. If that's not acceptable then just use your first method. There's nothing wrong with it and it's still around for this exact reason (delayed formatting). – FHTMitchell Apr 12 '18 at 13:24
  • 2
    @Jean-François Fabre I thought so.. I was hoping for some sneaky way around it – mdargacz Apr 12 '18 at 13:25
  • 1
    This is now officially documented as ["Since string extraction is done by the xgettext command, only syntaxes supported by gettext are supported by Django. In particular, Python f-strings are not yet supported by xgettext, and JavaScript template strings need gettext 0.21+."](https://docs.djangoproject.com/en/3.2/topics/i18n/translation/#standard-translation) – gdvalderrama Mar 08 '21 at 15:18

2 Answers2

28

'Hey {},' is contained in your translation dictionary as is.

If you use f'Hey {username},', that creates another string, which won't be translated.

In that case, the format method remains the only one useable, but you could approach the f-string features by using named parameters

_('Hey {username},').format(username=username)

or if you have a dictionary containing your data, this cool trick where format picks the required information in the input dictionary:

d = {"username":"John", "city":"New York", "unused":"doesn't matter"}

_('Hey {username} from {city},').format(**d)
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
24

My solution is to make a function f() which performs the f-string interpolation after gettext has been called.

from copy import copy
from inspect import currentframe

def f(s):
    frame = currentframe().f_back
    kwargs = copy(frame.f_globals)
    kwargs.update(frame.f_locals)
    return eval(s.format(**kwargs))

Now you just wrap _(...) in f() and don’t preface the string with an f:

f(_('Hey, {username}'))

Note of caution

I’m usually against the use of eval as it could make the function potentially unsafe, but I personally think it should be justified here, so long as you’re aware of what’s being formatted. That said use at your own risk.

Remember

This isn’t a perfect solution, this is just my solution. As per PEP 498 states each formatting method “have their advantages, but in addition have disadvantages” including this.

For example if you need to change the expression inside the string then it will no longer match, therefore not be translated unless you also update your .po file as well. Also if you’re not the one translating them and you use an expression that’s hard to decipher what the outcome will be then that can cause miscommunication or other issues in translation.

Jab
  • 26,853
  • 21
  • 75
  • 114
  • You don't really need to use f-string format inside your `f` function. Just use `s.format(**kwargs)` where kwargs is: `from copy import copy kwargs = copy(frame.f_globals) kwargs.update(frame.f_locals)` – egvo Aug 10 '21 at 08:40
  • 1
    @egvo I'll be honest I read your comment months ago and wrote it off as just another way to do it. But I just revisited and it is indeed actually faster than the `f-string` approach I used. – Jab Jan 07 '22 at 16:58
  • 3
    This is really bugging me, using your method works, but it leaves "unused" variables in the code since pylint cant tell you have used them. Ideally, f-string implementation should add an attribute `def __unformatted(self): return "blah {x}"` that gettext would use as the extracted string, and then also when formatted back into the run. – miigotu Apr 03 '22 at 15:10
  • There should also be functionality where the string is reformatted after the embedded variable changes. `x = "foo"; y = f"{x}"; print(y); "foo"; x = "bar"; print(y); "bar"` – miigotu Apr 03 '22 at 15:17