Meego Wiki
Views

QtInternationalization

From MeeGo wiki
Revision as of 09:25, 16 May 2011 by Andre (Talk | contribs)
Jump to: navigation, search

Contents

Internationalization

This is a developer's guide to internationalization (i18n) within the MeeGo UX project.

Localizable Strings

The most basic step in internationalization is to identify any user-visible strings in your application and replace them with code that looks up the appropriate string for the current locale. The way this is accomplished in Qt C++ code is to simply wrap the string with tr():

QString label = tr("Ring tone:");

This is accomplished in Qt Quick (QML) code by wrapping the string with qsTr():

Text {
    id: dialogTitle
    text: qsTr("Find contacts")
...

There is a Qt command-line tool lupdate that scans source code for these tr() and qsTr() macros and pulls out the identified strings. It writes them into a ".ts file," an XML-format file that lists information about where the strings were found. In UX projects, we generate these TS files automatically when creating the dist tarball that we push to OBS.

Your top-level .pro file should have lines like these:

TRANSLATIONS += .qml lib/.h lib/*.cpp
PROJECT_NAME = meego-app-example

dist.commands += rm -fR $${PROJECT_NAME}-$${VERSION} &&
dist.commands += git clone . $${PROJECT_NAME}-$${VERSION} &&
dist.commands += rm -fR $${PROJECT_NAME}-$${VERSION}/.git &&
dist.commands += rm -f $${PROJECT_NAME}-$${VERSION}/.gitignore &&
dist.commands += mkdir -p $${PROJECT_NAME}-$${VERSION}/ts &&
dist.commands += lupdate $${TRANSLATIONS} -ts $${PROJECT_NAME}-$${VERSION}/ts/$${PROJECT_NAME}.ts &&
dist.commands += tar jcpvf $${PROJECT_NAME}-$${VERSION}.tar.bz2 $${PROJECT_NAME}-$${VERSION} &&
dist.commands += rm -fR $${PROJECT_NAME}-$${VERSION} &&
dist.commands += echo; echo Created $${PROJECT_NAME}-$${VERSION}.tar.bz2

Make sure that TRANSLATIONS is set to include all files with translatable strings (you may need to include other subdirectories and .js files, for instance). Make sure that PROJECT_NAME is set to your application name.

Whenever you check in an update to OBS, you should create a clean tarball from git source. To create this tarball, run the following commands from the toplevel directory in your git tree:

$ qmake
$ make dist

Then copy the generated tarball up to OBS. Bump the version up once a week. You should never submit an "sr" for the same version twice. But you can push to OBS multiple times throughout the week without doing an "sr."

Later these TS files are translated into various languages, and each one is compiled into a .qm binary file that allows efficient string lookup at runtime. Elsewhere you need to load a translator for each .qm file in your app, but this is handled automatically by our meego-qml-launcher for QML applications, so you generally don't have to think about. You just name your .ts file with your package name and it will be handled correctly.

String Concatentation

A common pitfall for i18n is building up strings by concatenation. You do this using assumptions about grammar and order that are true for your own language, but may not be true for another language. For example, consider this code:

function getBestPepperColor() {
    return qsTr("red")
}

var message = qsTr("Tabasco is made from peppers selected using a ") + getBestPepperColor() + qsTr(" stick")

But in French, "red stick" is "bâton rouge". If you use concatenation as above, it will come out as "rouge bâton" in translation... incorrect!

So here's the solution:

var message = qsTr("Tabasco is made from peppers selected using a %1 stick").arg(getBestPepperColor())

Now the French translator will come up with something like: <source>Tabasco est faite à partir de piments sélectionnés à l'aide d'un bâton %1.</source> They are able to move the %1 around to the right place for their language.

Another example is where there are multiple variables in the string. The order of the variables may need to change in translation. This cannot happen if you're concatenating.

Wrong:

// dateHeader will be e.g. "March 14th"
var dateHeader = getLocalizedMonth() + " " + getLocalizedDay()

This looks right because the month and day are localized, but in another language maybe the day needs to come first, like "14. Марта" in Russian.

Right:

// localized date header: %1 is month, %2 is day of month
var dateHeader = qsTr("%1 %2").arg(getLocalizedMonth()).arg(getLocalizedDay())

Note: There is a problem with the tools in QML in that the translator will only see "%1 %2" so they won't know what to do with it. In Qt, you can pass a second argument to tr() to give the translator a hint, for example:

QString dateFormat(tr("%1 %2", "%1 is the month, %2 is the day of the month"));
QString dateHeader = dateFormat.arg(getLocalizedMonth(), getLocalizedDay());

Also notice that the "arg()" function on QString can take multiple arguments, unlike in QML (to my knowledge).

Translator comments

Strings like "%1 %2" cannot get correctly translated by translators without knowing some context. It is possible to add a comment intended for translators to help them.

See http://doc.qt.nokia.com/latest/i18n-source-translation.html#translator-comments for more information.

In QML, it is possible to pass a comment string as the second argument to qsTr(), but this is strongly discouraged due to the lack of documentation. Compare the two forms below:

Discouraged:

string: qsTr("%1 - %2", "This is a date range (start date - end date)")

Recommended:

//: This is a date range (start date - end date)
string: qsTr("%1 - %2")

Disambiguation of strings

Imagine an application that uses the translatable string "Read" in one place as an action (e.g. a button that enables the user to read something), and in another place uses the translatable string "Read" to mark an element that has been read. The two different meanings (and translations to other languages) require disambiguation as otherwise they will end up as the same string in the translation files and can only receive exactly one translation that will be wrong in 50% of the cases.

See http://doc.qt.nokia.com/latest/i18n-source-translation.html#disambiguation for more information how to solve this.

Plural handling

Many languages have more than one plural form (e.g. Czech has one plural form for 2-4 and another one for >=5) or complex rules (e.g. Arabic uses the singular form for amounts of 1, 11, 21, 31, ...), hence code like

n == 1 ? tr("%n message saved") : tr("%n messages saved")

will fail for these languages.

See http://doc.qt.nokia.com/latest/i18n-source-translation.html#handling-plurals for more information how to solve this.

Supported locales

The locales which must be enabled, per http://bugs.meego.com, are:

  • American English (en_US)
  • British English (en_GB)
  • French (fr)
  • Spanish (es)
  • German (de)
  • Italian (it)
  • Polish (pl)
  • Dutch (nl)
  • Russian (ru)
  • Swedish (sv)
  • Finnish (fi)
  • Brazilian-Portuguese (pt_BR)
  • Canadian-French (fr_CA)
  • Portuguese (pt)
  • Japanese (ja)
  • Korean (ko)
  • Chinese Simplified (zh_CN)
  • Chinese Traditional (zh_TW)

If in doubt of a language code, see here: http://www.transifex.net/languages/

Personal tools