GNOME Bugzilla – Bug 574520
gtk.Builder translations fail
Last modified: 2012-04-04 14:11:05 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.
Same thing on OS X (the system python, and MacPorts python 2.5 and 2.6) and Windows machine.
cc'ing Johan to get his opinion, he knows pygtk and gtkbuilder better than anyone else.
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.
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)
Please stop comenting on a closed bug. Just use ctypes on libintl to call bindtextdomain()
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.
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.
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....
Benny could you please provide a patch against the docs? Thanks in advance
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.
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?
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.
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
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
+1 for the last comment (14) ;)
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
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) ----
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.
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.
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.
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.
Just some additional info: At least on my machine (Ubuntu 9.10) calling locale.bindtextdomain(appname, loc) seems to do the trick.
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
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?
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.
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.
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.
@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).
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.
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
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
(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.
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?
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!
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()
(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.