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

em_*_*_ly 5 python architecture gettext internationalization

我读过很多关于使用 Python 的文章gettext,但没有一篇文章解决了运行时更改语言的问题。

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

  1. (重新)定义_函数以builtins翻译为所选语言
  2. 使用新_函数(重新)评估所需的对象 - 保证对对象定义内的任何调用都_使用 的当前定义进行评估_
  3. 返回对象

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

  • 在实践中实现步骤 2 的最佳方法是什么?
  • 理论上是否可以在不了解其实现的情况下对任意对象实现步骤 2?

可能的方法

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

模块级变量问题的最小示例在这里

重新评估

手动重新加载模块

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

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

importlib.reload(B)
importlib.reload(A)
# use A...
Run Code Online (Sandbox Code Playgroud)

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

自动重新加载模块

如果不了解模块的实现,您需要以正确的顺序自动重新加载依赖项。您可以对包中的每个模块或仅对(递归)依赖项执行此操作。为了处理更复杂的情况,您需要生成依赖关系图并从根开始按广度优先顺序重新加载模块。

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

仅重新评估所需的对象?

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

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

延迟评估

惰性评估

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

问题: ALazyString仍然可以在它应该被评估之前被评估。很多事情都可能调用它的__str__函数并触发评估,例如字符串格式化和连接。

延期翻译

python gettext 文档演示了临时重新定义_函数,并且仅在需要翻译的字符串时调用实际的翻译函数。

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

重构

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

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

Dav*_*ing 3

唯一可行的通用方法是重写所有相关代码,不仅用于_请求翻译,而且从不缓存结果。那\xe2\x80\x99s不是一个有趣的想法,它\xe2\x80\x99s不是一个新想法\xe2\x80\x94你已经列出了依赖于客户合作的重构和延迟翻译gettext\xe2\x80\x94但是它实践中\xe2\x80\x9c 的最佳方式[\xe2\x80\xa6]\xe2\x80\x9d。

\n

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

\n

您可以尝试遍历整个对象图并替换字符串,但即使撇开这种算法的固有技术难度(考虑__slots__基类和co_consts最温和的口味),它也将涉及对它们进行反翻译,这从困难变为当某种转换已经执行时,这是不可能的。该转换可能只是连接翻译后的字符串,或者可能预先替换已知值以进行格式化,或者填充字符串,或者存储它的散列: it\xe2\x80\x99s 通常肯定是不可判定的。(我\xe2\x80\x99也对其他数据类型执行了此操作,但仅限于由文件读取器构造的数据,其输出使用已知的简单结构。)

\n

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

\n

唯一的其他可能的方法是一个超级方法LazyString,它通过实现诸如+返回对最终应用的转换进行编码的对象之类的操作来拒绝翻译更长时间,但是\xe2\x80\x99不可能知道何时强制这些操作,除非你控制所有用于显示或传输字符串的机制。它\xe2\x80\x99s 也不可能推迟过去,比如说if len(_("\xe2\x80\xa6"))>80:

\n