如何同时使用多个 .mo 文件进行 gettext 翻译?

8 投票
2 回答
3861 浏览
提问于 2025-04-17 10:18

简单来说,我能在Python中同时使用多个.mo文件来处理同一种语言吗?

在我的Python应用程序中,我需要用到gettext来实现国际化(I18N)。这个应用程序有点像插件系统。也就是说,你可以下载一个插件,把它放到指定的目录里,然后它就像其他Python包一样运行。主应用程序把它使用的.mo文件放在比如说./locale/en/LC_MESSAGES/main.mo这个位置。而插件1也有自己的.mo文件,叫做plugin1.mo,同样放在这个目录下。

我会用下面的代码来加载main.mo中的国际化消息:

gettext.install('main', './locale', unicode=False)

那么,我该如何安装其他的.mo文件,以确保所有插件都能正确翻译呢?

我想到的解决方案有:

我是否应该在每个包的命名空间中使用gettext.install()?但这样会覆盖之前定义的_(),搞乱主应用程序未来的翻译。

有没有办法把两个.mo文件合并成一个(比如说安装新插件时)?

在运行时,我能把它们合并成一个GNUTranslation对象吗?或者覆盖添加到全局命名空间的默认_()方法?如果这样做,我该怎么实现呢?

我想用_('plugin1', '插件1中的Hello World')来代替_('Hello World')

注意:这个应用程序不应该知道所有要安装的插件,所以它不能在main.mo文件中预先翻译好所有消息。

2 个回答

3

gettext.install() 这个函数会把一个不能被改变的函数 _ 安装到全局环境中(在 Python 3 中是 builtins 模块)。这样做的结果就是你不能灵活地使用其他翻译方式。
注意:Python 查找名字的顺序是:局部变量 > 模块全局变量 > 内置变量。

所以,不管怎样,gettext.translation()(基于类的 API)或者 gettext.GNUTranslations()(比如用于自定义 .mo 文件路径)都可以用来明确地处理多个翻译,或者同时混合使用不同的翻译方式。

一些选项:

  • 通过 t = gettext.translation(...); _ = t.ugettext,你可以把不同的翻译放到每个模块的全局命名空间中,这样在实际应用中可能会更自动化。也许主要的翻译还是可以通过 main_t.install() 放到全局环境中。

  • 如果你想把所有的翻译混合在一起,或者这样做是可以接受的,你可以通过 t.install(); t.add_fallback(t_plugin1); t.add_fallback(t_plugin1);...; 来全局链式安装多个翻译,这样也能保持全局的应用方式。

  • 除了 _,你还可以使用其他的 gettext 关键字,并通过 xgettext -k other_keyword 选项来提供这些关键字。不过我不喜欢太长和模块独特的名字。
    (不过我个人更喜欢使用 I 这个关键字,而不是 _,我还启用了像 I % "some text" 这样的操作方式,而不是 _("some text")I("some text")。通过 I = t; t.__call__ = t.__mod__ = t.ugettext 这样做是有效的;再加上一小段对 pygettext.py 的修改。这个方式在输入时更方便,看起来更易读,也更符合 Python 的风格,而且避免了在使用 gettext 模块时,_ 与 Python 交互式环境中最后结果的命名冲突(参见 sys.displayhook)。_ 也被我用作在表达式中未使用值的占位符,比如 _, _, x, y, z, _ = some_tuple
    总的来说,gettext 是一个相对简单的模块和机制,Python 中的东西也很容易自定义。)

1

每个插件应该使用不同的域名。这个域名可以是包名,这样可以避免冲突。

我不太明白为什么你需要用插件的域名去翻译插件外的东西,但如果你真的需要这样做,那么每次都应该明确区分域名。

每个插件可以提供自己的“下划线”,这个下划线是和插件的域名绑定在一起的:

from my.plugin import MessageFactory as _my_plugin

请注意,下划线只是一个约定,用来帮助提取工具找到程序中支持国际化的消息。插件的消息应该在各自的包中用下划线标记(你是把它们放在不同的包里,对吧?)。在其他地方,你可以用其他名字来调用这些工厂,把下划线留给主程序的翻译域。

关于.mo文件我不太确定,但你可以把所有的.po文件编译成一个.mo文件。不过,如果插件是由不同的、不合作的作者写的,可能会出现msgid冲突。

更新:

如果插件和主应用在同一个包里,那么给它们使用单独的翻译域就没有意义了(这不是你的情况)。如果插件在不同的包里,那么提取工作应该独立进行。在这两种情况下,你都不会遇到变量_的问题。如果出于某种原因,主应用想在代码中使用插件的翻译,可以给_用其他名字,就像答案中提到的那样。当然,提取工具只会识别下划线。

换句话说,插件应该自己处理它们的翻译。主应用可以作为插件API的一部分,使用特定于插件的翻译函数。提取或手动添加字符串到po/mo文件的事情也不需要主应用担心:这由插件作者来提供翻译。

撰写回答