Chapter 27. Internationalization and Localization

Table of Contents

gtkmm applications can easily support multiple languages, including non-European languages such as Chinese and right-to-left languages such as Arabic. An appropriately-written and translated gtkmm application will use the appropriate language at runtime based on the user's environment.

You might not anticipate the need to support additional languages, but you can never rule it out. And it's easier to develop the application properly in the first place rather than retrofitting later.

The process of writing source code that allows for translation is called internationalization, often abbreviated to i18n. The Localization process, sometimes abbreviated as l10n, provides translated text for other languages, based on that source code.

The main activity in the internationalization process is finding strings seen by users and marking them for translation. You do not need to do it all at once - if you set up the necessary project infrastructure correctly then your application will work normally regardless of how many strings you've covered.

String literals should be typed in the source code in English, but surrounded by a macro. The gettext (or intltool) utility can then extract the marked strings for translation, and substitute the translated text at runtime.

Preparing your project

[Note] Note

In the instructions below we will assume that you will not be using gettext directly, but intltool, which was written specifically for GNOME. intltool uses gettext(), which extracts strings from source code, but intltool can also combine strings from other files, for example from desktop menu details, and GUI resource files such as .ui files, into standard gettext .pot/.po files.

We also assume that you are using autotools (automake and autoconf) to build your project (although autotools is not recommended for new applications), and that you are using ./autogen.sh from gnome-common or a similar autogen.sh file, which, among other things, takes care of some intltool initialization.

[Note] Note

If you are using meson (recommended), see the Localisation chapter in Meson's manual. You can then skip this section.

An alternative to gnome-common's autogen.sh may look like this:

#! /bin/sh -e
test -n "$srcdir" || srcdir=`dirname "$0"`
test -n "$srcdir" || srcdir=.

autoreconf --force --install --verbose --warnings=all "$srcdir"
echo "Running intltoolize --copy --force --automake"
intltoolize --copy --force --automake
test -n "$NOCONFIGURE" || "$srcdir/configure" "$@"

Create a sub-directory named po in your project's root directory. This directory will eventually contain all of your translations. Within it, create a file named LINGUAS and a file named POTFILES.in. It is common practice to also create a ChangeLog file in the po directory so that translators can keep track of translation changes.

LINGUAS contains an alphabetically sorted list of codes identifying the languages for which your program is translated (comment lines starting with a # are ignored). Each language code listed in the LINGUAS file must have a corresponding .po file. So, if your program has German and Japanese translations, your LINGUAS file would look like this:

# keep this file sorted alphabetically, one language code per line
de
ja

(In addition, you'd have the files de.po and ja.po in your po directory which contain the German and Japanese translations, respectively.)

POTFILES.in is a list of paths to all files which contain strings marked up for translation, starting from the project root directory. So for example, if your project sources were located in a subdirectory named src, and you had two files that contained strings that should be translated, your POTFILES.in file might look like this:

src/main.cc
src/other.cc

If you are using gettext directly, you can only mark strings for translation if they are in source code file. However, if you use intltool, you can mark strings for translation in a variety of other file formats, including .ui files, xml, .desktop files and several more. So, if you have designed some of the application UI in xml files then also add your .ui files to the list in POTFILES.in.

Now that there is a place to put your translations, you need to initialize intltool and gettext. Add the following code to your configure.ac, substituting 'programname' with the name of your program:

IT_PROG_INTLTOOL([0.35.0])

GETTEXT_PACKAGE=programname
AC_SUBST(GETTEXT_PACKAGE)
AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$GETTEXT_PACKAGE"],
                   [The domain to use with gettext])
AM_GNU_GETTEXT([external])
AM_GNU_GETTEXT_VERSION([0.17])

PROGRAMNAME_LOCALEDIR=[${datadir}/locale]
AC_SUBST(PROGRAMNAME_LOCALEDIR)

This PROGRAMNAME_LOCALEDIR variable will be used later in the Makefile.am file, to define a macro that will be used when you initialize gettext in your source code.

AM_GLIB_GNU_GETTEXT has been an alternative to AM_GNU_GETTEXT and AM_GNU_GETTEXT_VERSION, but AM_GLIB_GNU_GETTEXT is now deprecated, and shall not be used in new code.

In the top-level Makefile.am:

  • Add po to the SUBDIRS variable. Without this, your translations won't get built and installed when you build the program

  • Define INTLTOOL_FILES as:

    INTLTOOL_FILES = intltool-extract.in \
                     intltool-merge.in \
                     intltool-update.in

  • Add INTLTOOL_FILES to the EXTRA_DIST list of files. This ensures that when you do a make dist, these files will be included in the source tarball.

  • Update your DISTCLEANFILES:

    DISTCLEANFILES = ... intltool-extract \
                     intltool-merge \
                     intltool-update \
                     po/.intltool-merge-cache

  • Depending on the types of files that contain translatable strings, add code such as

    desktopdir = $(datadir)/applications
    desktop_in_files = programname.desktop.in
    desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
    @INTLTOOL_DESKTOP_RULE@

In your src/Makefile.am, update your AM_CPPFLAGS to add the following preprocessor macro definition:

AM_CPPFLAGS = ... -DPROGRAMNAME_LOCALEDIR=\"${PROGRAMNAME_LOCALEDIR}\"

This macro will be used when you initialize gettext in your source code.