📜 ⬆️ ⬇️

Localization of a simple pygtk program with glade form in Linux

At once I will make a reservation that python and gtk I have 2nd version.

The unexpected desire to make the inscriptions on the main form of the program I am developing are not only Russian, but also in other languages ​​(and maybe it will be necessary not only for me), forced me to start looking for implementation methods. With a swoop, it was not possible to find working documentation on the localization of glade forms, so it was decided to write this article in the future so that others are more fortunate.

What is not in this article:
- How to make a translation of the form in the process. I could not find it, but I would like to know ...
- how to translate text in .py code in the process.
- Of course there is no beer, blackjack and the rest.
')
The translation will be done by specifying the locale at startup (or the default locale).


The first thing you need is a finished glade form. Since I deliberately did a simple test to figure out, then I have a simple mold, with a label, a button and a checkbox.

xml form code
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> <!--Generated with glade3 3.4.5 on Tue Nov 13 12:44:47 2012 --> <glade-interface> <widget class="GtkWindow" id="window1"> <child> <widget class="GtkVBox" id="vbox1"> <property name="visible">True</property> <child> <widget class="GtkLabel" id="label1"> <property name="visible">True</property> <property name="label" translatable="yes">label text</property> </widget> </child> <child> <widget class="GtkButton" id="button1"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="label" translatable="yes">button text</property> <property name="response_id">0</property> </widget> <packing> <property name="position">1</property> </packing> </child> <child> <widget class="GtkCheckButton" id="checkbutton1"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="label" translatable="yes">checkbutton text</property> <property name="response_id">0</property> <property name="draw_indicator">True</property> </widget> <packing> <property name="position">2</property> </packing> </child> </widget> </child> </widget> </glade-interface> 


You will also need a program on python (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 is also changing in the console.

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

A question arises: how not to reinvent the wheel with square wheels? It turned out that everything is already there, you just 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 will need to connect the locale module.

  locale.setlocale(locale.LC_ALL, '') 

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

.mo files are files with a list of all translatable strings of the program.

How to get them:

First you need to tear out all the lines from the glade form (the inscriptions on the widgets), but you should not do this manually, of course. To do this, use the intltool command set:

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

if desired, additional parameters can be viewed in man ʻe. The last input parameter is the glade file of the form, from where you need to tear out the text. This command will create a 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 strings from form widgets are listed.

We remember that in the Python file there is also a string that I would like to translate. You do not need to tear it out, you just need to mark it so that the localization teams understand that it needs to be translated. Therefore, the documentation suggests to write a string in the form:

  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 must 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 option --keyword shows the program which labels to pay attention to when collecting, because there are two “_” and “N_” here. --output sets the name of the output file, and then comes a list of all the files where you need to look for tags (it can be concluded that the tags may be different, but I didn’t bother with them, because this is not particularly important).

The result is a file of the following content:
# SOME DESCRIPTIVE TITLE.
# Copyright YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the PACKAGE package.
# FIRST AUTHOR <EMAIL @ ADDRESS>, 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 <EMAIL @ ADDRESS> \ n”
"Language-Team: LANGUAGE <LL@li.org> \ 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 files with translations. You do not need to edit it. Now it's time to decide on languages. I used English (en_US), Russian (ru) and German (de_DE) (in fact, from German I only know “Hitler Kaput” and “Handehoh” and not in writing, but let it be to 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 files ru.po, de.po and en_US.po appear. Inside, they are almost as empty as the template, but the header is full, though not with the exact data that I would like (maybe I didn’t indicate something in the keys) and without translating strings into other languages ​​(naturally). Translation will have to be hammered into msgstr fields. I also adjusted the charset to utf-8, the character size to 16 bits and e-mail.

The result was:
ru.po
# Russian translations for PACKAGE package.
# Copyright 2012 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the PACKAGE package.
# <aaa @ bbb>, 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: <aaa @ bbb> \ 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"
"tag"

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

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

de.po
(Yes, there is not German, but in general it does not matter):
# German translations for PACKAGE package.
# Copyright 2012 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the PACKAGE package.
# <aaa @ bbb>, 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: <aaa @ bbb> \ 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 PACKAGE package.
# <aaa @ bbb>, 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: <aaa @ bbb> \ 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 the native version (I have en_US by default). But after all, another native person may have, for example, de_DE, and he wants to see a translation into English. Yes, and gettext developers recommend creating yet.

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, I do not see any need for it.

So, there is everything to create .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 option -o (quite obvious) indicates the directory in which the finished file will lie, and it is worth noting that the top directory (here “locale”) should be the same for all .mo files, and then there should be a directory with the locale name (ru, de, en_US, de_DE, ru_RU - since the last two are without dialects, the programs shorten them to the first letters, but you can use the full names). It should be called as well as the domain specified in the Python program, only with ".mo". Also LC_MESSAGES is one of several possible variants of the name of the internal directory (I also 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 LANGU, LC_ALL, LC_MESSAGES, and LANG respectively.

The result was a file with the translation of strings that can already be used in the program (such manipulations are made not only for python / glade).

Let's go back to the python program.

First set up gettext. After resetting locale settings, you need to tell him where to get the translation files and which files exactly. To do this, I have entered two variables:

  APP="show_form" DIR="locale" 

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

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

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

Now we need to explain to the python what to do with strings of the form _ (). To do this, "_" is assigned the function of taking a transfer from the specified file. This can be recorded 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 recording.

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

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

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

Run:

 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 then honestly crashes, because My German locale is not connected. Thus, to use a locale, you need to add it in the settings of the graphical shell.

That's all.

Upd :

Pro Builder. If the form for libglade is already there, then you can either try to convert using libglade-convert (I got an error) or draw a new one for the 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 (ie, not to change).

Source: https://habr.com/ru/post/159629/


All Articles