【问题标题】:Approaches to changing language at runtime with python gettext使用 python gettext 在运行时更改语言的方法
【发布时间】:2021-12-22 16:17:02
【问题描述】:

我已经阅读了很多关于使用 Python gettext 的帖子,但没有一篇涉及在运行时更改语言的问题。

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

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

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

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

可能的方法

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

此问题与模块级变量的最小示例是here

重新评估

手动重新加载模块

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

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

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

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

自动重新加载模块

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

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

只重新评估所需的对象?

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

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

延迟评估

懒惰评估

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

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

延迟翻译

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

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

重构

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

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

【问题讨论】:

    标签: python architecture internationalization gettext


    【解决方案1】:

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

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

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

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

    唯一可能的方法是 super-LazyString,它通过执行诸如 + 之类的操作以返回对最终应用进行编码的转换的对象,但无法知道 何时 强制执行这些操作,除非您控制所有用于显示或传输字符串的机制。也无法推迟过去,例如if len(_("…"))>80:

    【讨论】:

    • 感谢您的详细回答。对于我的用例,我将继续结合重构和重新加载,其中每个都最有意义。有趣的是,gettext 是标准,尽管有这些限制。
    • @em_ly:gettext 并不是真的有限制:它的默认行为鼓励急切地执行翻译,但人们可能会认为这很常见,因为它简单且与所有需要字符串的东西兼容。
    猜你喜欢
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 2023-03-04
    • 1970-01-01
    • 2017-04-02
    • 2012-10-22
    • 1970-01-01
    相关资源
    最近更新 更多