Trolltech | Documentation | Qt Quarterly | « Fields on Forms | Laying out MDI Children »

Internationalization Q & A
by Jasmin Blanchette
This article provides answers to some frequently asked questions about internationalizing Qt applications. It covers such topics as distributing translation files, reverse translations, and dynamic language switching.

How can I distribute my application's .qm translation files?

The standard way to distribute Qt's binary translation files (.qm files) is to install them in a directory on the user's machine and to load them at run-time. However, if the application can't locate the .qm files, the application will run with no translation.

To avoid this problem, you can embed the .qm files in the application's executable using the qembed tool, which you'll find in Qt's tools directory. For example:

    qembed myapp_de.qm myapp_fr.qm > qm_files.h
    

The qembed tool generates a static const array of data that you can then include in your application like this:

    #include "qm_files.h"
    
    int main(int argc, char *argv[])
    {
        ...
        QTranslator translator;
        translator.load(myapp_de_qm_data, myapp_de_qm_len);
        app.installTranslator(&translator);
        ...
    }
    

The load() function used here is an overload that accepts raw .qm data instead of a file name. It is new in Qt 3.2.

The qembed tool must be run every time the .qm files change to keep the qm_files.h file up to date. In a future version of Qt, this process may be integrated into qmake, which already supports the embedding of images through its "image collection" mechanism. (For more information on image collections, see the Qt Quarterly article Iconography 101 in issue 5.)
Most of our application was developed using Qt Designer. We would like to give the Qt Designer .ui files to the translator together with Qt Designer, but we can't find a way to specify a translation file in Qt Designer. Is there a way to preview .ui files with a translation?

Neither Qt Designer nor Qt Linguist allow you to preview .ui files with a translation, but it's fairly easy to write a previewing tool using QWidgetFactory, a class that dynamically constructs a widget based on a .ui file:

    #include <qapplication.h>
    #include <qtranslator.h>
    #include <qwidgetfactory.h>
    
    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
    
        if (argc < 3) {
            qWarning("Usage: lpreview " "file.qm form1.ui...");
            return 1;
        }
    
        QTranslator translator;
        translator.load(argv[1]);
        app.installTranslator(&translator);
    
        for (int i = 2; i < argc; ++i) {
            QWidget *widget = QWidgetFactory::create(argv[i]);
            widget->show();
        }
    
        QObject::connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
        return app.exec();
    }
    

The lpreview tool takes a .qm file and the .ui files to preview as command-line arguments. To compile the example, you'll need to link against Qt's qui library. This means adding this line to lpreview's .pro file:

    LIBS         += -lqui
    

This LIBS line works cross-platform since Qt 3.2. If you use an older version of Qt and want the .pro file to work on Windows, you'll need to write this instead:

    unix:LIBS    += -lqui
    win:LIBS     += $(QTDIR)/lib/qui.lib
    

In our application, we need look up strings in translation files for different languages simultaneously. But tr() always seems to return a translation for the language that was installed last. Can't you add a "language" parameter to tr() that would allow us to specify which .qm file to use for the translation?

The tr() mechanism is based on the idea that only one language is active at a time, but it's entirely possible to load multiple .qm files in distinct QTranslator files and to call QTranslator::findMessage() to look up a string instead of tr(). For example:

    QTranslator thaiTranslator;
    thaiTranslator.load("myapp_th.qm");
    QTranslator urduTranslator;
    urduTranslator.load("myapp_da.qm");
    
    thaiStr = thaiTranslator.findMessage("MainForm", "Help").translation();
    urduStr = urduTranslator.findMessage("MainForm", "Help").translation();
    

We would like to store our translations in a database rather than in .qm files. How can we make tr() aware of the database?

The solution is to subclass QTranslator and reimplement the findMessage() function. For example:

    class DBTranslator : public QTranslator
    {
    public:
        QTranslatorMessage findMessage(const char *context,
		const char *sourceText, const char *comment);
    };
    
    QTranslatorMessage DBTranslator::findMessage(const char *context,
	    const char *sourceText, const char *comment)
    {
        QSqlQuery query;
        query.prepare("SELECT translation FROM message "
	              "WHERE context=? AND sourcetext=? AND comment=?");
        query.addBindValue(context);
        query.addBindValue(sourceText);
        query.addBindValue(comment);
        query.exec();
        if (query.next()) {
            return QTranslatorMessage(context, sourceText, comment, query.value(0).toString());
        }
        return QTranslatorMessage();
    }
    

In main(), you would then write something like this:

    int main(int argc, char *argv[])
    {
        ...
        DBTranslator translator;
        app.installTranslator(&translator);
        ...
    }
    

My application contains the German text "Bestštigen", but when I load the .ts file in a text editor, it is shown as "Best√tigen". Can you fix lupdate so it understands German characters correctly?

The .ts ("translation source") XML files generated by lupdate use the UTF-8 Unicode encoding. This encoding is the default 8-bit encoding for XML, but is incompatible with other popular encodings, such as ISO 8859-1 (Latin-1) in Western Europe.

To convert a .ts file to another encoding, it isn't sufficient to read it in using one encoding and save it using another encoding. We must also prepend an XML declaration node to the saved file:

    <?xml version="1.0" encoding="ISO-8859-1"?>
    

We can write a tool to automate the conversion:

    int main(int argc, char *argv[])
    {
        if (argc < 3) {
            qWarning("Usage: lencode encoding file1.ts...");
            return 1;
        }
        QTextCodec *codec = QTextCodec::codecForName(argv[1]);
        if (!codec) {
            qWarning("Unknown encoding: %s", argv[1]);
            return 1;
        }
        for (int i = 2; i < argc; ++i)
            encodeFile(codec, argv[i]);
        return 0;
    }
    

The lencode tool uses its first command-line argument as the encoding name, and converts the files named in the other arguments to the given encoding. The real work is performed by the encodeFile() function:

    void encodeFile(QTextCodec *codec, const char *fileName)
    {
        QFile file(fileName);
        QDomDocument doc;
    
        if (!file.open(IO_ReadOnly | IO_Translate))
            ; // handle error
        if (!doc.setContent(&file, true))
            ; // handle error
    
        if (doc.firstChild().isProcessingInstruction() && doc.firstChild().nodeName() == "xml")
            doc.removeChild(doc.firstChild());
    
        QDomNode node = doc.createProcessingInstruction("xml",
                QString("version=\"1.0\" encoding=\"") + codec->mimeName() + "\"");
        doc.insertBefore(node, doc.firstChild());
    
        file.close();
        if (!file.open(IO_WriteOnly | IO_Translate))
            ; // handle error
        QTextStream out(&file);
        doc.save(out, 4);
    }
    

The encodeFile() function loads the XML into a DOM tree in memory. Then it removes any existing <?xml?> node at the beginning of the file and adds a new <?xml?> node with the required encoding. At the end, it calls QDomNode::save() to save the DOM tree to disk. QDomNode::save() uses the encoding specified in the <?xml?> tag for saving if such a tag is present; otherwise, it falls back to UTF-8. (We've omitted the error-handling code to save space.)
How can I make my application change language while it is running?

It would be nice if any Qt application would react immediately when the user changes the locale settings on Windows without requiring any effort from the Qt developer. Unfortunately this isn't possible because Qt's widgets don't have access to the original string, but only to the translated string. For example, the string

    tr("Host %1 found").arg(host)
    

might be seen as "HŰte www.troll.no trouvť", and from that string it is virtually impossible for Qt to discover that it comes from "Host %1 found" and translate it further to "Rechner %1 gefunden".

Nonetheless, dynamic language switching is fairly easy to achieve in Qt applications. The basic idea is to create a function that sets all the strings for a form. For example:

    MyDialog::MyDialog(QWidget *parent, const char *name)
        : QDialog(parent, name)
    {
        label = new QLabel(this);
        lineEdit = new QLineEdit(this);
        label->setBuddy(lineEdit);
        okButton = new QPushButton(this);
        connect(okButton, SIGNAL(clicked()), this, SLOT(accept()));
        retranslateStrings();
    }
    
    void MyDialog::retranslateStrings()
    {
        label->setText(tr("&Name:"));
        okButton->setText(tr("OK"));
    }
    

Then you need to call retranslateStrings() whenever you want to retranslate the form (for example, on a LanguageChange event). The "Internationalization" chapter of C++ GUI Programming with Qt 3 discusses this topic in depth.
We create a combobox with the items tr("Yes") and tr("No"). Thanks to the tr() function, the strings are easily translated. But other parts of our software expect the strings "Yes" and "No" in English. Does Qt provide a way to do the reverse mapping from tr("Yes") to "Yes"?

Yes. Although QTranslator provides no direct API for performing reverse lookups, you can call QTranslator::message() to obtain a list of the entries stored in the .qm file and navigate through the list until you find an entry with the right translation:

    QCString reverseLookup(const QTranslator &translator,
			   const char *context, const QString &translation)
    {
        typedef QValueList<QTranslatorMessage> MessageList;
    
        MessageList messages = translator.messages();
        MessageList::iterator it = messages.begin();
        while (it != messages.end()) {
            if ((*it).translation() == translation && (*it).context() == QCString(context))
                return (*it).sourceText();
            ++it;
        }
        return "";
    }
    

Note that this only works on uncompressed .qm files. The QTranslator::message() function's return value is undefined for compressed .qm files. To generate uncompressed .qm files, invoke lrelease with the -nocompress option. This option is new in Qt 3.2.


This document is licensed under the Creative Commons Attribution-Share Alike 2.5 license.

Copyright © 2003 Trolltech Trademarks Laying out MDI Children »