Skip to content

Instantly share code, notes, and snippets.

@ewjoachim
Last active April 24, 2020 16:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ewjoachim/d55d9b7dc55d8120ede3a38fc6cc6f36 to your computer and use it in GitHub Desktop.
Save ewjoachim/d55d9b7dc55d8120ede3a38fc6cc6f36 to your computer and use it in GitHub Desktop.
Workable gettext
# workalendar/i18n.py
import gettext
import collections
_ = lambda x: x
def get_translation_for_language(language: str) -> gettext.Translations:
return gettext.translation("mydomain", "/path/to/translations", [language])
def gettext(message:str) -> str:
return translations[get_current_language()].gettext(message)
def ngettext(count: int, message: str) -> str:
return translations[get_current_language()].ngettext(count, message)
def pgettext(context: str, message: str) -> str:
# 3.8+ only :o
return translations[get_current_language()].pgettext(context, message)
...
def get_current_language() -> str:
# Do some magic: read a module variable or a threadlocal or a ContextVar or whatever. Your business.
def set_current_translation(language: str) -> None:
# Do some magic: write a module variable or a threadlocal or a ContextVar or whatever. Your business.
# a.k.a translation.activate
@contextlib.contextmanager
def override_language(language: str):
prev = get_current_language()
set_current_language(language)
try:
yield
finally:
set_current_language(prev)
translations = collections.defaultdict(get_translation_for_language)
# workalendar/elsewhere.py
from workalendar.i18n import _, translate
holiday_name = _("Pâques")
def easter_name():
set_current_language("fr")
return gettext(holiday_name)
#also works with
with translation_override("de"):
gettext("Pâques")
# now back to french
# workalendar/i18n.py
import gettext
import collections
_ = lambda x: x
def get_translation_for_language(language: str) -> gettext.Translations:
return gettext.translation("mydomain", "/path/to/translations", [language])
def translate(language: str, message:str) -> str:
return translations[language].gettext(message)
def translate_count(language: str, count: int, message: str) -> str:
return translations[language].ngettext(count, message)
def translate_context(language: str, context: str, message: str) -> str:
# 3.8+ only :o
return translations[language].pgettext(context, message)
translations = collections.defaultdict(get_translation_for_language)
# workalendar/elsewhere.py
from workalendar.i18n import _, translate
holiday_name = _("Pâques")
def easter_name(language):
return translate(language, holiday_name)
# or
return translate(language, _("Pâques"))
@ewjoachim
Copy link
Author

note the following:

  • You want to have gettext("string literals to translate") or _("string literals to translate") to appear somewhere in your code (for extraction purposes), but the translation should not occur at import time, which means either you'll need a gettext_lazy like django, or _() to be a no-op, what I did here
  • Without global, you have to pass the language (that's kind of logic). This means the call to gettext cannot have the same signature as the original gettext, so I've chosen to give it a different name to make sure extraction is not confused

@brunobord
Copy link

too bad for the defaultdict function. the function that's passed as a parameter doesn't accept arguments. I've found a way to make my own factory, though.
One step at a time...

@ewjoachim
Copy link
Author

Ah snap what I meant was actually a lru_cache ! If you lru_cache get_translation_for_language, then it would work (and you don't need the translation object anymore)

@brunobord
Copy link

possibly. I think it'd be too early for caching stuff and such. What I'm trying to do at least (with a bit of success, right now, but it's late and I'm tired) is to get translations when language is known. It worked. I'm glad.

Premature optimisation is the root of all evil.

@ewjoachim
Copy link
Author

Hm, I think gettext.translation("mydomain", "/path/to/translations", [language]) is really not meant to be called multiple times...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment