The K Desktop Environment

Chapter 2. Overview

2.1. Internationalizing applications

An important goal of KDE is to make applications usable by people in many countries. Internationalization (often abbreviated as i18n, which stands for "I", then eighteen letters, then "n") comprises preparation for the translation of labels and messages appearing in the user interface, support for input, output and string manipulation in various character sets, support for different date, time and currency formats. In the end, a program should be able to run unmodifiedin any linguistic environment with any set of cultural conventions.

Thanks to their foundation Qt 2.x, KDE programs by default use the powerful QString class for all user-visible strings. As QString uses Unicode internally, in most cases you do not have to worry about unusual charsets. Whereever you have to use a local encoding (e. g. for filenames), Qt provides the QTextCodec class for conversion.

In the following we discuss how you prepare a program for the translation of its user interface. If you are a programmer of an application in the KDE CVS, you have luck, and you only have to care for marking all translatable strings. The translation itself will be taken care of by an army of diligent daemons known as the KDE Translation Team. If you are maintaining an application yourself, you have to take a look at these issues involved from the translator's point of view as well.

2.1.1. From the programmer's point of view

KDE programs normally use a translation system based on a modified form of the gettext package which is part of glibc on Linux systems. Although this is not mandatory, it is recommended because it is very easy to use for the programmer and it is the standard for open source projects. If you take care of enclosing all translatable strings in the source code with a call to i18n() (and include the header file klocale.h), you are almost done. For example, in order to construct a label within a dialog, write:

   1 #include <klocale.h>
   2 ...
   3 QLabel *lbl = new QLabel(i18n("File name:"), this);
   4             

The i18n() function has two aspects: The first is, it takes an argument of type const char *, looks it up in its dictionary - according to the user's preferred language, as set in the control center - and returns a QString with the translation. As a technical sidenote, note that the lookup is in fact very fast, and its memory usage is optimized by mapping a binary translation file directly into memory. Also, as the result of the function is an implicitly shared QString object, you do not have to worry about superfluous deep copies of the string.

The other aspect of this function is that is marks the strings to be translated in the source code, so they can be automatically extracted with the program xgettext. In order to use it, put this rule into the Makefile.am of your foo package:

   1 messages:
   2 	$(XGETTEXT) -C -ki18n -x $(kde_includes)/kde.pot -o $(podir)/foo.pot $(foo_SOURCES)
   3             

Executing this make file target will result in a file foo.pot, usually in the directory po unter the toplevel directory of your package. This file will have entries like the following:

   1 #: foo.cpp:3 
   2 msgid "File name:" 
   3 msgstr ""
   4             

Often you want to display a string which contains some elements which are only known at runtime. The naive approach of internationalizing it could look like:

   1 QString filename;
   2 ...
   3 filename = ... // get a file name from the user or from somewhere else
   4 if (!QFileInfo(filename).exists()) {
   5     QMessageBox::information(this, "Foo", i18n("File ") + filename + i18n(" does not exist.));
   6 }
   7             

Do not produce word puzzles like this! It makes the translator's life unnecessarily hard. It is better to use phrases which make sense for themselves and substitute the variable element with the arg() method of QString:

   1 QString filename;
   2 ...
   3 filename = ... // get a file name from the user or from somewhere else
   4 if (!QFileInfo(filename).exists()) {
   5     QMessageBox::information(this, "Foo", i18n("File %1 does not exist.").arg(filename));
   6 }
   7             

The method arg() takes substrings of the form "%n", where n is a number between 0 and 9, and replaces the one with the lowest number with its argument. For convenience, this method is overloaded for integers and floats, so in contrast to c-like functions like sprintf(), it is typesafe. It also has the important advantage that when you have several insertions in a string, the translator has the freedom the change their order. For example, consider the english phrase

Printed %1 pages of %2 total.

A german translator could now use as a translation

Von insgesamt %2 Seiten wurden %1 gedruckt.

Sometimes, it turns out that a word which must be translated is ambigous, i.e. it has several meanings in the original (which is normally english). For example, you may have a button with the label "Set" on it. One possible meaning is "A set of elements" which a german translator would substitute with "Menge". Another meaning is "Set an option" which a german translator would substitute with "Setzen". Obviously, the i18n() described above can not provide different results for the same phrase in different contexts. A way around this is an overloaded two-argument version of this function. The second argument is the untranslated string, which is used for english users. The first argument only serves the purpose of distinguishing different meanings of the untranslated string. In the above example, you can use

i18n("A set of", "Set")

for one occurance of "Set" and

i18n("to set", "Set")

for the other one.