如何同时使用多个 .mo 文件进行 gettext 翻译?
简单来说,我能在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 个回答
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 中的东西也很容易自定义。)
每个插件应该使用不同的域名。这个域名可以是包名,这样可以避免冲突。
我不太明白为什么你需要用插件的域名去翻译插件外的东西,但如果你真的需要这样做,那么每次都应该明确区分域名。
每个插件可以提供自己的“下划线”,这个下划线是和插件的域名绑定在一起的:
from my.plugin import MessageFactory as _my_plugin
请注意,下划线只是一个约定,用来帮助提取工具找到程序中支持国际化的消息。插件的消息应该在各自的包中用下划线标记(你是把它们放在不同的包里,对吧?)。在其他地方,你可以用其他名字来调用这些工厂,把下划线留给主程序的翻译域。
关于.mo文件我不太确定,但你可以把所有的.po文件编译成一个.mo文件。不过,如果插件是由不同的、不合作的作者写的,可能会出现msgid冲突。
更新:
如果插件和主应用在同一个包里,那么给它们使用单独的翻译域就没有意义了(这不是你的情况)。如果插件在不同的包里,那么提取工作应该独立进行。在这两种情况下,你都不会遇到变量_的问题。如果出于某种原因,主应用想在代码中使用插件的翻译,可以给_用其他名字,就像答案中提到的那样。当然,提取工具只会识别下划线。
换句话说,插件应该自己处理它们的翻译。主应用可以作为插件API的一部分,使用特定于插件的翻译函数。提取或手动添加字符串到po/mo文件的事情也不需要主应用担心:这由插件作者来提供翻译。