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:
gvfs
virtualesgvfs
/gio
Ahora usandoarchive://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 comolocaledir
.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.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 .Montaje de fusible
Esto sería superflaky. Pero, por supuesto, se puede acceder a los contenidos zip comogvfs-mount
etc. Simplemente parece como ungvfs-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 ..)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?