que para instalar example como application python pygtk gettext glib pyz

python - instalar - Obtenga catálogos de mensajes de texto del directorio virtual dentro de PYZ para widgets de GtkBuilder



python y gtk3 (1)

Este es mi ejemplo de aplicación Glade / GtkBuilder / Gtk. He definido una función xml_gettext que traduce de forma transparente los archivos glade xml y pasa a la instancia de gtk.Builder como una cadena.

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()

He archivado mis directorios de locale en locale.zip que se incluye en el paquete pyz .
Este es contenido de 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'')

Para hacer el locale.zip como un sistema de archivos, uso ZipFS de fs .

Afortunadamente Python gettext no es GNU gettext. gettext es puro Python, no usa gettext de GNU, pero lo imita. gettext tiene dos funciones básicas de find y translation . He redefinido estos dos en un módulo separado llamado mygettext para hacer que usen archivos de ZipFS .

gettext usa os.path , os.path.exists y se open para encontrar archivos y abrirlos que sustituyo con los módulos equivalentes del módulo fs .

Este es el contenido de mi aplicación.

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

Debido a que los archivos pyz tienen texto, generalmente un shebang, ante él, me salto esta línea después de abrir el archivo pyz en modo binario. Otros módulos en la aplicación que quieran usar la función gettext.gettext , deberían importar zfs_gettext lugar de mygettext y convertirlo en un alias para _ .

Aquí va 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'')

No se deben llamar los dos siguientes porque glade no usa Python gettext .

glade.bindtextdomain(''myapp'',''locale'') glade.textdomain(''myapp'')

¿Existe un enfoque establecido para incrustar la locale/xy/LC_MESSAGES/* gettext locale/xy/LC_MESSAGES/* en un paquete PYZ ? Específicamente para que la traducción automática de widgets de Gtks los recoja desde el archivo ZIP.

Para otros recursos integrados, pkgutil.get_deta o inspect / get_source funcionan lo suficientemente bien. Pero las API de gettext del sistema y de Python dependen de que bindtextdomain se proporcione como un localedir antiguo y localedir ; no hay recursos o cadenas, etc.

Por lo tanto, no pude idear una solución funcional práctica o incluso remota:

  1. gvfs virtuales gvfs / gio
    Ahora usando archive://file%3A%2F%2Fmypkg.pyz%2Fmessages%2F IRI serían una alternativa para leer otros archivos directamente desde un archivo zip. Pero glibs g_dgettext sigue siendo solo una envoltura delgada alrededor de la biblioteca del sistema. Y, por lo tanto, cualquier URL de este tipo no se puede utilizar como localedir .

  2. Extracción parcial de la cremallera.
    Así es como funciona PyInstaller, creo. Pero, por supuesto, es algo ridículo empaquetar algo como una aplicación .pyz , solo para tenerlo preextractado en cada invocación.

  3. Userland gettext .po / .po
    Ahora, leer los catálogos de mensajes de forma manual o simplemente usar dictados triviales sería una opción. Pero solo para cadenas en aplicación. De nuevo, esa no es manera de que Gtk / GtkBuilder los recoja implícitamente.
    Por lo tanto, tuve que atravesar manualmente todo el árbol de widgets, etiquetas, texto, widgets internos, markup_text, etc. Posible, pero meh .

  4. Montaje de fusible
    Esto sería superflaky. Pero, por supuesto, se puede acceder a los contenidos zip como gvfs-mount etc. Simplemente parece como un gvfs-mount memoria. Y dudo que siga siendo confiable con, por ejemplo, que se ejecuten dos instancias de aplicaciones, o una anterior finalizada de forma impura. (Como dunno, debido a una biblioteca del sistema, como gettext, tropezando con un punto de fusible zip frágil ..)

  5. Gtk señal / evento para la traducción (?)
    Me he dado cuenta de esto , así que estoy algo seguro de que no hay un mecanismo alternativo para las traducciones de widgets en Gtk / PyGtk / GI. Gtk / Builder espera y está atado a gettext.

¿Hay un enfoque más confiable tal vez?