I have read lots of posts about using Python gettext
, but none of them addressed the issue of changing languages at runtime.
Using gettext
, strings are translated by the function _()
which is added globally to builtins
. The definition of _
is language-specific and will change during execution when the language setting changes. At certain points in the code, I need strings in an object to be translated to a certain language. This happens by:
- (Re)define the
_
function inbuiltins
to translate to the chosen language - (Re)evaluate the desired object using the new
_
function - guaranteeing that any calls to_
within the object definition are evaluated using the current definition of_
. - Return the object
I am wondering about different approaches to step 2. I thought of several but they all seem to have fundamental flaws.
- What is the best way to achieve step 2 in practice?
- Is it theoretically possible to achieve step 2 for any arbitrary object, without knowledge of its implementation?
Possible approaches
If all translated text is defined in functions that can be called in step 2, then it's straightforward: calling the function will evaluate using the current definition of _
. But there are lots of situations where that's not the case, for instance, translated strings could be module-level variables evaluated at import time, or attributes evaluated when instantiating an object.
Minimal example of this problem with module-level variables is here.
Re-evaluation
Manually reload modules
Module-level variables can be re-evaluated at the desired time using importlib.reload
. This gets more complicated if the module imports another module that also has translated strings. You have to reload every module that's a (nested) dependency.
With knowledge of the module's implementation, you can manually reload the dependencies in the right order: if A imports B,
importlib.reload(B)
importlib.reload(A)
# use A...
Problems: Requires knowledge of the module's implementation. Only reloads module-level variables.
Automatically reload modules
Without knowledge of the module's implementation, you'd need to automate reloading dependencies in the right order. You could do this for every module in the package, or just the (recursive) dependencies. To handle more complex situations, you'd need to generate a dependency graph and reload modules in breadth-first order from the roots.
Problems: Requires complex reloading algorithm. There are likely edge cases where it's not possible (cyclic dependencies, unusual package structure, from X import Y
-style imports). Only reloads module-level variables.
Re-evaluate only the desired object?
eval
allows you to evaluate dynamically generated expressions. Instead could you re-evaluate an existing object's static expression, given a dynamic context (builtins._
)? I guess this would involve recursively re-evaluating the object, and every object referenced in its definition, and every object referenced in their definitions...
I looked through the inspect
module and didn't find any obvious solution.
Problems: Not sure if this is possible. Security issues with eval
and similar.
Delayed evaluation
Lazy evaluation
The Flask-Babel project provides a LazyString that delays evaluation of a translated string. If it could be completely delayed until step 2, that seems like the cleanest solution.
Problems: A LazyString
can still get evaluated before it's supposed to. Lots of things may call its __str__
function and trigger evaluation, such as string formatting and concatenating.
Deferred translation
The python gettext docs demonstrate temporarily re-defining the _
function, and only calling the actual translation function when the translated string is needed.
Problems: Requires knowledge of the object's structure, and code customized to each object, to find the strings to translate. Doesn't allow concatenation or formatting of translated strings.
Refactoring
All translated strings could be factored out into a separate module, or moved to functions such that they can be completely evaluated at a given time.
Problems: As I understand it the point of gettext
and the global _
function is to minimize the impact of translation on existing code. Refactoring like this could require significant design changes and make the code more confusing.