使用 python gettext 在运行时更改语言的方法

Approaches to changing language at runtime with python gettext

我已经阅读了很多关于使用 Python gettext 的文章,但是其中 none 解决了在运行时更改语言的问题。

使用 gettext,字符串由函数 _() 翻译,该函数被全局添加到 builtins_ 的定义是特定于语言的,并且会在语言设置更改时在执行过程中更改。在代码的某些位置,我需要将对象中的 字符串翻译成某种语言 。这发生在:

  1. (重新)定义 builtins 中的 _ 函数以翻译成所选语言
  2. (重新)使用新的 _ 函数评估所需的对象 - 保证 在对象定义中对 _ 的任何调用都使用 [=] 的当前定义进行评估16=].
  3. Return对象

我想知道第 2 步的不同方法。我想到了几种,但它们似乎都有根本缺陷。

可能的方法

如果所有翻译的文本都定义在可以在步骤 2 中调用的函数中,那么它很简单:调用函数将使用 _ 的当前定义进行计算。但在很多情况下情况并非如此,例如,翻译后的字符串可能是在导入时评估的模块级变量,或者是在实例化对象时评估的属性。

这个模块级变量问题的最小例子是here

重新评估

手动重新加载模块

可以使用 importlib.reload 在需要的时间重新评估模块级变量。如果模块导入另一个也有翻译字符串的模块,这会变得更加复杂。您必须重新加载作为(嵌套)依赖项的每个模块。

了解模块的实现后,您可以按正确的顺序手动重新加载依赖项:如果 A 导入 B,

importlib.reload(B)
importlib.reload(A)
# use A...

问题:需要了解模块的实现。仅重新加载模块级变量。

自动重新加载模块

在不了解模块实现的情况下,您需要以正确的顺序自动重新加载依赖项。您可以对包中的每个模块执行此操作,或 just the (recursive) dependencies。要处理更复杂的情况,您需要生成依赖关系图并从根部以广度优先顺序重新加载模块。

问题:需要复杂的重新加载算法。可能存在不可能的边缘情况(循环依赖、不寻常的包结构、from X import Y 样式的导入)。仅重新加载模块级变量。

只重新评估所需的对象?

eval 允许您评估动态生成的表达式。相反,您能否在给定动态上下文 (builtins._) 的情况下重新评估现有对象的静态表达式?我想这将涉及递归地重新评估对象,以及其定义中引用的每个对象,以及它们定义中引用的每个对象...... 我查看了 inspect 模块,没有找到任何明显的解决方案。

问题:不确定这是否可行。 eval 和类似的安全问题。

延迟评估

惰性评估

Flask-Babel 项目提供了一个 LazyString 来延迟对已翻译字符串的评估。如果它可以完全延迟到第 2 步,那似乎是最干净的解决方案。

问题: LazyString 仍然可以在预期之前得到评估。许多事情可能会调用其 __str__ 函数并触发评估,例如字符串格式化和连接。

延迟翻译

python gettext docs演示临时重新定义_函数,只在需要翻译字符串时才调用实际的翻译函数。

问题: 需要了解对象的结构和为每个对象定制的代码,才能找到要翻译的字符串。不允许连接或格式化已翻译的字符串。

重构

所有翻译的字符串都可以分解到一个单独的模块中,或者移动到函数中,以便在给定的时间对它们进行完整的评估。

问题: 据我了解,gettext 和全局 _ 函数的要点是尽量减少翻译对现有代码的影响。像这样的重构可能需要重大的设计更改并使代码更加混乱。

唯一合理的通用方法是 重写 所有相关代码,不仅使用 _ 请求翻译,而且从不缓存结果。这不是一个有趣的想法,也不是一个新想法——你已经列出了依赖于 gettext 客户合作的重构和延迟翻译——但它 “最好的方法[…] 在实践中。

您可以尝试通过从 sys.modules 中删除许多内容然后进行真正的重新导入来实现超级 reload。这种方法避免了理解导入关系,但只有在相关模块都写在 Python 中并且你可以保证你的程序的状态不会保留任何 references 时才有效使用旧语言的对象(包括类型和模块)。 (我已经这样做了,但只是在总体程序是一种对废弃模块的特性完全不感兴趣的主管的情况下。)

您可以尝试遍历整个对象图并替换字符串,但即使抛开这种算法的内在技术难度(考虑__slots__ 类 和 co_consts 只是最温和的口味),它将涉及 取消翻译 它们,当已经执行某种转换时,这会从困难变为不可能。这种转换可能只是连接翻译后的字符串,或者它可能是预先替换已知值来格式化,或者填充字符串,或者存储它的散列:一般来说它肯定是不可判定的。 (我对其他数据类型也这样做过,但仅限于由文件 reader 构造的数据,其输出使用已知的简单结构。)

任何基于部分重新评估的方法都结合了上述方法的问题。

唯一可能的方法是超级LazyString,它拒绝通过实施像 + 到 return 对象这样的操作来更长时间地转换,这些对象编码转换以最终应用,但是除非您控制所有用于显示或传输字符串的机制,否则不可能知道 何时 强制执行这些操作。推迟过去也是不可能的,比如 if len(_("…"))>80:.