Python 从PYZ中的virtual dir获取GtkBuilder小部件的Gettext消息目录

Python 从PYZ中的virtual dir获取GtkBuilder小部件的Gettext消息目录,python,pygtk,gettext,glib,pyz,Python,Pygtk,Gettext,Glib,Pyz,是否有一种既定的方法将gettextlocale/xy/LC\u MESSAGES/*嵌入到一个数据库中?特别是让GTK自动小部件翻译从ZIP归档中提取它们 对于其他嵌入式资源,pkgutil.get_deta或inspect/get_source工作正常。但系统和Python依赖于bindtextdomain提供一个普通的localedir;没有资源或字符串等 因此,我无法设计出一个可行的、甚至是一点都不实用的解决方案: 虚拟gvfs/gio路径 现在使用archive://file%3A%2

是否有一种既定的方法将gettext
locale/xy/LC\u MESSAGES/*
嵌入到一个数据库中?特别是让GTK自动小部件翻译从ZIP归档中提取它们

对于其他嵌入式资源,
pkgutil.get_deta
inspect
/
get_source
工作正常。但系统和Python依赖于
bindtextdomain
提供一个普通的
localedir
;没有资源或字符串等

因此,我无法设计出一个可行的、甚至是一点都不实用的解决方案:

  • 虚拟
    gvfs
    /
    gio
    路径

    现在使用
    archive://file%3A%2F%2Fmypkg.pyz%2Fmessages%2F
    IRIs将是直接从zip读取其他文件的替代方案。但它仍然只是系统库的一个薄薄的包装。因此,任何此类URL都不能用作
    localedir

  • 部分提取压缩文件
    我认为PyInstaller就是这样工作的。但将某些东西捆绑为.pyz应用程序,却在每次调用时预先提取,这当然有点荒谬

  • Userland gettext
    .mo
    /
    .po
    提取

    现在,手动读取消息目录或仅使用简单的dict将是一种选择。但仅适用于应用程序中的字符串。这也无法让Gtk/GtkBuilder隐式地获取它们。
    因此,我必须手动遍历整个小部件树、标签、文本、内部小部件、标记和文本等。可能,但是meh

  • 保险丝安装
    这将是超级富豪。但是当然,zip内容可以被访问
    gvfs mount
    等等。看起来就像某种内存占用。我怀疑它在运行两个应用程序实例或之前的一个不干净终止的应用程序实例时是否会保持可靠。(我不知道,因为像gettext这样的系统库在脆弱的zip熔丝点上绊倒了……)

  • 用于翻译的Gtk信号/事件(?)
    我发现了Shuck,所以我有点确定Gtk/PyGtk/GI中没有替代的小部件翻译机制。Gtk/Builder期望并且是gettext


  • 也许有更可靠的方法吗?

    这是我的示例Glade/GtkBuilder/Gtk应用程序。我已经定义了一个函数
    xml_gettext
    ,它透明地转换glade xml文件,并作为字符串传递给
    gtk.Builder
    实例

    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成为一个文件系统,我使用了来自的ZipFS

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

    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.bindtextdomain('myapp','locale')
    glade.textdomain('myapp')
    
    gettext
    使用
    os.path
    os.path.exists
    open
    查找文件并打开它们,我将其替换为
    fs
    模块中的等效文件

    这是我申请的内容

    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
    文件前面有文本,通常是shebang,所以在以二进制模式打开
    pyz
    文件后,我跳过这一行。应用程序中要使用
    gettext.gettext
    函数的其他模块应该从
    mygettext
    导入
    zfs\u gettext
    ,并将其作为
    \ugettext>的别名

    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.bindtextdomain('myapp','locale')
    glade.textdomain('myapp')
    
    下面是
    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.bindtextdomain('myapp','locale')
    glade.textdomain('myapp')
    
    不应调用以下两个函数,因为
    glade
    不使用Python
    gettext

    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.bindtextdomain('myapp','locale')
    glade.textdomain('myapp')
    

    我必须承认,我不明白你所说的“油嘴滑舌的人和gettext的关系紧密”是什么意思。这些只是几个方便的宏,GLib中绝对没有任何东西强迫你使用它们或gettext。当然。这也是glib文档断言的(“不强制任何特定的本地化方法…”)。在Gtk的背景下,它只是不太可行。(即使重新编译是一种选择,宏方案也不允许例如ICU替换)。因此,如果您使用的不是gettext,那么您就必须迭代地翻译所有Gtk小部件。如果您的问题是GtkBuilder xml文件本地化与gettext绑定(这对我来说似乎是一个有效的问题),那么您应该清楚地说。当你暗示GLib(或Gtk应用程序代码)翻译与gettext有某种联系时,这会使问题更难理解:我猜我不是唯一一个挠头思考的人“这些东西与翻译的实际制作方式无关:GTK小部件不关心翻译,他们只希望得到翻译后的字符串”……修复了这个问题。我最初希望glib提供一个解决方法(gio路径和所有),因此怀疑这是一个解决方法。但GtkBuilder UI文件是一个更好的提示。找到一个预翻译其文本节点的替代方法可能比查找小部件挂钩更简单。是否有一种已建立的方法将gettext locale/xy/LC_MESSAGES/*嵌入PYZ捆绑包中?“已建立的方法”是什么意思?您可以或不能嵌入。如果我理解正确,你想将
    gettext
    locale dir包含到应用程序pyz包中。对吗?哇,很多代码。仍在通读…看起来很实用。而且对其他用户也很有用。我将对其进行测试。-(已经看到了答案通知。所以请放松注释ping!)嘿,马里奥,你太酷了!非常感谢。编码快乐!