从PYZ中的虚拟目录获取GtkBuilder小部件的文本消息目录

时间:2015-04-16 21:24:26

标签: python pygtk gettext glib pyz

是否存在将gettext locale/xy/LC_MESSAGES/*嵌入PYZ bundle的既定方法?具体来说,让Gtks自动小部件翻译从 ZIP存档中选择

对于其他嵌入资源pkgutil.get_detainspect / get_source运行良好。但系统 Python gettext APIs依赖于bindtextdomain提供的普通旧localedir;没有资源或字符串等。

所以我无法设计出可行的甚至是远程实用的解决方法:

  1. 虚拟gvfs / gio路径
    现在使用archive://file%3A%2F%2Fmypkg.pyz%2Fmessages%2F IRI可以直接从zip中读取其他文件。但是glibs g_dgettext仍然只是系统库的一个薄包装器。因此,任何此类网址都不能用作localedir

  2. 部分解压缩
    我认为这就是PyInstaller的工作方式。但是,将某些内容捆绑为 .pyz 应用程序当然有点荒谬,只是在每次调用时将其预先提取。

  3. Userland gettext .mo / .po提取
    现在手动读取消息目录或仅使用简单的dicts将是一个选项。但仅适用于应用程序中的字符串。再也没办法让Gtk / GtkBuilder暗中捡起它们 因此,我必须手动遍历整个小部件树,标签,文本,内部小部件,markup_text等。可能,但 meh

  4. 安装FUSE
    这将是超级的。但是当然,可以访问zip内容gvfs-mount等等。看起来就像是某种记忆力。我怀疑它会保持可靠性,例如两个应用程序实例正在运行,或者之前的不正确终止。 (就像dunno一样,由于系统库,比如gettext,在一个脆弱的拉链保险丝点上磕磕绊绊......)

  5. 用于翻译的Gtk信号/事件(?)
    我发现深蹲about this,所以我有点确定在Gtk / PyGtk / GI中没有替代小部件翻译机制。 Gtk / Builder期望并且是tied to gettext。

  6. 是否有更可靠的方法?

1 个答案:

答案 0 :(得分:4)

这是我的示例Glade / GtkBuilder / Gtk应用程序。我已经定义了一个函数gtk.Builder,它透明地转换了glade xml文件,并以字符串形式传递给import mygettext as gettext import os import sys import gtk from gtk import glade glade_xml = '''<?xml version="1.0" encoding="UTF-8"?> <interface> <!-- interface-requires gtk+ 3.0 --> <object class="GtkWindow" id="window1"> <property name="can_focus">False</property> <signal name="delete-event" handler="onDeleteWindow" swapped="no"/> <child> <object class="GtkButton" id="button1"> <property name="label" translatable="yes">Welcome to Python!</property> <property name="use_action_appearance">False</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="use_action_appearance">False</property> <signal name="pressed" handler="onButtonPressed" swapped="no"/> </object> </child> </object> </interface>''' class Handler: def onDeleteWindow(self, *args): gtk.main_quit(*args) def onButtonPressed(self, button): print('locale: {}\nLANGUAGE: {}'.format( gettext.find('myapp','locale'),os.environ['LANGUAGE'])) def main(): builder = gtk.Builder() translated_xml = gettext.xml_gettext(glade_xml) builder.add_from_string(translated_xml) builder.connect_signals(Handler()) window = builder.get_object("window1") window.show_all() gtk.main() if __name__ == '__main__': main() 实例。

locale.zip

我已将我的语言环境目录存档到pyz包含在locale.zip包中。{ 这是(u'/locale/fr_FR/LC_MESSAGES/myapp.mo', u'/locale/en_US/LC_MESSAGES/myapp.mo', u'/locale/en_IN/LC_MESSAGES/myapp.mo')

的内容
gettext

要将locale.zip作为文件系统,我使用fs中的ZipFS。

幸运的是,Python gettext不是GNU gettext。 gettext是纯Python,它不使用GNU gettext但是模仿它。 find有两个核心功能translationmygettext。我在名为ZipFS的单独模块中重新定义了这两个模块,以使它们使用gettext中的文件。

os.path使用os.path.existsopenfs查找文件并打开它们,我将其替换为pyzzer.pyz -i glade_v1.pyz # A zipped Python application # Built with pyzzer Archive contents: glade_dist/glade_example.py glade_dist/locale.zip glade_dist/__init__.py glade_dist/mygettext.py __main__.py 模块中的等效文件。

这是我的申请内容。

pyz

因为pyz文件有文本,通常是一个shebang,所以我在二进制模式下打开gettext.gettext文件后跳过这一行。应用程序中要使用zfs_gettext函数的其他模块应从mygettext导入_,并将其设为mygettext.py的别名。

这里是from errno import ENOENT from gettext import _expand_lang, _translations, _default_localedir from gettext import GNUTranslations, NullTranslations import gettext import copy import os import sys from xml.etree import ElementTree as ET import zipfile import fs from fs.zipfs import ZipFS zfs = None if zipfile.is_zipfile(sys.argv[0]): try: myself = open(sys.argv[0],'rb') next(myself) zfs = ZipFS(ZipFS(myself,'r').open('glade_dist/locale.zip','rb')) except: pass else: try: zfs = ZipFS('locale.zip','r') except: pass if zfs: os.path = fs.path os.path.exists = zfs.exists open = zfs.open def find(domain, localedir=None, languages=None, all=0): # Get some reasonable defaults for arguments that were not supplied if localedir is None: localedir = _default_localedir if languages is None: languages = [] for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'): val = os.environ.get(envar) if val: languages = val.split(':') break if 'C' not in languages: languages.append('C') # now normalize and expand the languages nelangs = [] for lang in languages: for nelang in _expand_lang(lang): if nelang not in nelangs: nelangs.append(nelang) # select a language if all: result = [] else: result = None for lang in nelangs: if lang == 'C': break mofile = os.path.join(localedir, lang, 'LC_MESSAGES', '%s.mo' % domain) mofile_lp = os.path.join("/usr/share/locale-langpack", lang, 'LC_MESSAGES', '%s.mo' % domain) # first look into the standard locale dir, then into the # langpack locale dir # standard mo file if os.path.exists(mofile): if all: result.append(mofile) else: return mofile # langpack mofile -> use it if os.path.exists(mofile_lp): if all: result.append(mofile_lp) else: return mofile # langpack mofile -> use it if os.path.exists(mofile_lp): if all: result.append(mofile_lp) else: return mofile_lp return result def translation(domain, localedir=None, languages=None, class_=None, fallback=False, codeset=None): if class_ is None: class_ = GNUTranslations mofiles = find(domain, localedir, languages, all=1) if not mofiles: if fallback: return NullTranslations() raise IOError(ENOENT, 'No translation file found for domain', domain) # Avoid opening, reading, and parsing the .mo file after it's been done # once. result = None for mofile in mofiles: key = (class_, os.path.abspath(mofile)) t = _translations.get(key) if t is None: with open(mofile, 'rb') as fp: t = _translations.setdefault(key, class_(fp)) # Copy the translation object to allow setting fallbacks and # output charset. All other instance data is shared with the # cached object. t = copy.copy(t) if codeset: t.set_output_charset(codeset) if result is None: result = t else: result.add_fallback(t) return result def xml_gettext(xml_str): root = ET.fromstring(xml_str) labels = root.findall('.//*[@name="label"][@translatable="yes"]') for label in labels: label.text = _(label.text) return ET.tostring(root) gettext.find = find gettext.translation = translation _ = zfs_gettext = gettext.gettext gettext.bindtextdomain('myapp','locale') gettext.textdomain('myapp')

glade

不应调用以下两个,因为gettext不使用Python glade.bindtextdomain('myapp','locale') glade.textdomain('myapp')

{{1}}