Jeff H
Jeff H

Reputation: 1045

Internationalize Python project, using PyGTK3 and Glade

I am developing a Python program, using PyGTK3 and Glade for the UI. I would like to internationalize the UI, not based on locales in the system, but by languages "installed" with the program, and I would like to be able to change the UI language "on the fly", for example, by selecting a language in a menu. I'm on Windows 10, running Python 3.4.

I have done a lot of research and tried quite a variety of things, and made some progress. I was able to install a gettext for Windows package (link?), but it is apparently fairly minimal. xgettext does extract _("strings") from my Python source code. But if I try xgettext -L Glade (per https://askubuntu.com/questions/218432/how-extract-strings-from-a-ui-file-glade-with-gettext), it fails saying this version was built without expat. Also the various flavors of intltool don't seem to be available. No problem. Based on the output of intltool-extract described on this page (http://www.learningpython.com/2006/12/03/translating-your-pythonpygtk-application), I knew I could just manually extract the translatable strings from the .glade file and put them in a file with C .h format:

char *s = N_("_File");

Then I could add that .h file and -kN_ in my xgettext command, to get the .pot file with strings from both the Python source and the .glade UI file. I could also just write a Python script that extracts various strings from the .glade source file and adds them as pairs, with an empty string, to the .pot file. From my understanding, these are just techniques to get the appropriate strings into the .pot file, so they can be translated, but it appears that I could also just manually create the .pot file…

Once I have the .pot file with the strings to translate, then I can create .po files with the msginit command:

msginit --input=test.pot --locale=fr_FR     (this creates a file fr.po)

Then I manually translate (or get someone to translate) all of the strings in each .po text file, and once that's done, I create the .mo files and put them into the appropriate directory structure (all are sub-directories of \po):

msgfmt --output-file=fr_FR\LC_MESSAGES\test.mo fr.po

So far so good.

In my Python code, to enable translation, I've come up with the following minimal code:

import gettext
APP_NAME = "test"
locale_path = os.path.realpath('.\po')
langs = ['fr_FR', 'en_US']
lang = gettext.translation(APP_NAME, locale_path, languages=langs, fallback = True)
_ = lang.gettext
builder = Gtk.Builder()
builder.set_translation_domain(APP_NAME)
builder.add_from_file("test.glade")
builder.connect_signals(Handler())

This works great for translating the strings that are in the Python code, but none of the Glade interface gets translated. This is my basic problem.

Setting text manually in Glade XML I found this page: Translating python gtk builder files (http://martens.me/programming/translating-python-gtk-builder-files.html), which explains how to create a derived class of Gtk.Builder which scans through the XML of the .glade file and manually calls set_{} (in my context, it would be set_label, set_text or set_title) for each one with a parameter of _(child.text), so that the translation can happen. I made a few changes to his code, as the objects weren't loaded into the Builder before the XML was scanned, so I load the .glade file before we parse the tree, like this:

class GtkBuilder(Gtk.Builder):

    def __init__(self, glade_file, APP_NAME):
        super().__init__()
        self.set_translation_domain(APP_NAME)
        self.add_from_file(glade_file)

        tree = ET.parse(glade_file)
        root = tree.getroot()
        self.recursive_xml_translate(root)

And the translation happens a bit further down (with the _(child.text) to trigger the translation):

    # translate property value
    if child.tag == 'property' and parent_id and child.attrib.get('name'):
        name = child.attrib.get('name')
        obj = self.get_object(parent_id)
        func = getattr(obj, 'set_{}'.format(name), func_not_found)
        func(_(child.text))

In my code, instead of the 4 builder lines shown above, I use:

builder = GtkBuilder("PrimerPrep.glade", APP_NAME)
builder.connect_signals(Handler())

This seems to work, but it seems like a very convoluted way to get where I want to go.

Other attempts to translate Glade interface I also have read that the problem is that the Glade interface is presented using C code, so that the override of _() doesn't translate the Glade strings. So I have tried variations of this code:

try:
    import ctypes
    libintl = ctypes.cdll.LoadLibrary('libintl-8.dll')
    libintl.bindtextdomain(APP_NAME, locale_path)
    libintl.bind_textdomain_codeset(APP_NAME, 'UTF-8')
except:
    print('Error loading translations into Glade file.')

I've also tried setting an environment variable:

os.environ['LANG'] = 'fr_FR'

I've played around with different locale options, including what is decribed here: How to bind a text domain to a local folder for gettext under GTK3) but since my locale module doesn't have a bindtextdomain object, that didn't bear any fruit. And I don't want to actually set the computer locale. E.g. I don't have a French locale on my computer, but I want to be able to change the program UI to French. If I try to set the locale to one that's not installed, it gives an error.

None of these options seem to translate the Glade interface.

gettext should be able to do better? It seems from the gettext documentation (https://docs.python.org/3.5/library/gettext.html), in 23.1.3.3, that this shouldn't be hard:

lang1 = gettext.translation('myapplication', languages=['en'])
lang2 = gettext.translation('myapplication', languages=['fr'])
# start by using language1
lang1.install()
# ... time goes by, user selects language 2
lang2.install()

I've tried various combinations of these sorts of things, but none of them seem to affect the Glade interface (possibly do the C language issue mentioned above).

Getting the UI to update So the convoluted solution above is the only one that works to some degree. But even then, I've only been able to set the locale at program startup to have it take effect. What would be the process for changing the UI language while the program is running. I tried to run the builder.recursive_xml_translate() routine again once I manually changed the _() routine to a new translation language, but that didn't seem to update the interface. I couldn't find anything like a Gtk.Builder.refresh() method. Any suggestions for how I should update the UI language?

Conclusion So I found one convoluted way to translate the Glade interface, so it seems like I could make that work with enough effort (if I can find a way to update it on the fly). But there must be an easier way! Please tell me I'm just missing some small thing that would make my life a lot easier…

Upvotes: 1

Views: 792

Answers (2)

Rubén
Rubén

Reputation: 11

The gettext module does not work correctly with Glade. Use instead the gettext function that provides locale module: locale.gettext:

# At the beginning of the script ...
from locale import gettext as _
DOM = script_name[:-3]  # script_name = 'myscript.py' 
locale.bindtextdomain(DOM, PATH_SCRIPT + 'locale/')
locale.textdomain(DOM)
print(f"{DOM}")  # --> DOM = 'myscript'
....

Next, where you define the Gtk.Builder:

...
builder = Gtk.Builder()
builder.add_from_file(PATH_GLADE + "win.glade")
builder.set_translation_domain(DOM)  #  <--- !!!!!!
self.window = builder.get_object("window_app")
builder.connect_signals(Handler())
...

In the example we assume to have a 'locale' directory like the following one, in the directory where the script is located:

     locale/
      ├── es_AR
      │   └── LC_MESSAGES
      │       └── myscript.mo
      └── fr_FR
          └── LC_MESSAGES
              └── myscript.mo

Then the usual procedure:

Create the .h file

$ intltool-extract --type="gettext/glade" ./ui/app.glade

Create the .pot file

$ xgettext -k_ -kN_ --from-code utf-8  -o myscript.pot myscript.py ./ui/app.glade.h

Create the .po file

$ msginit -i myscript.pot -o myscript.po

Now incorporate the translation into the myscript.po file.

Create the .mo file

$ msgfmt myscript.po -o myscript.mo

Move myscript.mo to directory To finish, move the file to the directory of the language it corresponds to Example:

     locale/
       └─ es_AR
          └── LC_MESSAGES
              └── myscript.mo

For another language, create a new .po file, incorporate the translation, create the .mo file and move it to the corresponding folder.

Upvotes: 1

Andr&#233;s Cancer
Andr&#233;s Cancer

Reputation: 541

Try checking:

Using locale to bind glade i18n

The actual title is different but it does the trick. I still have problems with stock-buttons, but the rest of my app began to be i18ned

Upvotes: 0

Related Questions