2.1. The i18n header file

Throughout this tutorial, the goal is always to keep your application buildable. Since you are about to wrap strings in some new function calls, you need to make sure your program knows about these functions. Then you initialise the i18n code at the beginning of your application and go through the code marking up strings.

2.1.1. The international header file

Create a new header file which will contain the various i18n macros. The following listing is a reasonable example to follow.

Example 1. slice-i18n.h

#ifndef __SLICE_INTL_H__
#define __SLICE_INTL_H__

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#ifdef ENABLE_NLS
#include <libintl.h>
#define _(String) gettext(String)
#ifdef gettext_noop
#define N_(String) gettext_noop(String)
#else
#define N_(String) (String)
#endif
#else /* NLS is disabled */
#define _(String) (String)
#define N_(String) (String)
#define textdomain(String) (String)
#define gettext(String) (String)
#define dgettext(Domain,String) (String)
#define dcgettext(Domain,String,Type) (String)
#define bindtextdomain(Domain,Directory) (Domain) 
#define bind_textdomain_codeset(Domain,Codeset) (Codeset) 
#endif /* ENABLE_NLS */

#endif /* __SLICE_INTL_H__ */

Note

For the sake of examples in this tutorial, I am going to be working with a mythical project called Slice-n-Dice. I do not know what this application does, but it is definitely going to be available to an international audience.

Naturally, you may want to call your file something other than slice-i18n.h. You should not change the ENABLE_NLS variable's name, though, since that has special meaning when we come to Section 3, “Incorporating i18n into the package's build infrastructure”.

If your application has libgnome as a dependency, then you can avoid creating your own i18n support file by just including the <libgnome/gnome-i18n.h> file instead of slice-i18n.h (or your replacement name). I am including the above for the benefit of application developers working lower down the metaphorical food chain.

For library developers, some modifications to the above file may be necessary. The following sections still apply, but you may wish to skip ahead to Section 2.2.1.1, “Special requirement for libraries” to find out the couple of lines that need to be changed here.

In a moment, I will describe the purpose of each of those functions, but the important point from a developer point of view is that you are about to start wrapping strings up in _() and N_() calls. If you use those functions in a source file, you need to make sure you have included the above header file. Of course, failure to do this will be fairly obvious; your application simply will not build, since a few functions will be undeclared.

2.1.2. Initialising the i18n support code

In case you do not have an intimate understanding of GNU's gettext application, here is a brief explanation of the big picture before we get into the actual code changes you need to make. I am glossing over some details here and mostly concentrating on the parts that are relevant to GNOME. If you want to read all the fine print, the gettext manual (see [gettext]) is excellent.

The collection of all translated strings on your installation is divided into different message domains. Almost always, each separate application or library is a different domain and the domain is usually named after the package. The strings for a given domain are compiled into an efficient binary format (one file for each locale) at install time and installed along with the package. Not surprisingly, we need to tell our program where these files are installed so that it can look them up when required.

A single application may not always return messages from just one domain. Obviously, all its own messages will come from the application's domain, but it may also call library functions that return or display translated strings and they will most likely come from the library's own domain. So facilities exist in the C library to change the currently active domain (of which there can only ever be one).

A final point of significance is that translated strings have to be encoded somehow when they are passed around. ASCII or ISO8859-1 encodings, which are common for English-speaking locales are not appropriate if you are working in Chinese or Russian. By default, gettext knows which is the most natural encoding for a given locale setting and will return the strings in the appropriate encoding. However, throughout GNOME all strings are encoded in UTF-8 so you end up having to re-encode the strings from their native encoding to UTF-8. Fortunately, we can tell the C library this and it will not bother doing the locale-specified encoding, which saves some time on each and every string lookup.

The last three paragraphs were just an extremely verbose way of describing what the following three lines of code do. These should be just about the first three lines of code you put in the main() function of your application, since you really want to be ready to handle user visible strings as early as possible. If you are writing a library that will be used by client applications, you need to put the first two lines from here into your library's initialisation function.

bindtextdomain (GETTEXT_PACKAGE, SLICELOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);

I should explain what is happening here: The GETTEXT_PACKAGE variable is defined by your build process (usually in configure.in or configure.ac) and is the name of the message domain for this application. So its value can be set to the name of your program. The SLICELOCALEDIR variable (the name is usually based on the application name, but that is entirely arbitrary and up to you) is defined at build time and is the root of the directory hierarchy that will store the message catalogs. The compiled message catalogs will be installed into the directory SLICELOCALEDIR/locale/LC_MESSAGES/GETTEXT_PACKAGE.mo.

Note

In general, the LC_MESSAGES component can be any category name (see the locale(7) manual page for more explanation). In GNOME, you will only have to deal with LC_MESSAGES, so I will ignore the others.

The above code fragment, then, tells your application

  • where to find the message catalogs;

  • to use UTF-8 for the encoding for all strings;

  • initially, at least, translate using strings from this application's domain.

This last item is no good for a library (the client application controls the active domain most of the time), so you do not need to do it there. Instead, a library has to set the message domain before retrieving one of its own strings and then restore it against afterwards (more details on this later).

It should be noted in passing that the if you are using an i18n header file like the one in the last section, the above code fragment will compile regardless of whether you have i18n support available or not. That is the benefit of the header file from the previous section: once you have set that up, you can deal with the rest of the code as though i18n support is always available and it will be just be built as an empty operation if the support is not present.

Note

As I was writing this I noticed that the libgnome/gnome-i18n.h header file does not define bind_textdomain_codeset() in the case when ENABLE_NLS is not defined. So your package may need to treat that function (only) as a special case if you are reusing code from libgnome.