python - 使用 python gettext 在运行时更改语言的方法
问题描述
我已经阅读了很多关于使用 Python 的帖子gettext
,但没有一篇涉及在运行时更改语言的问题。
使用,字符串由全局添加到gettext
的函数翻译。的定义是特定于语言的,并且会在语言设置更改时在执行期间更改。在代码中的某些点,我需要将对象中的字符串翻译成某种语言。这通过以下方式发生:_()
builtins
_
- (重新)定义
_
函数builtins
以翻译为所选语言 - 使用新函数(重新)评估所需的对象
_
- 保证对象定义中的任何调用都使用._
_
- 返回对象
我想知道第 2 步的不同方法。我想到了几个,但它们似乎都有根本缺陷。
- 在实践中实现第 2 步的最佳方法是什么?
- 理论上是否可以在不了解其实现的情况下为任意对象实现步骤 2?
可能的方法
如果所有翻译文本都定义在可以在步骤 2 中调用的函数中,那么它很简单:调用该函数将使用_
. 但在很多情况下并非如此,例如,翻译后的字符串可能是在导入时评估的模块级变量,或者是在实例化对象时评估的属性。
模块级变量的这个问题的最小例子是here。
重新评估
手动重新加载模块
可以在所需时间使用 重新评估模块级变量importlib.reload
。如果模块导入另一个也具有翻译字符串的模块,这会变得更加复杂。您必须重新加载每个(嵌套)依赖项的模块。
了解模块的实现后,您可以按照正确的顺序手动重新加载依赖项:如果 A 导入 B,
importlib.reload(B)
importlib.reload(A)
# use A...
问题:需要了解模块的实现。仅重新加载模块级变量。
自动重新加载模块
在不了解模块实现的情况下,您需要以正确的顺序自动重新加载依赖项。您可以对包中的每个模块执行此操作,或者仅对(递归)依赖项执行此操作。要处理更复杂的情况,您需要生成依赖关系图并从根以广度优先顺序重新加载模块。
问题:需要复杂的重新加载算法。可能存在不可能的边缘情况(循环依赖、不寻常的包结构、from X import Y
-style 导入)。仅重新加载模块级变量。
只重新评估所需的对象?
eval
允许您评估动态生成的表达式。相反,您能否在给定动态上下文 ( ) 的情况下重新评估现有对象的静态表达式builtins._
?我想这将涉及递归地重新评估对象,以及在其定义中引用的每个对象,以及在其定义中引用的每个对象......我查看了inspect
模块并没有找到任何明显的解决方案。
问题:不确定这是否可能。eval
和类似的安全问题。
延迟评估
懒惰的评价
Flask-Babel 项目提供了延迟对已翻译字符串进行评估的LazyString。如果它可以完全延迟到第 2 步,那似乎是最干净的解决方案。
问题: ALazyString
仍然可以在它应该被评估之前得到评估。很多东西都可以调用它的__str__
函数和触发求值,比如字符串格式化和连接。
延期翻译
python gettext 文档演示了临时重新定义该函数_
,并且仅在需要翻译的字符串时才调用实际的翻译函数。
问题:需要了解对象的结构和为每个对象定制的代码,才能找到要翻译的字符串。不允许连接或格式化翻译的字符串。
重构
所有翻译后的字符串都可以分解到一个单独的模块中,或者移动到函数中,以便在给定时间对它们进行完全评估。
问题:据我了解,gettext
全局_
功能的重点是尽量减少翻译对现有代码的影响。像这样的重构可能需要重大的设计更改并使代码更加混乱。
解决方案
唯一可行的通用方法是重写所有相关代码,不仅用于_
请求翻译,而且从不缓存结果。这不是一个有趣的想法,也不是一个新想法——您已经列出了依赖于gettext
客户合作的重构和延迟翻译——但它是“实践中的最佳方式 [...]”。
您可以尝试reload
通过从中删除许多内容sys.modules
然后进行真正的重新导入来进行超级操作。这种方法避免了理解导入关系,但只有在相关模块全部用 Python 编写并且您可以保证程序的状态不会保留对使用旧语言的任何对象(包括类型和模块)的引用时才有效。(我已经这样做了,但只是在总体程序是一种对废弃模块的特性完全不感兴趣的主管的情况下。)
您可以尝试遍历整个对象图并替换字符串,但即使不考虑这种算法的内在技术难度(考虑__slots__
基类并且co_consts
只是为了最温和的口味),它也会涉及不翻译它们,这从难变为当已经执行了某种转换时是不可能的。这种转换可能只是连接翻译后的字符串,或者它可能是预先替换已知值来格式化,或者填充字符串,或者存储它的哈希值:通常它肯定是不确定的。(对于其他数据类型,我也这样做过,但仅限于由文件读取器构造的数据,其输出使用已知的简单结构。)
任何基于部分重新评估的方法都结合了上述方法的问题。
唯一可能的方法是 super-通过执行操作(例如返回编码转换以最终应用的对象)LazyString
来拒绝转换更长时间,但是除非您控制用于显示或传输的所有机制,否则不可能知道何时强制执行这些操作字符串。也不可能推迟过去,比如说,。+
if len(_("…"))>80: