I’ve recently struggled with GNU gettext for internationalizing some of my programs. This, for the benefit of other people who, like me, can’t be bovered to read the documentation, is the method I’ve used successfully:
I’m assuming you have a standard GNU autoconf/automake setup. If you don’t, there’s a lot of manual work to do. I’ve no idea how to do that: better read the manual instead.
There’s a handy tool gettextize
which handles a lot of the monkey work for you. Run it in the top-level project directory:
$ gettextize
This will create a bunch of files (in the m4
and po
directories), make a few changes to your configure.ac
and Makefile.am
(backups are created with a ~ suffix) and also print some instructions. Let’s follow them.
The first thing to do is add AM_GNU_GETTEXT([external])
to configure.ac
in order to cause autoconfiguration
to look for an external libintl (apparently).
Next rename po/Makevars.template
to po/Makevars
and open it up. You’ll probably want to change the COPYRIGHT_HOLDER
and MSGID_BUGS_ADDRESS
to something relevant. I usually add --keyword=i18n
to XGETTEXT_OPTIONS
because I think the default _
looks pretty ugly.
Now you need to add every file that contains translatable strings to po/POTFILES.in
. You can’t use wildcards or directory names — yuk! Something like this should do the trick:
$ find src -name '*.[ch]pp' > po/POTFILES.in
Now it’s time to regenerate the configure
script with the gettext macros:
$ aclocal -I m4 $ autoconf
If you try to run configure
now, it will complain about a missing config.sub
. Luckily automake
can add this for us:
$ automake --add-missing configure.ac:10: installing `./config.guess' configure.ac:10: installing `./config.sub'
Now run configure
in your build directory. You should see some additional NLS messages.
Next we mark some strings as translatable. Make sure you include libintl.h
in every source file with translatable strings. Surround the translatable strings with whatever you passed as a keyword
in po/Makevars
(_
is default, but I prefer i18n
). These macros aren’t defined by default, and they need to be aliases to gettext
. I usually make a header file i18n.h
like this:
#ifndef INC_18N_H #define INC_18N_H #include <locale.h> #include <libintl.h> // Macros which xgettext extracts translatable strings from #define i18n(s) gettext(s) #endif
Now add i18n
s around your translatable strings like this:
puts(i18n("Hello world"));
Before any call to gettext
you need to execute the following setup code or it won’t work (assuming you’ve included config.h
):
setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE);
LOCALEDIR
is passed to the compiler from my Makefile.am
where I have something like:
localedir = $(datadir)/locale DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@
Now we need to (re-)generate the master package.pot
file which contains all the translatable strings (where `package’ is the name of your package). We can do this with make dist
in the build directory, but the following command will update the .pot
and nothing else:
$ make -C po package.pot-update
Again, substitute `package’ for whatever your package name is. Notice that even if you run this in the build tree it will update the file in the source tree (ewww!!).
Finally, we can actually translate something. In the po
directory of the source tree run:
$ msginit -l de_DE -i package.pot
Replace de_DE
with whatever locale you wish to translate into. It’ll ask you for your email address and create a new de_DE.po
file. Open it up and you’ll see a bunch of lines with msgid
and msgstr
pairs. The msgid
is the English (or whatever language you’re translating from) and the msgstr
line is the language you’re translating into. So for our example, we change it to:
msgid "Hello world" msgstr "Hallo Welt"
It’s .gmo
files rather than .po
files that get distributed and installed. So how do we generate them? This is the bit that really confused me, and the documentation was not helpful. In the end I looked in the makefile source to see how it works. You need to create a file po/LINGUAS
listing the available locales, one per line. Like this:
# List of available locales de_DE en_GB
There might be an easier way to do this, however. Now run configure
to regenerate your makefiles and run:
$ make dist
The .gmo
files for each locale should be generated in the po
directory of the source tree.
Now if we install the package with make install
the translations should be copied to $PREFIX/share/locale
(or whatever your LOCALEDIR
was). To test it out you can run your program with a non-default LANG
environment variable — it should automagically select the correct set of strings, if a translation exists.
For example, to test our German translation we would set LANG
to de_DE.UTF-8
. You might have problems if your C library is not set up to support this locale (dpkg-reconfigure locales
on Debian).
Finally, whenever you make a change to the translatable strings in your program, run the following to update the .pot
file:
$ make -C po package.pot-update
Now in the po
directory of the source tree, merge the changes into each translation with the following command:
$ msgmerge -U en_GB.po package.pot
Your blog is interesting!
Keep up the good work!
August 15, 2008 @ 3:37 pm
Hi Nick,
Your blog is very interesting and useful, but I have a problem following this tutorial. When I try to do this: make -C po package.pot-update, it doesn’t work and show me this message: *** No rule to make target `package.pot-update’. Stop.
Do you know what it could happen?
Thank you very much
October 30, 2008 @ 9:48 am
Natalia ,
You need to replace `package’ with the name of your package (as passed to AC_INIT).
October 30, 2008 @ 8:13 pm
haha it says package pot.
November 10, 2008 @ 12:25 am