XRC and i18n
(written by Stefan Hudac, [mailto:johnjsal@gmail.com stefan@datax.biz] )
There is not much written about XRC, wxPython and internationalization. At least I wasn't able to find it easily. That's why I want to share my recent knowledge on the topic. So lets take it from the ground up.
Build your XRC file
I tried several GUI builders for wxPython. I am not very fond of XRCed. I liked wxGlade much more. However the editor of choice for me is DialogBlocks. Among other things DialogBlocks allows you to have several projects open at the same time so you can copy paste parts of GUI from one to another without any problem. You just need to search for the open multiple projects settings in configuration. So find out which which one better suites your taste.
As for any unexperienced GUI programmer (as I certainly am), it is a great thing to look up examples in any wx GUI designer. It is also valuable to look for other samples or real life projects. It saves a lot of time not to reinvent a wheel and learn from other peoples' experience. A comprehensive list of wx GUI designers can be found in an article UsingXmlResources.
So now, that you have GUI designer of your iking and spent some time with it, create your XRC file with frames, dialogs, etc.
Create room for localization in your application
All the projects I looked up had a directory named locale in root directory of the application. I am not sure if it as an convention, but seems fairly logical so I am not going to think up anything other. Create locale directory in your application root directory. This directory will become home for the localized message files.
Now you are free to create any number of subdirectories for localized languages in locale directory. Each subdirectory should keep a name of the language according to a localization standard. For example: en, es, fr, de, pl, sk, ru, ... Once done you are ready for next step.
Create POT file
After you created your XRC file you need to extract translatable phrases out of it. As an XRC file is a plain XML it is fairly easy to parse it and get what you want. We are looking for content of tags <title> and <label>. This needs to be put into file which is usually put into locale directory with an extension .pot (portable object template - i guess)
POT file has certain structure which needs to be adhered to.
msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: Fri Feb 18 16:51:55 2005\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" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: ENCODING\n" "Generated-By: xrc_i18n.py\n" msgid "Basic Info" msgstr "" msgid "Title:" msgstr "" msgid "Street:" msgstr "" msgid "City:" msgstr "" msgid "Postal Code:" msgstr ""
I would lie if I tell you right now that I know all the header meaning. I just used it as I saw it and I hope to research it later.
Create PO file
Now that we have POT file we need to create our translation. Copy POT file into desired subdirectory of your locale directory. Change its file extension from pot to po and you may start editing.
It is pretty common for some operating system to save files for you in weird encodings. This may become a problem later when you find out that your application does not display messages properly on different platform/system. The correct encoding for portability issues is UTF-8.
My personal preference for editor is Eclipse. It allows me to set the output encoding for a file on any platform.
Compile MO files
Once done editing you need to compile po files you created into mo files which are used by wx or gettext. The standard Python installation comes with Tools directory where you can find a script called msgfmt.py.
On Windows you should look into Python's install dir to find it. It wasn't present on my Linux machine. I use Debian and Ubuntu distributions so I just had to lookup for the right package to install. Afterwards I was ready to go on my Linux machine as well. I had a prob on MacBook notebook as I could not locate this tool there. I plainly copied the needed file from my Linux box and continue with no problem. It is wise to put this tool into your environment PATH as you may need to call it often. Now you just need to call the msgfmt.py and it will crate mo file for you:
python msgfmt.py locale/fr/messages.po locale/fr/messages.mo
Created mo file is ready to being used in your application.
Problems and improvements of this approach
As anyone knows software development is endless process. The moment you create your app it is unavoidable that you will have to fix something later at any part of it. This means that you will have to, at some point, make changes to XRC file. This change has to logically propagate further into POT and PO files. Once the messages in those files are already translated it is a nightmare to keep track of what needs to be transfered from POT file into PO file manually.
Therefor I created a tool which makes those task easier to handle. The script does following things:
- Extracts all the messages from XRC file
- Backups POT file if it exists.
- Creates new POT file.
- Searches for all PO files in locale subtree.
- Backups PO files.
- Syncs each PO file with messages extracted from XRC. Translated messages which are part of updated XRC file messages are kept, while messages which are no longer used are excluded.
- Compiles MO file for each PO file.
The script needs to be placed together with msgfmt.py mentioned above as it is used compile MO file. Usage is following:
python xrc_i18n.py xrc_file_location locale_dir_location
Download the script here: attachment:xrc_i18n.py
(I intend to continue working on the tool. I have already more ideas on enhancing it. Keep me posted with your ideas. I'll see what I can do)
With this tool I am able to do the changes to do app at any time not worrying about loosing its bits. Of course code repository as CVS or SVN comes in handy for these kinds of mistakes anyway.
Localization implementation
So the only left thing is to implement created localization in our code. For my purposes I found the best place for doing these things in my App class:
1 class MyApp(wx.App):
2 def OnInit(self):
3 # -- initialize i18n
4 self.initI18n()
5 # create a handle to the XML resource file
6 self.xrcres = wx.xrc.EmptyXmlResource()
7 self.xrcres.Load('MyApp.xrc')
8 self.mainWindow = MainWindow(None)
9 self.SetTopWindow(self.mainWindow)
10 self.mainWindow.Show()
11 return True
12 def initI18n(self):
13 wx.Locale.AddCatalogLookupPathPrefix(os.path.join(os.getcwd(), 'locale'))
14 self.i18n = wx.Locale(wx.LANGUAGE_FRENCH)
15 self.i18n.AddCatalog('MyApp')
Among other components (classes) of your application you can get access to self.xrcres by calling app = wx.GetApp() which will return reference to the application instance. And calling the xrcres further is a snap - app.xrcres - and you can draw your dialogs across the entire program of yours...
According to code above the directory structure of app should look like this:
locale/ locale/MyApp.pot locale/en/ locale/en/MyApp.mo locale/en/MyApp.po locale/fr/ locale/fr/MyApp.mo locale/fr/MyApp.poMyApp MyApp.py MyApp.xrc