Solution for Multilingual App

Hello Backendless Community,

Here is the solution I implemented to make my app multilingual.

It supports:

  • User selecting a language
  • Language translations for UI (page data)
  • Language translations for object data (table data)

I didn’t yet implement saving/remembering the user’s preferred language for future sessions. That would be pretty straightforward to do.

In my solution, app administrators manually enter translations in the backend. We usually enter translations in the database directly, but I also created pages for admin users to manage translations via custom UI. My solution is not one that translates content using an external tool at runtime such as Google Translate. All translation logic is in the app.

My app currently supports English and Spanish, but this solution can support any number of languages. I followed the ISO code standard for languages: en, es, etc… One can easily support country-level variants such as en-CA, es-MX, etc. if desired.

In my logic, English (en) is considered the default language. This means that whenever a localization for a given language is not defined, the logic will return the English value as a fallback.

-BACKEND-

For UI Translations…

I store localization definitions for app UIs (pages and components) in a database table, which looks like this:

I personally use a key-value structure, and prefix “LOCALIZATIONS_” for keys to differentiate, because I happen to be using this table for a number of configuration elements. You’re welcome to set up a dedicated table for localizations as desired.

I use “LOCALIZATIONS_[Page or Component Name]” as a naming convention to retrieve the JSON definition of localizations for a given UI.

The JSON content per entry is a collection that looks like this:

{
“back”: [
{
“value”: “BACK”,
“language”: “en”
},
{
“value”: “VOLVER”,
“language”: “es”
}
],
“exit”: [
{
“value”: “EXIT”,
“language”: “en”
},
{
“value”: “SALIDA”,
“language”: “es”
}
]
}

“back” and “exit” are keys that the UI uses to retrieve a localized string value.

For object data translations…

I add a JSON column named “localizations” to any table that requires translated properties.

For example, consider a Sport table with columns: objectId, name (string), description (string), localizations (JSON).

The content in the localizations column provides translations for the name and description properties:

{
“name”: [
{
“value”: “Baseball”,
“language”: “en”
},
{
“value”: “Béisbol”,
“language”: “es”
}
],
“description”: [
{
“value”: “Baseball is a bat-and-ball game played between two teams of nine players each, that take turns batting and fielding.”,
“language”: “en”
},
{
“value”: "El béisbol es un juego de béisbol entre dos equipos de nueve jugadores cada uno, que se turnan para batear y lanzar. ",
“language”: “es”
}
]
}

-FRONTEND-

The user selects their preferred language using a Select component the app header, which is a reusable component. The components options are value/label pairs en/English and es/Español.

image

When the user selects a language, I set the session language in App Data using a custom function to ensure that the selection applies to all pages.

image

setSessionLanguage custom function is simple:

When my app starts, I call setSessionLanguage to set the language to “en”, which is our default language. The user can then change it after.

I also made a corresponding getSessionLanguage function:

To retrieve localizations in UI Builder, I created two custom functions:

  • getLocalizedUIValue: to get a localized value for a UI component such as a Text component
  • getLocalizedObjectPropertyValue: to get a localized object property value such as the name property of a Sport

image

The custom function implementations are:

All the logic is encapsulated into 4 convenient custom functions that can be called from anywhere in my app to display translated content. It all works really well.

Of course, there’s always room for improvement, so please feel free to share feedback.

Hope this helps!

Marc

3 Likes

Great job, thanks !

Thank you @Marc_Chriqui for sharing this!

Btw, the UIBuilder team is researching/experimenting with different approaches for I18N in UIBuilder apps, so I believe in the nearest future we will be able to introduce some easier way to enable translations without any extra logic

Regards, Vlad

2 Likes

Hi @Marc_Chriqui,

Thanks to your guidance, I was able to build a nearly bilingual app, thanks a bunch !

I was coming back to this thread wondering how you had dealt with specific components, namely emails. Have you been able to send confirmation emails for registration or password reset emails in the user’s language ? And if so, how did you manage ? Were you able to somehow localize the email template itself ? Or did you create several email email templates ? If so, how is it possible to replicate the link generations in a custom template ?

@Team : how would you suggest I best implement this ? Thanks !

Nicolas Remy

1 Like

Great! I’m currently only supporting two languages so I took the simple approach of creating an email template for each language. To support many languages, I would create one email template and store, retrieve, and pass in localized strings (or complete html) to it similar to what I’ve done above with UIs and objects.

Hi, @Nicolas_REMY

The option suggested by @Marc_Chriqui is good. I would suggest the same thing, create template values (dynamic text) where you can add data dynamically.

https://backendless.com/docs/rest/email_templates.html

Regards,
Marina

There is a little-known feature that might be helpful here. When a user registers with your app with the User Registration API, Backendless stores their locale in the blUserLocale column in the Users table. You could use that value to determine the language of the email or push notification that should go out. We plan to make that process more automated where you will be able to provide the same email template in multiple languages and map the templates to the locale codes. Backendless will use the mapping to send out the right “version” of your template.

2 Likes

Hi @mark-piller ,

I am indeed using the blUserLocale field and it works great throughout the app. Would love to see the template mapping feature for emails.

Because, while I do understand the fact that I can feed generated content as parameters, it would be quite cumbersome. And in any case, I have not found a way to add a new variable field in the system templates. Or is it enough to type text in between curly brackets ? Because I have tried and it does not appear as a new variable. So the solution suggested by @Marina.Kan does not seem to apply to system templates - unless I am mistaken.

On the contrary, if I create several custom templates, then I am missing the dynamic links created by the system, such as the password reset link {change_link}. If I have to reimplement the whole password reset logic in the backend it also defeats the purpose.

Hence my question on what the best course of action would be.

Wondering if there had been any progress here.

Indeed I can’t think of a solution to send the password reset email in the user’s language.

Is there a way to access the {change_link} or the token somehow ?

Indeed I can’t think of a solution to send the password reset email in the user’s language.

You could create your custom template and API service. When a client requests a password reset you make a call to your service with a language as an argument, and the service creates content that depends on it. After that, you could send an email template with the needed content language.

Indeed, @Dima, I understand this.

Note that it is really cumbersome to rebuild this whole logic for a basic feature of user management, but OK, suppose that is the way to go.

There are still a few hurdles that I can’t seem to grasp, though.

  1. When my own Cloud Code logic will make the call to the “Recover user password” block, then an email will still go out to the user, containing the {change_link} variable, and that email will be in a given language. I currently can’t seem to disable sending that email, otherwise the other “User requests password recovery” email will be send instead, and that’s also in one language only.

  2. Suppose I find a way of making sure that no “system” email is sent. I could replace those with my own, in the user’s language. But that new custom email would have to include the {change_link}. How can my Cloud Code logic access this link, or at least the token in order to rebuild the link ?

I understand I could build some logic. It doesn’t make sense, because this is basic user management, but I could. However, those two items still make it impossible for me to design a working process.

  1. When my own Cloud Code logic will make the call to the “Recover user password” block, then an email will still go out to the user, containing the {change_link} variable, and that email will be in a given language. I currently can’t seem to disable sending that email, otherwise the other “User requests password recovery” email will be send instead, and that’s also in one language only.

You don’t need to use this block, as you say at the start of your message, this approach requires rebuilding full recovering logic. You must make all with your hands: reset password, send an email, create a restoring page.

  1. Suppose I find a way of making sure that no “system” email is sent. I could replace those with my own, in the user’s language. But that new custom email would have to include the {change_link}. How can my Cloud Code logic access this link, or at least the token in order to rebuild the link ?

If you use your custom email template, you could pass any identity from the client to your API service and create the link which you want.

I’m afraid I understand what you are talking about and it’s even worse than I thought.

Are you meaning to say that because the custom API call should not use the “Recover user password” block, then no token is created, and I would also have to recreate the token logic and store this in the database, and manage its validity ?

From what I understand, this also introduces a security weakness, because it’s right there in the database and allows anyone getting hold of this token to connect and access all the user’s data. Any API call getting the user will return this user’s token along…

It seems to me that we are talking about :

  1. rebuilding a significant portion of the user logic
  2. weakening security

And all this just to be able to send an email in the user’s language !

Could you not look into making it perhaps more usable instead ?