Thursday, April 10, 2014

Internationalized application with Gtkada.Intl

Gtkada comes with Gtkada.Intl, which is a binding for libintl to provide support for string internationalization.

In this example, a very simple program is developed to illustrate the usage of Gtkada.Intl package.

We use the helloworld example, and modified it to support i18n. You could find all the source at GitHub.

The directory structure is similar as before, and we are using Makefile to automate some tasks.


|- Makefile.common
|- intl
|  |- obj/
|  |- src/
|  |- po/
|  |   |- Makefile
|  |   `- build_skeleton.pl
|  |- Makefile
|  `- generate_locale_path.sh
...
src/intl.adb:
...
with Gtkada.Intl;     use Gtkada.Intl;

with intl_cb; use intl_cb;
with intl_locale;

procedure Intl is
   Win    : Gtk_Window;
   Button : Gtk_Button;
   prgname : constant String := "intl";
begin
   -- procedure calls to setup Locale and text domain binding
   SetLocale;
   Text_Domain (prgname);
   Bind_Text_Domain (prgname, intl_locale.path);

...
   Win.Set_Title (-"Window");
...
   Gtk_New (Button, -"Hello World");
...
end Intl;
Here, we call SetLocale, Text_Domain and Bind_Text_Domain to initialize the locale data. The Bind_Text_Domain procedure needs a locale path as its second parameter, as the path may change on different system, we need a way to tell the program where is the location. A script generate_locale_path.sh was created. It is called from the Makefile, when ever there is changed in the locale path, it will generate a new src/intl_locale.ads file. A sample file is as follow:
-- This is an automatically generated file during compilation
-- Please don't edit it by hand.
-- Generated at 2014-04-09 23:45

package Intl_Locale is
   path : constant String := "/usr/local/share/locale";
end Intl_Locale;
Each string that is to be translated has a "-" signed in front. Messages in src/intl_cb.adb are also altered. We won't list the detail source here.

To generate the pot file, a modified version of build_skeleton.pl borrowed from the GtkAda project is used. There are a few modifications that make build_skeleton.pl works much better than the original one:

  • Use the perl Text::Balanced module to capture quoted string, which will work for a case like -("Hello world (from Ada)!");. The original script will get a partial string "Hello world (from Ada".
  • Use the perl Tie::IxHash module to preserve the order of message strings appear in the source file, which give translation a better view of the content near by.
  • Allow spaces in between '-', '(' and '"', for the case like -   (   "Hello world 2!");, it will not be captured by the original script.
  • Allow replace of Ada.Characters.Latin_1.LF to \n besides ASCII.LF
  • Add translation of quoted '"' in the string to \" in -("Ada said ""Hello World!");, which will allow gettext to work correctly.
  • Use command parameters for project specific information, so that it could be used on other projects without modifications.
  • Change the POT-Creation-Date format to include time and timezone information.
  • Only check src/ and its sub directories.

A Makefile is borrowed from the GtkAda project and simplified.

po/Makefile
LANGS:=zh_CN
CP:=cp
prgname := intl
## Refresh all translations by extracting the strings from the current sources
refresh:
 ./build_skeleton.pl > ${prgname}.pot
 ${foreach lang,${LANGS}, \
 if [ -f ${lang}.po ]; then true; else ${CP} ${prgname}.pot ${lang}.po; fi; \
 msgmerge --no-wrap --update ${lang}.po ${prgname}.pot}

## Install the translation files
$(LANGS): 
 -msgfmt -o $@.gmo $@.po --statistics --check-header --check-format --check-domain

clean:
 -$(RM) *.gmo
A simple run of it under the po directory will give us a simple pot file (intl.pot):
# Translation file for intl example app
# Copyright (C) 2014 Zhu Qun-Ying
#
msgid ""
msgstr ""
"Project-Id-Version: intl\n"
"Report-Msgid-Bugs-To: zhu.qunying@gmail.com\n"
"POT-Creation-Date: 2014-04-10 22:29-0700\n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: intl_cb.adb
msgid "button clicked"
msgstr ""

#: intl_cb.adb
msgid "button_quit is called"
msgstr ""

#: intl_cb.adb
msgid "Delete event encounter."
msgstr ""

#: intl.adb
msgid "Hello World"
msgstr ""

#: intl.adb
msgid "Window"
msgstr ""

We have defined a default translation language zh_CN in the Makefile, so after the make command, we also have zh_CN.po file for translation. If there is no existing zh_CN.po, it will be a copy of the intl.pot file. If there exist a zh_CN.po file and has translation, it will be merged with the intl.pot file if any changes have been made.

In order to better edit it, an editor that knows the format of the po file will ease your translation work. I am using vim with po.vim plugin.

Under po directory, run make zh_CN will compile the zh_CN.po file to zh_CN.gmo.

At the intl directory, we could issue the command:

make prefix=/usr
Which will generated the src/intl_locale.ads file and set the locale path to "/usr/share/locale" and compile the program.

Just for test, we copy the po/zh_CN.gmo to /usr/share/locale/zh_CN/LC_MESSAGES/intl.mo, then run the command:

LANG=zh_CN.utf8 obj/intl
The simple window with a button 世界你好 is shown. Click on the button, the program will exit, and you will get some output from command line:
按钮被点击
调用 button_quit

For advance usage of gettext utilities, please refer to the GNU gettext utilities document.

All the source is available at GitHub

No comments: