[Multilingual Mobile Component] Multilingual Mobile Component -  Documentation

[Multilingual Mobile Component] Multilingual Mobile Component -  Documentation

  
Forge Component
(10)
Published on 9 Jan by OutSystems Labs
10 votes
Published on 9 Jan by OutSystems Labs

With OutSystems you can create apps using any human language. E.g. creating an app in English is as easy as creating an app in Portuguese. But what if your app needs to be both in English and in Portuguese, and it’s up to the end-user to choose the language in which to use the app?

For web-responsive apps, OutSystems already provides built-in mechanisms to localize the app. However, those same mechanisms are not yet available for mobile apps. In this document we describe a component that you can use to easily create multi-language mobile apps.

Together with the component, we’re also providing an app, MultiLingual To Do, that you can use as an example when creating your own multi-language mobile app.

At a high-level, creating a multi-language app is a two step process involving two different personas: developers and translators.

  1. The developer’s responsibility is to prepare the app to support multiple languages. One of the deliverables of this step is a list of resources that need to be translated.
  2. The translator’s responsibility is to translate the resources provided by the developer. Afterwards, the developer bundles the translations into the app.

1 Internationalization

Internationalization (or I18n) is the process of making your app ready to be translated.

In this section we describe the steps that you need to follow when using the MultiLingual component.

1.1 Add dependency to the MultiLingual module

The MultiLingual module exposes several client actions that you might need to reference: 

  • GetLocale and SetLocale - Check and set the locale at runtime.
  • GetTranslation - Translate a text literal in an expression.
  • AddTranslationsFromResource - Loads translations from a bundled resource. Use it if your app needs to work offline.
  • AddTranslations - Load translations from a list. Use it if you want to load translations dynamically, e.g. from the output of a server action.

Typically you’ll want to reference the GetLocale, SetLocale, and GetTranslation actions, and either AddTranslations or AddTranslationsFromResource depending on how you plan to store and load translations.

1.2 Add the data-trans attribute to Text widgets

You need to identify each Text widget that you want to translate by setting the data-trans attribute.

The value of the attribute is a key (a text, also known as the “resource key”.) Keys don’t need to follow a specific format, but you should ensure their uniqueness: two widgets with the same key will be translated at runtime to the same value. A simple way to avoid duplicates is to use GUIDs.

In the example below we’re setting the data-trans attribute in a Text widget.

1.3 Use GetTranslation for text literals in expressions

Text literals in client-side expressions are translated using the GetTranslation function. This function expects two arguments:

  • Key - A resource key uniquely identifying the text literal. Please follow the same rules / guidelines as described above for Text widgets.
  • DefaultValue - The original text literal, to be used if there’s no translation defined for the current locale.

In the example below we’re setting Input_DueDate.ValidationMessage. The original message was "Due Date cannot be in the past."

1.4 Keep track of resource keys / values

You must keep a list of the resource keys used in Text widgets and the GetTranslation function, together with their associated default text.  This list will be used by the translator when localizing your app. As an suggestion, you may use Excel to keep track of translations.

Using the examples in the preceding sections as a reference, this would be the information that you’d collect:

Key
Default text
3973c4fe-52c7-42ec-b556-ffa1aa634916
To Dos
419ee7c8-b26a-4b6d-ac98-7c46bf22e2ed
Due Date cannot be in the past.


1.5 Load translations

Your app needs to explicitly load translations at runtime; the MultiLingual component doesn’t do that automatically. The place where translations are loaded is app-specific, but in most cases it makes sense to load translations when the app starts (in theOnApplicationReady system event.)

The MultiLingual component offers two different ways to load translations. The one that you should use depends on where you store translations:

  • If you keep translations inside the app itself as a resource, then you should use the AddTranslationsFromResource action;
  • If you keep translations elsewhere (e.g., in a database entity), then you should use the AddTranslations action.

1.5.1 Load translations from a local resource

If you opt to bundle the translations as a resource in your app, you can use the AddTranslationsFromResource action to load them.

In the example below, we’re using the AddTranslationsFromResource action in the OnApplicationReady system event to load the translations in the ToDo.translations.json resource.

In this case we’re bundling several languages in a single resource file; if you choose to split languages throughout several files then you have to call AddTranslationsFromResource once for each file.

Check the attached PDF file for additional information on what the AddTranslationFromResource action expects as inputs.

1.5.2 Load translations from a list

The MultiLingual component also allows you to load translations from an arbitrary source, e.g. from a database entity, by using the AddTranslations action. Storing translations in a database entity is interesting for scenarios in which your app and its translations have different lifecycles, e.g. if you need the flexibility to update translations without republishing the app.

It is your responsibility as a developer to prepare the data in the format expected by theAddTranslations action.

1.6 Set current locale

The SetLocale action is used at runtime to set the desired language for the app. If you do not call it, the application will be presented in the default language, i.e., in the language in which it was designed.

By setting the locale, the next time a screen is rendered it will use the translations defined for that locale. A resource’s default value will still be used in the following two instances:

  • If no translations have been added for the locale;
  • If no translation is found for the resource key.

The place where SetLocale is called depends entirely on your particular app. As an example, you may have a Settings screen where the user can select the language you want. When the settings are saved you then call the SetLocale action.

Another alternative is to use the device’s preferred language. See the Globalization Plugin component in the Forge for an example.

2 Localization

Localization (or L10n) is the process of adding support for a particular language.

Localization involves the work of a translator (a process which is out of the scope of this document). The translator is responsible for providing a translation for each resource that has been identified by the developer.

After the translator hands off the translations to the developer, it is the developer’s responsibility to bundle them into the app. As described in the "Load Translations" section, he may opt to bundle the translations as resources inside the app, a process which is detailed below, or load them in a custom way.

2.1 Built-in validation / upgrade messages

There is a set of predefined messages that can be always translated and for which the developer doesn’t need to create resource keys: built-in validation and upgrade messages.

Check the attached PDF for a full list of keys and messages.

2.2 Storing translations as bundled resources

Add the file with the translations as a resource to your app’s main module. The resource file must follow the rules presented in the "Load translations from a local resource" section. Make sure that the resource has the “Deploy Action” set to “Deploy to Target Directory”, as in the example below.

Hello

The PDF file is now being shown to be downloaded.

Regards

I'm new to Outsystems and this resource, so I may be looking in the wrong location and I apologize for that.  But I'm not finding the PDF file of instructions.  Could you either send to me, or point me in the right direction?  Thanks!

JM Ackerman wrote:

I'm new to Outsystems and this resource, so I may be looking in the wrong location and I apologize for that.  But I'm not finding the PDF file of instructions.  Could you either send to me, or point me in the right direction?  Thanks!

Hello JM,

The PDF is attached to the 1st post, but unfortunately we're having an issue with the forums and it's not visible... This problem will be fixed soon, but in the meantime I've added the bulk of the documentation on the 1st post. Hope that helps.

Cheers,
R


JM Ackerman wrote:

I'm new to Outsystems and this resource, so I may be looking in the wrong location and I apologize for that.  But I'm not finding the PDF file of instructions.  Could you either send to me, or point me in the right direction?  Thanks!

Hey JM, 

We have fixed this issue, so you should be able to see the PDF now.


Thanks for your patience!

-Alexandra


So if I understand this correctly, the load translations call will load the translations and translate all fields with attribute data-trans set to the selected locale, i'm searching for a translation action in layout or screen but doesn't seem to be here .. so all is done by the LoadTranslations call ? And if I change runtime the translation, I should reload the correct translation to make it work on the screen ? 

Hi Wim,

The LoadTranslations action only loads the translations and stores them in memory. The actual translation happens when a screen is rendered, and that is done automatically by the component. That's the reason why you don't see any action being explicitly called to translate the text literals.

Whenever you change the translations (by calling LoadTranslations again), the new translations will be effective the next time the screen is rendered.

Hope this helps,

Hugo Lourenço


That helped a lot, I now understand the mechanics of this thing

Wim