Thursday, January 20, 2011

Qt translations working



Qt minimizes the performance cost of using translations by translating the phrases for each window as they are created. In most applications the main window is created just once. Dialogs are often created once and then shown and hidden as required. Once the initial translation has taken place there is no further runtime overhead for the translated windows. Only those windows that are created, destroyed and subsequently created will have a translation performance cost.
Translation files consist of all the user-visible text and Ctrl key accelerators in an application and translations of that text. Translation files are created as follows:
1.      Run lupdate initially to generate the first set of .ts translation source files with all the user-visible text but no translations.
2.      The .ts files are given to the translator who adds translations using Qt Linguist. Qt Linguist takes care of any changed or deleted source text.
3.      Run lupdate to incorporate any new text added to the application. lupdate synchronizes the user-visible text from the application with the translations; it does not destroy any data.
4.      Steps 2 and 3 are repeated as often as necessary.
5.      When a release of the application is needed lrelease is run to read the .ts files and produce the .qm files used by the application at runtime. The QM file format is a compact binary format that is used by the localized application. 
For lupdate to work successfully, it must know which translation files to produce. The files are simply listed in the application's .pro Qt project file, for example:
    TRANSLATIONS    =           tt2_fr.ts \
                                              tt2_nl.ts
This is how a simple main() function of a Qt application begins.
    int main( int argc, char **argv )
    {
        QApplication app( argc, argv );
 
        QTranslator translator( 0 );
        translator.load( "tt1_la", "." );
        app.installTranslator( &translator );
               …………
 
Line by Line Walk-through
 QTranslator translator( 0 );
Creates a QTranslator object without a parent.
        translator.load( "tt1_la", "." );
Tries to load a file called tt1_la.qm (the .qm file extension is implicit) that contains Latin translations for the source texts used in the program. No error will occur if the file is not found.
        app.installTranslator( &translator );
 
Adds the translations from tt1_la.qm to the pool of translations used by the program.
        QPushButton hello( QPushButton::tr("Hello world!"), 0 );
Creates a push button that displays "Hello world!". If tt1_la.qm was found and contains a translation for "Hello world!", the translation appears; if not, the source text appears.
 
For a translation-aware application a translator object is created, a translation is loaded and the translator object installed into the application.
    int main( int argc, char **argv )
    {
        QApplication app( argc, argv );
 
        QTranslator translator( 0 );
        translator.load( QString("tt2_") + QTextCodec::locale(), "." );
        app.installTranslator( &translator );
               …………
 
In production applications a more flexible approach, for example, loading translations according to locale, might be more appropriate. If the .ts files are all named according to a convention such as appname_locale, e.g. tt2_fr, tt2_de etc, then the code above will load the current locale's translation at runtime.
If there is no translation file for the current locale the application will fall back to using the original source text.

C++ namespaces and the using namespace statement can confuse lupdate. It will interpret MyClass::tr() as meaning just that, not as MyNamespace::MyClass::tr(), even if MyClass is defined in theMyNamespace namespace. Runtime translation of these strings will fail because of that.


Natually, it would be preferable to show "6 occurrences replaced" with an 's', and "1 occurrence replaced" with no 's'. Some developers solve this problem through code that looks like this:
    tr("%1 item%2 replaced").arg(count)
                            .arg(count == 1 ? "" : "s");
This approach works for languages like English that form their plural using 's', but as soon as we try to translate the application to languages like Arabic, Chinese, German, Hebrew, or Japanese (to name just a few), this breaks in a horrible way.
Developers who are slightly more sympathetic might write code that looks more like this:
    QString message;
    if (count == 1) {
        message = tr("%1 item replaced").arg(count);
    } else {
        message = tr("%1 items replaced").arg(count);
    }
This code is definitely more internationalization-friendly, but it still makes two assumptions about the target language:
  • It assumes that the target language has two grammatical numbers (singular and plural).
  • It assumes that the plural form should be used in the "n = 0" case (e.g., "0 items").
These assumptions hold for many of the world's languages, including Dutch, English, Finnish, Greek, Hebrew, Hindi, Mongolian, Swahili, Turkish, and Zulu, but there are many languages out there for which they don't.
Case in point: In French and Brazilian Portuguese (but not international Portuguese, interestingly enough), the singular form is used in conjunction with 0 (e.g., "0 maison", not "0 maisons"), breaking assumption 2. In Polish, there are three grammatical numbers:
  • Singular: n = 1
  • Paucal: n = 2--4, 22--24, 32--34, 42--44, ...
  • Plural: n = 0, 5--21, 25--31, 35--41, ...
For example, the Polish word dom ("house") has the paucal form domy and the plural form domów. The table below shows the rendition of "n house(s)" in English, French, and Polish for different values of n.

Qt 4.2 includes a QObject::tr() overload that will make it very easy to write "plural-aware" internationalized applications. This new overload has the following signature:
    QString tr(const char *text, const char *comment, int n);
Depending on the value of n, the tr() function will return a different translation, with the correct grammatical number for the target language. Also, any occurrence of "%n" is replaced with n's value. For example:
    tr("%n item(s) replaced", "", count);
If a French translation is loaded, this will expand to "0 item remplacé", "1 item remplacé", "2 items remplacés", etc., depending on n's value. And if no translation is loaded, the orignal string is used, with "%n" replaced with count's value (e.g., "6 item(s) replaced").
Qt Linguist and its helper tool lrelease know the specific plural rules for all the languages supported by QLocale. These rules are encoded in the binary .qm file that is generated from the .ts file, so thattr() uses the correct form based on n's value.
These rules are hard-coded in Qt Linguist and lrelease and neither the application developers nor the translators need to understand them.
Considering how easy it is to use the new tr() overload, there should be no excuse(s) anymore for not handling plural forms correctly in Qt applications.
Developers can include information about each translatable string to help translators with the translation process. These are extracted whenlupdate is used to process the source files. The recommended way to add comments is to annotate the tr() calls in your code with comments of the form:
//: ...
or
/*: ... */
Examples:
 //: This name refers to a host name.
 hostNameLabel->setText(tr("Name:"));
 
 /*: This text refers to a C++ code example. */
 QString example = tr("Example");

Additional data can be attached to each translatable message. These are extracted when lupdate is used to process the source files. The recommended way to add meta-data is to annotate the tr() calls in your code with comments of the form:
//=
his can be used to give the message a unique identifier to support tools which need it.
An alternative way to attach meta-data is to use the following syntax:
//~
This can be used to attach meta-data to the message. The field name should consist of a domain prefix (possibly the conventional file extension of the file format the field is inspired by), a hyphen and the actual field name in underscore-delimited notation. For storage in TS files, the field name together with the prefix "extra-" will form an XML element name. The field contents will be XML-escaped, but otherwise appear verbatim as the element's contents. Any number of unique fields can be added to each message.

Example:
 //: This is a comment for the translator.
 //= qtn_foo_bar
 //~ loc-layout_id foo_dialog
 //~ loc-blank False
 //~ magic-stuff This might mean something magic.
 QString text = MyMagicClass::tr("Sim sala bim.");
Meta-data appearing right in front of a magic TRANSLATOR comment applies to the whole TS file.

If the same translatable string is used in different roles within the same translation context, an additional identifying string may be passed in the call to tr(). This optional disambiguation argument is used to distinguish between otherwise identical strings.
Example:

 MyWindow::MyWindow()
 {
     QLabel *senderLabel = new QLabel(tr("Name:"));
     QLabel *recipientLabel = new QLabel(tr("Name:", "recipient"));    ...

1 comment:

  1. Super post! .. I want know about The Arabic Voice Over Company , Specialized In All Voice Over Services , a full team of voice over talents! Have you another post like Arabic Voice Over where we can get whatever you need it for, we’ll help you select the best Arabic voice for the job. Thanks.

    ReplyDelete