I am looking fo a way to set the language on the fly when requesting a translation for a string in gettext. I'll explain why :
I have a multithreaded bot that respond to users by text on multiple servers, thus needing to reply in different languages. The documentation of gettext states that, to change locale while running, you should do the following :
import gettext # first, import gettext
lang1 = gettext.translation('myapplication', languages=['en']) # Load every translations
lang2 = gettext.translation('myapplication', languages=['fr'])
lang3 = gettext.translation('myapplication', languages=['de'])
# start by using language1
lang1.install()
# ... time goes by, user selects language 2
lang2.install()
# ... more time goes by, user selects language 3
lang3.install()
But, this does not apply in my case, as the bot is multithreaded :
Imagine the 2 following snippets are running at the same time :
import time
import gettext
lang1 = gettext.translation('myapplication', languages=['fr'])
lang1.install()
message(_("Loading a dummy task")) # This should be in french, and it will
time.sleep(10)
message(_("Finished loading")) # This should be in french too, but it wont :'(
and
import time
import gettext
lang = gettext.translation('myapplication', languages=['en'])
time.sleep(3) # Not requested on the same time
lang.install()
message(_("Loading a dummy task")) # This should be in english, and it will
time.sleep(10)
message(_("Finished loading")) # This should be in english too, and it will
You can see that messages sometimes are translated in the wrong locale.
But, if I could do something like _("string", lang="FR")
, the problem would disappear !
Have I missed something, or I'm using the wrong module to do the task... I'm using python3
While the above solutions seem to work, they don’t play well with the conventional _()
function that aliases gettext(). But I wanted to keep that function, because it’s used to extract translation strings from the source (see docs or e.g. this blog).
Because my module runs in a multi-process and multi-threaded environment, using the application’s built-in namespace or a module’s global namespace wouldn’t work because _()
would be a shared resource and subject to race conditions if multiple threads install different translations.
So, first I wrote a short helper function that returns a translation closure:
import gettext
def get_translator(lang: str = "en"):
trans = gettext.translation("foo", localedir="/path/to/locale", languages=(lang,))
return trans.gettext
And then, in functions that use translated strings I assigned that translation closure to the _
, thus making it the desired function _()
in the local scope of my function without polluting a global shared namespace:
def some_function(...):
_ = get_translator() # Pass whatever language is needed.
log.info(_("A translated log message!"))
(Extra brownie points for wrapping the get_translator()
function into a memoizing cache to avoid creating the same closures too many times.)
You can just create translation objects for each language directly from .mo
files:
from babel.support import Translations
def gettext(msg, lang):
return get_translator(lang).gettext(msg)
def get_translator(lang):
with open(f"path_to_{lang}_mo_file", "rb") as fp:
return Translations(fp=fp, domain="name_of_your_domain")
And a dict cache for them can be easily thrown in there too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With