Mobile native Wearable native

Internationalization

Tizen provides localized resources to make your application usable for different countries. The localization is based on the Internationalization API (in mobile and wearable applications), which makes strings translatable and places them in .po (portable object) files.

Note

The .po files must be placed in the res/po directory of the application. The files can be edited using the PO File Editor provided by the Tizen SDK.

Depending on the device's locale information, your application must load the proper resource set. If no matching resource set is found for the device's current locale, the default resource set is used.

To get the localized value of a string, use the MsgID shown in the PO File Editor, prefaced with an underscore _ (for example, _(<MsgID>)). The underlying implementation calls the i18n_get_text() function to retrieve the localized value:

char *hello_text = i18n_get_text("Hello");

The hello_text variable is set to the localized text for "Hello" for the current locale of the device.

When you change the language setting on the device, the text changes in the application according to the current language.

Marking Text Parts as Translatable

The most common way to use a translation involves the following APIs:

elm_object_translatable_text_set(Evas_Object *obj, const char *text)
elm_object_item_translatable_text_set(Elm_Object_Item *it, const char *text)

They set the untranslated string for the "default" part of the given Evas_Object or Elm_Object_Item and mark the string as translatable.

Similar functions are available if you wish to set the text for a part that is not "default":

elm_object_translatable_part_text_set(Evas_Object *obj, const char *part, const char *text)
elm_object_item_translatable_part_text_set(Elm_Object_Item *it, const char *part, const char *text)

It is important to provide the untranslated string to these functions, because the EFLs trigger the translation themselves and re-translate the strings automatically, if the system language changes.

Translating Texts Directly

The approach described in the previous section is not applicable all of the time. For instance, it does not work if you are populating a genlist, if you need plurals in the translation or if you want to do something else than put the translation in elementary UI components.

It is possible to retrieve the translation for a given text through the i18n_get_text() function from app_i18n.h.

char *i18n_get_text(const char *msgid);

This function takes as input a string (that is copied to an msgid field in the .po files) and returns the translation (the corresponding msgstr field).

Note
Do not modify or free the return value of these functions. They are statically allocated.

When giving the text for a genlist item, you can use it in a similar manner as in the example below.

#include "app_i18n.h"
static char *_genlist_text_get(void *data, Evas_Object *obj, const char *part)
{
   return strdup(i18n_get_text("Some Text"));
}

Plurals

Plurals are handled through the ngettext() function. Its prototype is shown below.

char *ngettext(const char *msgid, const char *msgid_plural, unsigned long int n);
  • msgid is the same as before, that is, the untranslated string
  • msgid_plural is the plural form of msgid
  • the quantity (in English, 1 is singular and anything else is plural)

A matching fr.po file contains the following lines.

msgid "%d Comment"
msgid_plural "%d Comments"
msgstr[0] "%d commentaire"
msgstr[1] "%d commentaires"

Several Plurals

It is possible to have several plural forms. For instance, the .po file for Polish could contain the following lines.

msgid "%d Comment"
msgid_plural "%d Comments"
msgstr[0] "%d Komentarz"
msgstr[1] "%d Komentarze"
msgstr[2] "%d Komentarzy"

The index values after msgstr are defined in system-wide settings. The ones for Polish are given below:

"Plural-Forms: nplurals = 3; plural = n = = 1 ? 0 : n%10> = 2 && n%10< = 4 && (n%100<10 | | n%100> = 20) ? 1 : 2;\n"

There are three forms (including singular). The index is 0 (singular), if the given integer n is 1. Then, if (n % 10 >= 2 && % 10 <= 4 && (n % 100 < 10 | | n % 100 >= 20), the index is 1, otherwise it is 2.

Handling Language Changes at Runtime

The user can change the system language settings at any time. When that is done, the framework notifies the application, which changes the language used in the Elementary. The UI components receive a "language,changed" signal and reset their text.

This is how to handle the framework event:

static void
_app_language_changed(void *data)
{
   char *language;
   // Retrieve the current system language
   system_settings_get_value_string(SYSTEM_SETTINGS_KEY_LOCALE_LANGUAGE, &language);
   // Set the language in elementary
   elm_language_set(language);
   free(language);
}
int
main(int argc, char *argv[])
{
   ui_app_add_event_handler(&handlers[APP_EVENT_LANGUAGE_CHANGED], APP_EVENT_LANGUAGE_CHANGED, _app_language_changed, &ad);
}

The call to elm_language_set() above triggers the emission of the "language,changed" signal, which is handled the same way as the typical smart event signals.

Extracting Messages for Translation

The xgettext tool extracts strings to translate to a .pot file (po template), while msgmerge maintains the existing .po files. The typical workflow is as follows:

  • run xgettext once; it generates a .pot file
  • when adding a new translation, copy the .pot file to <locale>.po and translate that file
  • new runs of xgettext update the existing .pot file and msgmerge updates the .po files

The following example is a typical call to xgettext.

xgettext --directory = src --output-dir = res/po --keyword = _ --keyword = N_ --keyword = elm_object_translatable_text_set:2 --keyword = elm_object_item_translatable_text_set:2 --add-comments = --from-code = utf-8 --foreign-use

This extracts all strings that are used inside the _() function (as an optional short-hand for i18n_get_text()), use UTF-8 as the encoding and add the comments right before the strings to the output files).

The following example is a typical call to msgmerge.

msgmerge --width=120 --update res/po/fr.po res/po/ref.pot

Internationalization Tips

Do Not Make Assumptions about Languages

Languages vary greatly and even if you knew several of them, do not assume there is any common logic to them.

For example, with English typography, no character must appear before colons and semicolons (':' and ';'). However, with French typography, there must be "espace fine insécable", that is, a non-breakable space (HTML's &nbsp;) that is narrower that regular spaces.

This prevents proper translation in the following construct:

snprintf(buf, some_size, "%s: %s", i18n_get_text(error), i18n_get_text(reason));

The correct way to translate it is to use a single string and let the translators manage the punctuation. This means translating the format string instead:

snprintf(buf, some_size, i18n_get_text("%s: %s"), i18n_get_text(error), i18n_get_text(reason));

It is not always possible, but aim for this unless a specific issue arises.

Translations Are of Different Lengths

Depending on the language, the translation has a different length on screen. In some cases, a language has a shorter construct than another, while the situation is reversed in another case; a language may have a word for a specific concept, while another does not and requires a circumlocution (designating something by using several words).

For Source Control, Do Not Commit .po If Only Line Indicators Have Changed

From the example above, a translation block looks like:

#: some_file.c:43 another_file.c:41
msgid "Click Here"
msgstr "Cliquez ici"

In case you insert a new line at the top of "some_file.c", the line indicator changes to look like this:

#: some_file.c:44 another_file.c:41

Obviously, on non-trivial projects, such changes often happen. If you use source control and commit such changes even though no actual translation change has happened, each commit probably contains a change to .po files. This hampers readability of the change history, and in case several people are working in parallel and need to merge their changes, this creates huge merge conflicts each time.

Only commit changes to .po files when there are actual translation changes, not because line comments have changed.

Using _() as Shorthand to the i18n_get_text() Function

Since calling i18n_get_text() may often happen, it is abbreviated to _(). The Tizen SDK provides this abbreviation by default.

Proper Sorting: strcoll()

There is a string comparison tailored for sorting data for display: strcoll(). It works the same way as strcmp() but sorts the data according to the current locale settings.

int strcmp(const char *s1, const char *s2);
int strcoll(const char *s1, const char *s2);

The function prototype is a standard one and indicates how to order strings. A detailed explanation is out of scope for this guide, but use the strcoll() function as the comparison function for sorting the data set you are using.

Working with Translators

The system described above is a common one and is likely to be known to translators, meaning that giving its name (gettext) may be enough to explain how it works. In addition to this documentation, there is extensive additional documentation as well as questions and answers on the topic in the Internet.

Do not hesitate to put comments in your code above the strings to be translated, since they can be extracted along with the strings and saved in the .po files for the translator to see them.

Go to top