gettext with Autotools Tutorial

In this tutorial we walk through the steps needed in order to add localizations to an existing project that uses GNU Autotools as build system.

We start by taking a a slightly modified version of the Hello World example that comes with Automake sources. You can keep track of the changes to the source throughout this tutorial by following the commits to amhello-gettext on GitHub. We start with the following files:

$ ls -RF
.:
configure.ac  Makefile.am  README  src/

./src:
main.c  Makefile.am

Running gettextize

The first step is copying some necessary gettext infrastructure to your project. This is done running gettexize in the root directory of your project. The command will create a bunch of new files and modify some existing files. Most of these files are auto-generated, so there is no need to add them to your version control. You should only add those files you create or modify manually.

You will need to add the following line to your configure.ac.

AM_GNU_GETTEXT([external])
AM_GNU_GETTEXT_VERSION(0.18)

The version specified is the minimum required version of gettext your package can compile against.

Copy po/Makevars.template to po/Makevars and modify it as needed.

The next step is to copy over gettext.h to your sources.

$ cp /usr/share/gettext/gettext.h src/

libintl.h, which is the header that provides the different translation functions. gettext.h is a convenience wrapper around it which allows disabling gettext if the --disable-nls is passed the ./configure script. It is recommended to use gettext.h in favor of libintl.h.

Triggering gettext in main()

In order for gettext to work, you need to trigger it in your main(). This is done by adding the following lines to the main() function:

setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);

You should also add #include "gettext.h to the list of includes.

PACKAGE should be the name of your program, and is usually defined in config.h file generated by either autoconf or autoheader. To define LOCALEDIR we need to add the following line to src/Makefile.am:

AM_CPPFLAGS = -DLOCALEDIR='"$(localedir)"'

If AM_CPPFLAGS is already defined, just append to it the -DLOCALEDIR='"$(localedir)"' part.

Marking strings for translation

At this point, your program should compile with gettext. But since we did not translate anything yet it will not do anything useful. Before translating we need to mark the translatable strings in the sources. Wrap each translatable string in _(...), and add the following lines to each file that contains translatable strings:

#include "gettext.h"
#define _(String) gettext (String)

Extracting string for translation

Before extracting the strings, we need to tell gettext where to look. This is done by listing each source file with translatable strings in po/POTFILES.in. So in our example po/POTFILES.in should look like:

# List of source files which contain translatable strings.
src/main.c

Afterwards the following command can be used to actually extract the strings to po/amhello.pot (which should go in the version control):

make -C po/ update-po

If you haven’t ran ./configure yet you need to run autoreconf --install && ./configure before running the above make command.

Translating strings

To begin translating, you need to a *.po file for your language. This is done using msginit:

cd po/ && msginit --locale he_IL.utf8

The locale should be specified as two-letter language code followed by two-letter country code. In my example, I’ve used Hebrew, hence it will create a po/he.po file. To translate the program you edit the .po file, using either a text editor or a dedicated program (see list of editors here).

After you updated the .po file for your language, list the language in po/LINGUAS (you need to create it). For example, in my case:

# Set of available languages
he

Now you should be ready to compile and test the translation. Unfortunately, gettext requires installing the program in order to properly load the message catalogs, so we need to call make install.

./configure --prefix /tmp/amhello
make
make install

Now to check the translation simply run /tmp/amhello/bin/hello (you might need to change LC_ALL or LANGUAGES depending on your locale to see the translation).

$ LANGUAGE=he /tmp/amhello/bin/hello 
שלום עולם!

Final note about bootstrapping. When people checkout your code from the version control, many autogenerated files will be missing. The simplest way to bootstrap the code into a state which you can simple call ./configure && make is by using autoreconf:

autoreconf --install

Will add any missing files and run all the autotools friends (aclocal, autoconf, automake, autoheader`, etc.) in the right order. Additionally it will callautopointwhich copies the necessarygettextfiles that were generated when you calledgettextize` earlier in the tutorial. If your project is using ./autogen.sh script that call the autotools utilities manually, you should add a call to autopoind --force before the call to aclocal.

Finally, those are the files that end up version controlled in our example:

$ ls -RF
.:
configure.ac  Makefile.am  po/  README  src/

./po:
amhello.pot  he.po  LINGUAS  Makevars  POTFILES.in

./src:
gettext.h  main.c  Makefile.am

Refrences

Question Marks Instead of Non-ASCII Chars when using Gettext in PHP

Yesterday I’ve ported a PHP website to use Gettext for localizations (l10n). After reading through the Gettext documentation and going through the documentation in the PHP site, I’ve manged to get everything working (almost). I had one problem, all the non-ASCII characters (accented Latin chars, Japanese and Chinese) where displayed as question marks (?) instead of the correct form. This happened despite me using UTF-8 encoded files.

While some people (e.g. this one) suggested that it’s not possible to use non-ASCII characters when using a UTF-8 encoded message files, there is a solution and it’s quiet simple one. All you have to do is to call bind_textdomain_codset and pass it UTF-8 as charset.

Vim Macros for Wrapping Strings for Gettext

I’m working on a website and we decided to localize it using GNU gettext. Soon enough I found it tiring to wrap each string manually in _( and ) and also to do it in Smarty (using {t}string{/t}. So I decided that I need a macro that will let highlight the string that needs translation and the macro will wrap for me.

I ended up writing two macros one for PHP files (but it’s also good for C/C++ etc.) and one for smarty.

:vmap tg di_(<ESC>pa)<ESC>
:vmap ts di{t}<ESC>pa{/t}<ESC>

To use these macros just highlight the string for translation in vim’s visual mode and press tg (or ts), and your string will be wrapped for translation.