Localization of a simple pygtk program with a glade form in Linux

    Immediately make a reservation that python and gtk I have the 2nd version.

    An unexpected desire to make inscriptions on the main form of the program I am developing not only Russian, but also in other languages ​​(what if it will be necessary not only for me), forced to start searching for implementation methods. I couldn’t find working documentation for glade forms localization from a snap, so it was decided to write this article in the future so that others would be more fortunate.

    What is not in this article:
    - how to do a translation of the form in the process. I could not find this, but I would like to know ...
    - how to translate text in .py code in the process.
    - Of course, beer, blackjack and the rest are not here either.

    Translation will be done by specifying the locale at startup (or the default locale).


    The first thing you need is a ready-made glade form. Because I specifically did a simple test to figure it out, then my form is simple, with a label, button and checkbox.

    xml form code
    TrueTruelabel textTrueTrueTruebutton text01TrueTruecheckbutton text0True2


    You will also need a python program (since I'm writing about it), which shows this form:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    import pygtk, gtk, gtk.glade
    print "hello to me"
    wTree = gtk.glade.XML("localize.glade", "window1")
    window = wTree.get_widget("window1")
    window.connect("delete_event", gtk.main_quit)
    window.show_all()
    gtk.main()
    

    At the same time, it will output a string to the console to show that the language in the console is changing.

    You can localize in different ways. For example, write the processing code for each line / widget yourself and update the lines depending on some kind of control command. There is a tiny plus in this - all actions can be performed at any time of the program, but the rest are minuses. And it will look terrible and work.

    The question naturally arises - how not to reinvent the wheel with square wheels? It turned out that everything is already there, you only need to be able to use it (as in most cases).

    First you need to reset the locale settings to default for the user (usually defined in the LANG environment variable). This will help get rid of possible problems in a multi-thread program. For such an action, you need to connect the locale module .

    	locale.setlocale(locale.LC_ALL, '')
    

    Next, we will use the capabilities of the gettext module (therefore, it will also have to be connected). Looking at his documentation, you can see that he needs some “binary .mo files”.

    .mo files are files with a list of all translatable program lines.

    How to get them:

    First you need to tear out all the lines from the glade form (the inscriptions on the widgets), but doing this manually, of course, is not worth it. To do this, we will use the set of intltool commands:

    	intltool-extract --type=gettext/glade localize.glade
    

    if desired, additional parameters can be found in man. The last input parameter is the glade file of the form where you want to rip the text from. This command will create the localize.glade.h file:

    	char *s = N_("label text");
    	char *s = N_("button text");
    	char *s = N_("checkbutton text");
    

    where, as you can see, all text lines from the widgets of the form are listed.

    We recall that in the Python file there is also a line that I would like to translate. You do not need to rip it out, you just need to mark it so that the localization teams understand that it needs to be translated. Therefore, the documentation suggests writing the line as:

    	print _("hello to me")
    

    those. take in _ ()

    As you can see above, in localize.glade.h the lines are wrapped in N_ (). It is also a kind of marker.

    So, all the necessary text is marked and now it needs to be collected in one place. The team will help us with this:

    	xgettext --language=Python --keyword=_ --keyword=N_ --output=show_form.pot show_form.py localize.glade.h
    

    The --keyword option shows the program what labels to look for when collecting, because there are two "_" and "N_" here. --output sets the name of the output file, and then there is a list of all files where you need to search for labels (we can conclude that the labels may be different, but I did not bother with them, because this is not particularly important).

    The result is a file with the following contents:
    # SOME DESCRIPTIVE TITLE.
    # Copyright YEAR THE PACKAGE'S COPYRIGHT HOLDER
    # This file is distributed under the same license as the PACKAGE package.
    # FIRST AUTHOR, YEAR.
    #
    #, fuzzy
    msgid ""
    msgstr ""
    "Project-Id-Version: PACKAGE VERSION \ n"
    "Report-Msgid-Bugs-To: \ n"
    "POT-Creation-Date: 2012-11-14 13: 54+ 0300 \ n "
    " PO-Revision-Date: YEAR-MO-DA HO: MI + ZONE \ n "
    " Last-Translator: FULL NAME\ n "
    " Language-Team: LANGUAGE\ n "
    " Language: \ n "
    " MIME-Version: 1.0 \ n "
    " Content-Type: text / plain; charset = CHARSET \ n "
    " Content-Transfer-Encoding: 8bit \ n "

    #: show_form.py:14
    msgid" hello to me "
    msgstr" "

    #: localize.glade.h: 1
    msgid" label text "
    msgstr" "

    #: localize.glade.h: 2
    msgid "button text"
    msgstr ""

    #: localize.glade.h: 3
    msgid "checkbutton text"
    msgstr ""

    This is a template for all future translation files. You do not need to edit it. Now is the time to decide on the languages. I used English (en_US), Russian (ru) and German (de_DE) (in fact, from German I only know "Hitler Kaput" and "Handehokh" and that is not written, but let it be until the heap). For each of the languages ​​you need to create a localized file from the template. This is done by the commands:

    	msginit --locale=ru --input=show_form.pot
    	msginit --locale=en_US --input=show_form.pot
    	msginit --locale=de_DE --input=show_form.pot
    

    As a result, three ru.po, de.po, and en_US.po files appear. Inside, they are almost as empty as the template, but the header is filled, though not with exactly the data that I would like (maybe I didn’t indicate something in the keys) and without translating the lines into other languages ​​(naturally). You will have to drive the translation into the msgstr fields with your hands. I also corrected charset to utf-8, character size at 16 bits and e-mail.

    The result is:
    ru.po
    # Russian translations for PACKAGE package.
    # Copyright 2012 THE PACKAGE'S COPYRIGHT HOLDER
    # This file is distributed under the same license as the PACKAGE package.
    #, 2012.
    #
    msgid ""
    msgstr ""
    "Project-Id-Version: PACKAGE VERSION \ n"
    "Report-Msgid-Bugs-To: \ n"
    "POT-Creation-Date: 2012-11-14 13: 54+ 0300 \ n "
    " PO-Revision-Date: 2012-11-14 13: 58 + 0300 \ n "
    " Last-Translator:\ n "
    " Language-Team: Russian \ n "
    " Language: ru \ n "
    " MIME-Version: 1.0 \ n "
    " Content-Type: text / plain; charset = utf-8 \ n "
    " Content-Transfer-Encoding: 16bit \ n "
    " Plural-Forms: nplurals = 3; plural = (n% 10 == 1 && n% 100! = 11? 0: n% 10> = 2 && n »
    "% 10 <= 4 && (n% 100 <10 || n% 100> = 20) ? 1: 2); \ n "

    #: show_form.py:14
    msgid" hello to me "
    msgstr" hello to me "

    #: localize.glade.h: 1
    msgid" label text "
    msgstr" label "

    #: localize.glade .h: 2
    msgid "button text"
    msgstr "button"

    #: localize.glade.h: 3
    msgid "checkbutton text"
    msgstr "checkmark"

    de.po
    (yes, it’s not German, but that’s not important at all):
    # German translations for PACKAGE package.
    # Copyright 2012 THE PACKAGE'S COPYRIGHT HOLDER
    # This file is distributed under the same license as the PACKAGE package.
    #, 2012.
    #
    msgid ""
    msgstr ""
    "Project-Id-Version: PACKAGE VERSION \ n"
    "Report-Msgid-Bugs-To: \ n"
    "POT-Creation-Date: 2012-11-14 13: 54+ 0300 \ n "
    " PO-Revision-Date: 2012-11-14 14: 14 + 0300 \ n "
    " Last-Translator:\ n "
    " Language-Team: German \ n "
    " Language: de \ n "
    " MIME-Version: 1.0 \ n "
    " Content-Type: text / plain; charset = utf-8 \ n "
    " Content-Transfer-Encoding: 16bit \ n "
    " Plural-Forms: nplurals = 2; plural = (n! = 1); \ n "

    #: show_form.py:14
    msgid" hello to me "
    msgstr" f "

    #: localize.glade.h: 1
    msgid" label text "
    msgstr" d "

    #: localize .glade.h: 2
    msgid "button text"
    msgstr "g"

    #: localize.glade.h: 3
    msgid "checkbutton text"
    msgstr "e"

    en_US.po
    # English translations for PACKAGE package.
    # Copyright 2012 THE PACKAGE'S COPYRIGHT HOLDER
    # This file is distributed under the same license as the PACKAGE package.
    #, 2012.
    #
    msgid ""
    msgstr ""
    "Project-Id-Version: PACKAGE VERSION \ n"
    "Report-Msgid-Bugs-To: \ n"
    "POT-Creation-Date: 2012-11-14 13: 54+ 0300 \ n "
    " PO-Revision-Date: 2012-11-14 13: 58 + 0300 \ n "
    " Last-Translator:\ n "
    " Language-Team: English \ n "
    " Language: en_US \ n "
    " MIME-Version: 1.0 \ n "
    " Content-Type: text / plain; charset = utf-8 \ n "
    " Content-Transfer-Encoding: 16bit \ n "
    " Plural-Forms: nplurals = 2; plural = (n! = 1); \ n "

    #: show_form.py:14
    msgid" hello to me "
    msgstr" hello to me "

    #: localize.glade.h: 1
    msgid" label text "
    msgstr" label text "

    #: localize.glade.h: 2
    msgid “button text”
    msgstr “button text”

    #: localize.glade.h: 3
    msgid “checkbutton text”
    msgstr “checkbutton text”

    It would seem - why make a native version (I have en_US by default). But after all, another person may have a dear one, for example, de_DE, and he will want to see a translation into English. Yes, and gettext developers recommend creating it.

    Some programmers also suggest using the intltool-merge command to make changes back to the form, but since at the same time I created exactly the same form without any changes, then I do not see the need for it.

    So, there is everything for creating .mo files. This is done by the commands:

    	msgfmt ru.po -o locale/ru/LC_MESSAGES/show_form.mo
    	msgfmt en_US.po -o locale/en_US/LC_MESSAGES/show_form.mo
    	msgfmt de.po -o locale/de/LC_MESSAGES/show_form.mo
    

    The -o option (quite obviously) indicates the directory in which the finished file will be located, and it is worth noting that the top directory (here “locale”) should be the same for all .mo files, and then the directory with the locale name should go (ru, de, en_US, de_DE, ru_RU - because the last two are without dialects, the programs reduce them to the first letters, but you can use the full names). It should be called in the same way as the domain indicated in the Python program, only with ".mo". Also LC_MESSAGES is one of several possible options for the name of the internal directory (also, I think it is better to use the same names).
    Here is what the official documentation says about this:

    ...
    localedir / language / LC_MESSAGES / domain.mo, where languages ​​is searched for in the environment variables LANGUAGE, LC_ALL, LC_MESSAGES, and LANG respectively.

    As a result, we got files with line feeds that can already be used in the program (such manipulations are performed not only for python / glade).

    Let's get back to the python program.

    First, configure gettext. After resetting the locale settings, you need to tell him where to get the translation files and which files. For this, I have introduced two variables:

    	APP="show_form"
    	DIR="locale"
    

    The fact that APP matches the name of the program is left over from the documentation, but I think that there can be any name. Although if you look at .mo files with the name of the program to which they relate, it is much easier to understand what's what.

    APP is the name of .mo files, DIR is the common directory with languages. The explanation of this fact gettext is produced by the lines:

    	gettext.bindtextdomain(APP, DIR)
    	gettext.textdomain(APP)
    

    Now you need to explain to python what to do with lines of the form _ (). To do this, "_" is assigned the function of taking the translation from the specified file. This can be written in two ways:

    	lang = gettext.translation(APP, DIR)
    	_ = lang.gettext
    

    or

    	_ = gettext.gettext
    

    that, if you look at the module code, the same thing. So it makes sense to choose a shorter record.

    This is enough for the text in .py files to be displayed in the desired language. And to localize the glade form, you need to explain gtk, where to get the translation and which:

    	gtk.glade.bindtextdomain(APP, DIR)
    	wTree = gtk.glade.XML("localize.glade", "window1", APP)
    

    Final code:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    import pygtk, gtk, gtk.glade
    import locale, gettext
    APP="show_form"
    DIR="locale"
    locale.setlocale(locale.LC_ALL, '')
    gettext.bindtextdomain(APP, DIR)
    gettext.textdomain(APP)
    _ = gettext.gettext
    print _("hello to me")
    gtk.glade.bindtextdomain(APP, DIR)
    wTree = gtk.glade.XML("localize.glade", "window1", APP)
    window = wTree.get_widget("window1")
    window.connect("delete_event", gtk.main_quit)
    window.show_all()
    gtk.main()
    

    Launch:

    LANG=en_US.utf-8 ./show_form.py
    

    image

    LANG=ru_RU.utf-8 ./show_form.py
    

    image

    LANG=de_DE.utf-8 ./show_form.py
    

    And here the error honestly flies out, tk. The German locale is not connected to me. Thus, to use the locale, you need to add it in the settings of the graphical shell.

    That's all.

    Upd :

    About Builder. If there is already a form for libglade, then you can either try to convert using libglade-convert (I got an error) or draw a new one for builder.

    The code will look like this:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    import gtk, gtk.glade
    import locale, gettext
    APP="show_form"
    DIR="locale"
    locale.setlocale(locale.LC_ALL, '')
    gettext.bindtextdomain(APP, DIR)
    gettext.textdomain(APP)
    _ = gettext.gettext
    print _("hello to me")
    builder = gtk.Builder()
    gtk.glade.bindtextdomain(APP, DIR)
    builder.set_translation_domain(APP)
    builder.add_from_file("localize.xml")
    window = builder.get_object("window1")
    window.connect("delete_event", gtk.main_quit)
    window.show_all()
    gtk.main()
    


    And, as Moonrise correctly noted, Content-Transfer-Encoding is officially recommended to be set to 8 bits (i.e., not to change).

    Also popular now: