DEVELOPERS BLOG

How to Launch Your App in the International Market

Localization_1_1

So you launched your app in BlackBerry World for North American users and got awesome reviews. In fact, so many people in Brazil heard that your app is great that they want to download it as soon as possible. You’d love to utilize this momentum and launch the app quickly in the Brazilian market for even more success. Simple! Just translate some UI text and messages into Portuguese and launch the app in Brazil! Google translator works pretty good, right?

Well, you would be disappointed if I told you it won’t be as simple as you would think to localize the app smoothly unless you planned it from the beginning. Besides correct translation, there are various localization-specific issues that you should consider. The good news is it’s not completely impossible. BlackBerry 10 platform provides you all you need for your app to launch in the different regions in the world. With early design considerations and well executed development plans, you can quickly localize your app for the target markets and maximize the potential revenue.

Here is how you do it!

Think Internalization starting from day 1

Designing UI Elements

When designing your app, keep in mind that it will be localized into different languages. Design user interface elements with the space to accommodate localized messages and text input. When text is translated from English to other languages, it usually gets longer. Consider wrapping text in multiple lines, applying auto scroll or displaying ellipsis in localized strings, and define methods to resize horizontally and vertically for buttons, lists and labels. Your text strings should not overlap borders or the screen edge in any of your target languages. The table below shows the average growth in length when English text is translated. A 100% growth indicates that your 4 character English word is very likely to be 8 characters long in some other language.

Localization_1_2

For graphic images, avoid using cultural specific images or symbols. What is a well understood and accepted image in one culture may be inappropriate or offensive in another. Also, avoid using text in graphics. Text inside graphics is hard to localize and time consuming because you may have to redraw or recreate the image in each localized language.

It would be ideal if you design and use one user interface layout for all supported languages. Sound too good to be true? It can be achieved by identifying the target languages early and getting the translated text/strings ready during the UI design phase to test the app. That can also lead to improved communication with translators during development.

Identify Target Languages and Locales in Advance

As mentioned, the earlier you identify the target language(s) the better for designing UI and implementing your app. Decide the target regions first, then the language and locale so it becomes clearer for localization. As soon as UI strings and design are stable, send all user visible strings for translation to a professional translation vendor and provide as much information as possible with comments and disambiguation arguments. It’s always good practice to version every iteration of translation files and to keep one translation file for each target language. In my next blog article, I will guide you how to generate, translate and package the translation resources such as .ts and .qm files in detail.

Localization also involves adapting to the region specific locale formats like numbers, currency, dates, time and measurements. You can use internationalization APIs to support the many region locales available on the device, and as long as the APIs are implemented correctly, you don’t have to do anything specific to support any particular region locale. What is this mean? See the region formatting locale section below for more.

Follow the Best Localization Practices when Coding

Qt uses translation contexts by default. The standard translation methods are tr() for C++ based strings, and qsTr() for QML based strings. These methods use the default translation context. The default context is determined by the C++ class or the QML file that the string appears in.

At runtime, translations are looked-up based on a key. This key is a combination of the context and the actual source string. If you refactor, and for example, move a string from one class to another, then the context changes and the linkage to the existing translations is broken. This makes translations more fragile. For this reason, we recommend using custom contexts. Strings with custom contexts are refactor-proof.

For custom contexts, you need to use the QCoreApplication::translate (C++) and qsTranslate (QML) methods. Both of these methods accept a custom context as the first parameter and the actual source string as the second parameter. In this blog post, I will demonstrate all the C++/QML translation functions.

Wrapping UI strings: Always wrap UI strings with translation methods; otherwise the strings will remain untranslated. Identify the text in QML with qsTr()/qsTranslate() function or translate()/tr() function in C++.

BAD: AppName/assets/main.qml

import Cascades 4.0
Label {

    text: "Hello World"
}

GOOD: <ProjectName>/assets/main.qml

import Cascades 4.0
Label {
 //: Sample greeting
 text: qsTr( "Hello World" )
}

EVEN BETTER: <ProjectName>/assets/main.qml

import Cascades 4.0
Label {
    //: Sample greeting
    text: qsTranslate( "myCustomContext", "Hello World" )
}

Disambiguating strings in the same context: Use comments and disambiguation arguments to provide information to translators. You can also create separate strings for use in different contexts. Suppose you have the same string in one context, but it means two different things. You can differentiate/disambiguate the two meanings by using the optional third parameter to the QCoreApplication::translate() (C++) and qsTransate() (QML) methods:

Label *locationServicesLabel = new Label(QCoreApplication::translate("CustomContext", "On:", "refers to Location Services"));
Label *wifiLabel = new Label(QCoreApplication::translate("CustomContext","On:", "refers to Wifi"));

String concatenation: Do not concatenate strings in code; always use several QString.arg() instead. For example:

BAD

QCoreApplication::translate("myContext", "Printing page ") + i + QCoreApplication::translate("myContext", " of ") + j + "."

GOOD

QCoreApplication::translate("myContext", "Printing page %1 of %2.").arg(i).arg(j)

Note that this rule applies to punctuation too. Always include punctuation such as colons, question marks, etc. in the source string. Some languages require extra spaces between text and punctuation marks, and some languages require completely different punctuation marks.

Plural forms: Rules for plural forms are different in some languages. Generally for plural strings, pass an additional integer argument to the translation functions. For example:

BAD

int n = messages.count();
if (n == 1)
    msg = tr("You have 1 new message.");
else
    msg = tr("You have %1 new messages.").arg(n);

GOOD

int n = messages.count();
msg = tr("You have %n new message(s).", "", n);

Region formatting locale: Numbers, Currency, Dates, Times, Measurements and other locale specific formats

Note that in above example, the number is not formatted according to current locale in tr() function. For example, it won’t be rendered using Arabic numerals in Arabic locale. In order to get the correct number formatting based on the specific locale, we can use:

bb::system::LocalType::Region.

For example:

Using bb::system::LocalType::Region

int n = messages.count();
bb::system::LocaleHandler region( bb::system::LocaleType::Region );
msg = tr("You have %1 new message(s).", "", n).arg(region.locale().toString(n));

You can also listen for the changed() signal in C++ to be notified whenever the user changes the specified QNX BB10 locale. The locale() function returns a QLocale instance presenting the currently selected locale. Qt 4.8 provides set of internalization APIs to perform correct localization. QLocale class provides most of the localization APIs, while BlackBerry 10 OS supplements and extends the Qt’s APIs with libraries of its own like bbsystem, bbutilityi18n and QtCollator to bridge the gap from Qt 4.8. More details about BlackBerry 10 OS internationalization support can be found on our developer’s site.

Translation methods: Use QT_TR_NOOP or QT_TRANSLATE_NOOP if you need to mark a string for translation, but do the runtime translation later.

static const char *baseNames = {
QT_TR_NOOP("adenine"),
QT_TR_NOOP("cytosine"),
QT_TR_NOOP("guanine"),
QT_TR_NOOP("thymine")
};

QString base(int num) {
return tr(baseNames[num]);
}

Language fallback: Note that language fallback takes place. For example, if the language is set to ‘pt_BR’, then Qt will look for the ‘pt_BR’ resource. If it can’t find the ‘pt_BR’, then Qt looks for the ‘pt’ resource. If the ‘pt’ can’t be found, then Qt falls back to the untranslated strings.

Font fallback: Cascades is integrated with a text layout engine that handles complex text languages like Arabic, Thai, and Hindi, and BlackBerry OS includes fonts for most scripts. Cascades supports font fallback automatically, so if user specifies some font X to use and then requests to draw glyph Y (which is not present in font X), Cascades will automatically fallback to a font family that contains the glyph Y and is the nearest match to the user requested style and use that to draw Y.

Use Unicode encoding: Unicode is a character encoding standard that provides a unique number for every character and symbol in various languages. It is the basis for translatable software and international content creation. UTF-8 is the default encoding of BlackBerry 10 devices and developers should use UTF-8 for all source code, including translatable files, and then use QString::fromUtf8() for string literals. For more information about encodings, see the Qt documentation.

Handle Dynamic Locale Change: Your app should update the UI strings dynamically when the user changes the UI language and/or locale device changes its locale. Cascades supports dynamic translation so that when the UI language and/or the region locale is changed, the visible strings and formats get updated according to the changed locale. It could be as simple as the sample code below to translate the strings dynamically.

text: qsTr("Appointment: ") + date + Retranslate.onLocalOrLanguageChanged

Install Translator: In order for source texts to get translated, the application needs to parse in translation resources. This is typically done in the constructor of the application by loading a resource file into a translator, and then installing the translation. Then by calling “onSystemLanguage()”, the app updates UI strings/formats according to the new locale.

<ProjectName>/src/applicationUI.hpp

class ApplicationUI : public QObject
{
    Q_OBJECT
public:
    ApplicationUI(bb::cascades::Application *app);
    virtual ~ApplicationUI() { }
private slots:
    void onSystemLanguageChanged();
private:
    QTranslator* m_pTranslator;
    bb::cascades::LocaleHandler* m_pLocaleHandler;
};
<ProjectName>/src/applicationUI.cpp
#include "applicationui.hpp"
…

#include <bb/cascades/LocaleHandler>

using namespace bb::cascades;

ApplicationUI::ApplicationUI(bb::cascades::Application *app) :
        QObject(app)
{
    // prepare the localization
    m_pTranslator = new QTranslator(this);
    m_pLocaleHandler = new LocaleHandler(this);
    if(!QObject::connect(m_pLocaleHandler, SIGNAL(systemLanguageChanged()), this, SLOT(onSystemLanguageChanged()))) {
        // This is an abnormal situation! Something went wrong!
        // Add own code to recover here
        qWarning() << "Recovering from a failed connect()";
    }
    // initial load
    onSystemLanguageChanged();

…

}
void ApplicationUI::onSystemLanguageChanged()
{
    QCoreApplication::instance()->removeTranslator(m_pTranslator);
    // Initiate, load and install the application translation files.
    QString locale_string = QLocale().name();
    QString file_name = QString("CascadesProject_%1").arg(locale_string);
    if (m_pTranslator->load(file_name, "app/native/qm")) {
        QCoreApplication::instance()->installTranslator(m_pTranslator);
    }
}

For more information about translation contexts and translation methods, check out Qt documentations online: i18n source translation and internationalization.

Test! Test! Test!

Test your app early and often to make sure the localized app is acceptable. Test country and language dependencies for the app. App data and user interface for conformity should also be tested to the locale’s standards for date and time, numeric values, currency, list separators and measurements. It is always good to consult with local users and/or region experts to ensure cultural acceptance of images.

Last but not least, stay tuned for our part 2 on this topic. I will guide you through how to generate, translate and package the translation resource within QNX Momentics IDE to get you ready to launch your app in different markets. That’s it for now. I hope you enjoyed reading this blog article.

Let’s start making international savvy apps!

EK Choi

About EK Choi

EK is a member of the Enterprise Solutions Team, helping developers to create secure applications using BlackBerry solutions and services.