After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 574520 - gtk.Builder translations fail
gtk.Builder translations fail
Status: RESOLVED NOTABUG
Product: pygtk
Classification: Bindings
Component: documentation
2.15.x
Other Linux
: Normal normal
: ---
Assigned To: Nobody's working on this now (help wanted and appreciated)
Python bindings maintainers
Depends on:
Blocks:
 
 
Reported: 2009-03-08 02:43 UTC by Osmo Salomaa
Modified: 2012-04-04 14:11 UTC
See Also:
GNOME target: ---
GNOME version: 2.23/2.24



Description Osmo Salomaa 2009-03-08 02:43:37 UTC
This came up while trying to migrate from Libglade to GtkBuilder. Glade had a gtk.glade.bindtextdomain function to define the directory where to look for translations (.mo-files). The documentation of gtk.Builder leads me to believe that gtk.Builder uses gettext directly. However, using gettext.bindtextdomain to define the translation directory seems to have no impact on gtk.Builder. Translations from a custom directory work fine when calling gettext.gettext, but fail for GtkBuilder files. I have gotten translations of GtkBuilder files to work only if the .mo-file is located under /usr/share/locale. I would expect to be able to use whatever locale directory I specify.

Using Python 2.5.4, GTK+ 2.14.7 and PyGTK 2.13.0 on Debian unstable.
Comment 1 Cezary Krzyżanowski 2009-03-08 08:32:26 UTC
Same thing on OS X (the system python, and MacPorts python 2.5 and 2.6) and Windows machine.
Comment 2 Gian Mario Tagliaretti 2009-03-20 21:20:10 UTC
cc'ing Johan to get his opinion, he knows pygtk and gtkbuilder better than anyone else.
Comment 3 Johan (not receiving bugmail) Dahlin 2009-03-20 22:11:46 UTC
gtk.glade.bindtextdomain was only added because locale.bindtextdomain wasn't introduced at the point glade bindings were written.

It should be deprecated in favor locale.bindtextdomain which does the same thing.
Comment 4 Thomas Perl 2009-05-12 11:54:45 UTC
There is no locale.bindtextdomain() on Python on Windows, and using Python's gettext module does not set the text domain as pointed out in the original post.

I am now using the following code to "distribute" the locale dir setting for my application on Windows. Ideally, gtk.Builder should automatically use Python's gettext settings or provide a bindtextdomain() function for use on Win32, so I can finally remove the dependency on libglade. Optionally, setting the locale dir for each gtk.Builder would also solve the problem for me.

This code is currently used as a workaround to get gtk.Builder working:

First, the code somewhere sets up the locale dir for the given textdomain:

>>> import gettext
>>> textdomain = 'myapp'
>>> # ... some code calls gettext.bindtextdomain(textdomain, SOMEPATH) ...

Then, I come to the part where I want to use gtk.Builder and I set the path to the translation files using the following code:

>>> locale_dir = gettext.bindtextdomain(textdomain)
>>> import gtk.glade
>>> gtk.glade.bindtextdomain(textdomain, locale_dir)
Comment 5 Johan (not receiving bugmail) Dahlin 2009-05-12 12:54:43 UTC
Please stop comenting on a closed bug.
Just use ctypes on libintl to call bindtextdomain()
Comment 6 Benny Malengier 2009-07-07 16:01:36 UTC
We hit this issue in our application. 

> Just use ctypes on libintl to call bindtextdomain()

Perhaps you can be a bit more verbose  ?? 

I fail to what else is needed here. We are doing now
gettext.bindtextdomain(appname, loc)
at the start of the application, but then gtkbuilder does not translate gtk labels in the glade files.
Comment 7 Benny Malengier 2009-07-07 16:15:40 UTC
Note that I have read the relevant info overriding the guideline to use gettext only,
http://docs.python.org/library/locale.html#access-to-message-catalogs 

and have added now to the start of the app

locale.bindtextdomain(appname, loc)

but no positive result. Well, my app does not use C, pure python only, so perhaps to be expected.

Please, if solution is easy, update http://www.pygtk.org/docs/pygtk/class-gtkbuilder.html with this.
Comment 8 Benny Malengier 2009-07-16 12:15:37 UTC
I'd like to document here my error and why I thought gtkbuilder was to blame.

My glade file labels where not translated with gtkbuilder because I failed to call
gtk.Builder.set_translation_domain(myapp)

With gtk.glade, it sufficed to do a 
gettext.textdomain(myapp)
in the beginning of the application code. Above is still needed, but gtk.Builder does not use the textdomain set as such, one must call on every gtk.Builder instance created also gtk.Builder.set_translation_domain(myapp)

I think the documentation in 
http://www.pygtk.org/docs/pygtk/class-gtkbuilder.html#properties-gtkbuilder
can be improved by stating that this property _must_ be set here, even if gettext has been passed a textdomain

Funny thing is I even tried the ctypes hint without success first....
import ctypes
libintl = ctypes.cdll.LoadLibrary("/usr/lib/preloadable_libintl.so")
libintl.bindtextdomain(localedomain, localedir)

Well, at least all works....
Comment 9 Gian Mario Tagliaretti 2009-07-18 15:13:26 UTC
Benny could you please provide a patch against the docs?

Thanks in advance
Comment 10 Cosimo Cecchi 2009-09-08 14:43:39 UTC
Thing is:

- from C you don't have to call _set_translation_domain() manually
- it's not explained anywhere that one should call that function manually
- python modules that migrate from Glade are very likely to miss this and have a serious regression.

Is there any reason why PyGTK can't call _set_translation_domain() automatically? As far as I understand, there's no standard GETTEXT_PACKAGE variable defined like in C, but you could also make it so that the GtkBuilder constructor accepts a translation domain as an (optional) parameter.
Comment 11 Sebastien Bacher 2009-09-08 16:52:50 UTC
there is lot of applications which got broken translations in the recent gtkbuilder conversions due to that, any reason why that extra steps are required?
Comment 12 Benny Malengier 2009-09-09 07:08:36 UTC
Things are not yet all ok for me too.
This is our downstream bug: 
http://www.gramps-project.org/bugs/view.php?id=3090

For old version of ubuntu, an import glade is needed, probably due to old gtk.builder version. 
For new ubuntu, all works, but a user on Fedora does not succeed in making it work without an import glade statement with binddomain in glade.
Comment 13 Benny Malengier 2009-09-09 07:21:45 UTC
Made an error here, the user is using mandriva 2009.1 with gtk 2.16.1

Another regression/change is that one can only use LANG or LC_ALL environment setting to change language of an application if the locale you change to is installed. This used to be different with gtk.glade, and gettext does it also different. Setting LANGUAGE variable works ok as that does not change the locale.

So in my case

LANGUAGE=sv python myapp.py   ==> builder uses the sv language

LANGUAGE=sv LANG=sv_SE.UTF-8 python myapp.py  
                              ==> builder uses C locale for language, gettext sv
                                  this because sv is not installed on my box

LANGUAGE=nl LANG=nl_NL.UTF-8 python gramps.py
                             ==> builder uses nl for language, nl is installed
Comment 14 Iuri Diniz 2009-09-13 02:39:15 UTC
I have this same problem, but I can workaround it by using gtk.glade functions

import gtk.glade

APP="empada"
DIR="locale"

gtk.glade.bindtextdomain(APP, DIR)
gtk.glade.textdomain(APP)


IMHO gtk.Builder must have a bindtextdomain method or class method
Comment 15 Jendrik Seipp 2009-09-28 15:00:01 UTC
+1 for the last comment (14) ;)
Comment 16 Benny Malengier 2010-02-06 18:19:53 UTC
It is possible to have all working without gtk.glade, but the order in which locale and gettext functions are called is important apparently. 

The order in gramps works: http://gramps.svn.sourceforge.net/viewvc/gramps/trunk/src/gramps.py?revision=14253&view=markup
See top of the file

In gramps, gtk.builder is abstracted in a class: 
http://gramps.svn.sourceforge.net/viewvc/gramps/trunk/src/glade.py?revision=13881&view=markup

Here the init does the rest of what is needed to have localization working. 
Still have to test this on windows though, but I'll leave that for the windows porters.

The basis for this code comes from: http://www.pixelbeat.org/programming/i18n.html
Comment 17 Iuri Diniz 2010-02-06 19:50:08 UTC
for Benny Malengier @16
I have followed your instructs but not worked at all.

Your approach works only for translatable strings inside sources files (.py) but not for strings inside glade files

code
----
import gettext
import locale

APP="empada"
DIR="locale"

gettext.bindtextdomain(APPNAME, DIR)
locale.setlocale(locale.LC_ALL, '')
gettext.textdomain(APPNAME)
gettext.install(APPNAME, localedir=None, unicode=1)
gettext.bindtextdomain(APPNAME, DIR)
----

In order to get strings inside glade files translated, I still have to add gtk.glade.bindtextdomain and gtk.textdomain, like the following code:
----

import locale
import gettext

# FOR STRINGS INSIDE SOURCE FILES (.py)
gettext.bindtextdomain(APPNAME, DIR)
locale.setlocale(locale.LC_ALL, '')
gettext.textdomain(APPNAME)
gettext.install(APPNAME, localedir=None, unicode=1)
gettext.bindtextdomain(APPNAME, DIR)

# FOR STRINGS INSIDE XML FILES (.glade)
import gtk.glade
gtk.glade.bindtextdomain(APPNAME, DIR)
gtk.glade.textdomain(APPNAME)
----
Comment 18 Iuri Diniz 2010-02-06 19:56:18 UTC
For clarification, the problem only occurs if I want to use a custom translation directory, if I put the *.mo files under /usr/share/locale/ everything works without glade call.
Comment 19 Benny Malengier 2010-02-06 20:22:54 UTC
Iuri, I did not test non standard install location. With the build system of Gramps this would be straithforward to test however.

Note that we already had most strings in glade files translated with the 
gtk.Builder.set_translation_domain(myapp) 
call after init of gtk.Builder, as indicated by the post at the start.

Preseeded comboboxes in glade designer had their entries not translated however, my comment 16 just contains the workflow we had to do to have also those entries translated on reading glade files.

For your problem, I would not give DIR="locale", but use an absolute path to the position. In Gramps we do:

if "GRAMPSI18N" in os.environ:
    LOCALEDIR = os.environ["GRAMPSI18N"]
elif os.path.exists( os.path.join(const.ROOT_DIR, "lang") ):
    LOCALEDIR = os.path.join(const.ROOT_DIR, "lang")
else:
    LOCALEDIR = os.path.join(const.PREFIXDIR, "share/locale")

I'll see if I find somebody willing to test if setting GRAMPSI18N still works.
Comment 20 Iuri Diniz 2010-02-06 20:56:04 UTC
Benny,
First, thanks for response...

Second, the problem, as I mentioned, only occurs if I use a non standard locale dir that is common on the development stage.

The DIR="locale" is just to simplify the problem, in real I use absolute paths

And I call gtk.Builder.set_translation_domain(myapp) latter on the application (immediately after instantiate a gtk.Builder object)

Please let me now if you find someone to test your env var and, if so, confirm the issue.
Comment 21 Benny Malengier 2010-02-07 10:17:06 UTC
Iuri,

changing a string used in glade file, compiling with 'msgfmt nl.po', putting it in a directory

po/nl/LC_MESSAGES/gramps.mo

then doing in the directory where po is:

GRAMPSI18N=po python src/gramps.py

has full translation of the glade file, so all is working here with the code.

We did not succeed yet in making windows work with intl.dll, though that is another developer, I don't have windows to code that.

So, I have this confirmed working in latests ubuntu, mandriva and opensuse.
Comment 22 Jendrik Seipp 2010-02-10 13:57:02 UTC
Just some additional info:

At least on my machine (Ubuntu 9.10) calling

locale.bindtextdomain(appname, loc)

seems to do the trick.
Comment 23 Benny Malengier 2010-02-10 14:11:19 UTC
Jendrik, on ubuntu 9.10 all that was not working for me was adding a combobox with glade designer and seeding it with translatable values, then trying to load that. Hmm, the file was upgraded from old glade 2, perhaps I should check if something in the xml file needs to be changed with glade 3
Comment 24 Romel Sandoval 2010-04-12 23:04:47 UTC
There is no way to make gtk.Builder translations to work under Windows. Not even with GTK 2.20.

The ctype trick it doesn't work nor the Gramps project try.

This is a serious bug. But I think this is more a GTK bug.

Why this is not a higher priority/severity bug?

This means Windows is not well supported or unimportant?
Comment 25 Benny Malengier 2010-04-13 14:21:30 UTC
The Gramps way has evolved considerably with the help of a windows coder, see
http://gramps.svn.sourceforge.net/viewvc/gramps/trunk/src/TransUtils.py?view=markup
method setup_windows_gettext(), and init_windows_gettext() 

Very ugly indeed, but it works on windows now, so glade is no longer a dependency of Gramps.

However Gramps has also addons which have their own translation domain, and gtk.Builder does not work translated in that case. I really did not want to spend more time again on that problem. As others have mentioned, the work around is (eg for labels):

for label in listoftranslated labels:
   label.set_text(_(label.get_text())

which works on windows and linux as _ is installed ok for the addons, only via gtk.Builder does it fail.

All not very pretty. You could generate the listoftranslated_labels automatically by going over all objects in the glade file though. We now do it by hand in Gramps, it is only for tool addons with a glade file.
Comment 26 Sandro Mani 2010-05-31 17:29:55 UTC
The I solved the problem by calling builder.set_translation_domain _before_ builder.add_from_file, entire setup is as follows:

[...]
# Internationalization
import locale
import gettext
locale.setlocale(locale.LC_ALL, '')
locale.bindtextdomain('gImageReader', 'locale')
gettext.bindtextdomain('gImageReader', 'locale')
gettext.textdomain('gImageReader')
_ = gettext.gettext

class gimagereader:
	def __init__(self,filename=None):
		# Read builder
		self.builder=gtk.Builder()
		self.builder.set_translation_domain('gImageReader')
		self.builder.add_from_file("gimagereader.xml")
[...]

which works also with local locale dir.
Comment 27 Yann Leboulanger 2010-10-01 22:06:38 UTC
Thanks guy for sharing your experience. I hardly tried all those ctypes workarrounds to make strings translatable under windows, but I'm not able to make things work. All strings are translated except the one from .ui files

So this is a desperate call for help. If anyone could help things work for Gajim, that would help lots of users.

Gtk is really not easy to use under windows.
Comment 28 Sandro Mani 2010-10-01 22:45:30 UTC
@Yann Leboulanger: I suggest you looked at the source code of a working program, for example gImageReader: download the source tarball from http://sourceforge.net/projects/gimagereader/files/, then look at src/main.py lines 40-73 (also look at bin/gimagereader to see how the gimagereader class is instantiated).
Comment 29 Benny Malengier 2010-10-02 07:46:43 UTC
gimagereader uses a simple intl.dll search, see
http://gimagereader.svn.sourceforge.net/viewvc/gimagereader/trunk/src/main.py?revision=65&view=markup

A windows developer coded a more general one for gramps. First it is important to have no imports that call gettext or translate before the system is set up.
Therefore, setting up the translation should be the first real thing you do, see giimagereader main.py file, or in gramps:
http://gramps.svn.sourceforge.net/viewvc/gramps/branches/maintenance/gramps32/src/gramps.py?revision=15939&view=markup
Both have translation setup at the top after imports that do no translates internally.

For windows, you could copy the relevant function from TransUtils.py as given in post above.
The problem indicated there: distributing addons with their own translation domain, is fixed in the present code, so all translation works.
Comment 30 Yann Leboulanger 2010-10-02 15:19:14 UTC
Thanks Sandro and Benny for your answers. I already took those code to test, but the error I did was that I was trying with libintl3.dll, which doesn't work.
Now with intl.dll, I have a beginning of working things: All string are now translated, but the one with accentuated chars don't work: I have a square with a cross inside instead of my accentuated char. And my app crashes when I try to open some other window in my app.
string for which I use translation with _('text') works ok with accentuated chars, so it's not my mo file that has a problem.

I use intl.dll v0.18.1 from GTK2.20 bundle tarball [0].

Any idea about that?

[0] http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.20/gtk+-bundle_2.20.1-20100912_win32.zip
Comment 31 Sandro Mani 2010-10-02 16:02:07 UTC
All string are now translated, but the one with accentuated chars don't work: I have a square with a cross inside instead of my accentuated char.
-> Perhaps wrong encoding of the *.po file? (i.e. UTF-8 vs ISO-8859-15 etc)

And my app crashes when I try to open some other window in my app.
-> Are you using threading? If so, maybe this helps: http://faq.pygtk.org/index.py?file=faq20.006.htp&req=show
Comment 32 Yann Leboulanger 2010-10-03 18:20:56 UTC
(In reply to comment #31)
> All string are now translated, but the one with accentuated chars don't work: I
> have a square with a cross inside instead of my accentuated char.
> -> Perhaps wrong encoding of the *.po file? (i.e. UTF-8 vs ISO-8859-15 etc)

No, I don't think so. As I wrote, accentuated chars works correctly for _("") strings. And I tried mo files built under linux (UTF-8) and windows. Both have the same result.

> And my app crashes when I try to open some other window in my app.
> -> Are you using threading? If so, maybe this helps:
> http://faq.pygtk.org/index.py?file=faq20.006.htp&req=show

I only use threads for some very specific part of my code, and in the test I did, I don't use threads. And I'm quite sure it's related to translation, because if I just comment the line "libintl.bindtextdomain(APP, DIR)", text from gtk.builder is not translated, and same test doesn't crash.
Comment 33 Yann Leboulanger 2010-10-20 20:07:36 UTC
I've progressed a bit. If I encode the french PO file in iso8859-15 instead of UTF-8, there is no more problem in gtk.builder strings. But of course strings in my code like label.set_text(_('test')) doesn't work correctly if the translation of 'test' contains an accentuated char.

Any idea why po files in utf-8 don't work correctly with gtk.builder strings?
Comment 34 Yann Leboulanger 2010-12-06 22:21:18 UTC
For those interested, here is the line I was missing to make things work:

libintl.bind_textdomain_codeset(domain, 'UTF-8')

I hope some day we won't need ctypes anymore!
Comment 35 Dysmas 2011-07-11 09:35:51 UTC
As far as I know this is not fixed for Windows in the python Gtk for Windows bundle 2.24.

So here is a full working code which shows how to translate in Windows in pure Python without ctypes dll and so on.
Not beautiful (especially the try ... except should be improved), but works as is.
Works also for accentuated characters, provided your po file is written in utf8.

It could be put in a small class but I am not the guy for that and the intention is just to show how it works. This is nothing more that the solution of Benny Malengier, but with a Pythonic solution to build the list of all Labels (or any other type of widget, just give the proper type). 

Windows XP SP3
Python 2.6
Python Gtk for Windows bundle 2.24


#!/usr/bin/python
# coding: utf-8 -*-

import gettext
import gtk

print gettext.find("test", "locale", ["fr", "en"], all)

lang1 = gettext.translation('test', './locale/', languages=['fr'])
lang1.install()

# Test gettext. This MUST work first.
print _("Test string")


widgets = gtk.Builder()
widgets.add_from_file('data/test51.glade')

# Now the stuff to translate the labels.

# this small function returns the type of a widget
def widget_type(widget) :
    try :
        z = widget.class_path()
        z2 = z.split(".")[-1]
        return z2
    except:
        return False

# create an array of all widgets
arWidgets = widgets.get_objects()
arw = {}
for z in arWidgets :
    try :
        name = gtk.Buildable.get_name(z)
        arw[name]= z
    except :
        pass

# translate all labels
for name in arw :
    widget = arw[name]
    if (widget_type(widget) == "GtkLabel") :
        widget.set_text(_(widget.get_text()))


arw["window1"].connect("destroy", lambda w: gtk.main_quit())
arw["window1"].show_all()
gtk.main()
Comment 36 Dieter Verfaillie 2011-07-11 11:09:40 UTC
(In reply to comment #34)
> I hope some day we won't need ctypes anymore!

One way or another, initializing intl.dll [1] or libintl-?.dll [2]
(as Johan suggested in comment #5) is the only correct solution
we have here. Remember it is Python that decided to come up with
it's own "pure Python" implementation of the gettext package. GTK+
(and the rest of GNOME platform's) binaries and just about all of
it's dependencies use libintl.

I don't see how people can expect libintl to be magically
initialized from Python's gettext package when it does no
effort to actually do so?

For those who might ask how to go about all of this, I wrote a
reusable module that implements all of the above and more that
might serve as an example (fully documented, etc):
https://github.com/dieterv/elib.intl/blob/master/lib/elib/intl/__init__.py

Also, this discussion should probably be continued on the
mailing list, as this is not a bug. Never has been...

mvg,
Dieter

[1] if you're using the GTK+ binaries from ftp.gnome.org/www.gtk.org
[2] if you're using the GTK+ binaries from build.opensuse.org,
    where ? is whatever number the current mingw32-libintl package
    provides.