The Sunday Blog: Multilingual GUI design

More than three ways

Inspired by a recent question from a Swiss user in the Nextion forums, I decided to explain four of the many ways which lead to Rome when it comes to localized or multilingual GUI design. There are static (design time) and dynamic (runtime) ways to create multilingual HMI applications. And, naturally, depending on your specific application, you will decide for one of these ways or for a mix of the different techniques. One trivial workaround is simply not using text but only pictograms and symbols. But this is not always recommended, there are culture gaps which may lead to ambiguous interpretations and there are legal security concerns in some countries which require unambiguous button captions in some cases. So, let’s look at three different text based solutions, the multiple sub-application, the code based translation, and the dynamic filter method.

Technical prerequisites

First, and that is the most important, finalize and debug your Nextion HMI GUI in one single default language until it is 120% perfect before you start thinking about localizing it. If not, you risk to have n times the debugging work for n languages!

Second, think well of the needed character sets. If you can remain within an ISO character set, i.e. for English, French, German, Italian, no problem. But if you need multiple character sets, think well about the characters you need. Compiling one single font (MS Sans Serif) in 24px height with the full utf8 character set gives you a 7.6Mb font resource which will already not fit into the 4Mb Flash memory of NX3224T028 HMI display. Thus, you’ll have to use the “Specified Characters” option, when you create a utf8 font resource. For this, you’ll need all translations already done with pencil on paper beforehand.

Third, whatever technique you’ll use, it will use more memory than the simple monolingual variant. Thus, keep everything as simple, economic (in terms of resources) and ergonomic.

This said, let’s look at…

The sample application

Based on a sample application (see it as a framework which you can then extend to your needs) with 3 screens, a main screen, and two sub screens with the corresponding navigation buttons, all three having a title bar, we’ll now look, how things can be realized.

Navigation is hard coded here, the Main screen (page 0) is loaded by default, its “More…” button’s event code says just “page 1” which is Sub screen 1. There the “Previous” button points to page 2 which is Sub screen 2. Its “Previous” button points to page 1, the “Ready” button to page 0. For a better understanding of this simple structure, please load the following .hmi file in the Nextion Editor and play with it in the Debugger/Simulator: multilingual1.HMI

The first way of localizing: The multiple sub-application method

This method as all the following will forcibly have to add a new page, allowing the user to select a language. This page has to be shown on startup and should be inserted before all other pages with the page index 0. And our three application pages in the default language (English) follow after that, now with indexes 1 to 3. Then, in the page pane, we copy the 3 english pages and adapt everything to be in French (indexes 4 to 6), then the same procedure for German (indexes 7 to 9) and Russian (indexes 10 to 12). Once the language selected, it will be saved in a global variable, let’s define it as set_lang in program.s. To calculate a page index, the code will also have to know how many pages there are per language (pp_lang=3) and we’ll define a t_page variable to hold temporarily the computation of the target page. This dynamisation allows later to add more languages or more screens without having to go over many hard coded navigation targets since everything is calculated dynamically. Even the language selector buttons on the start page do not have any event code, we use a TouchCap component to save the selected language index and to calculate the index of the corresponding main page.

The definitions in program.s

int sel_lang
int pp_lang=3
int t_page
page 0                       //Power on start page 0

And the event code for tc0

if(tc0.val>0)
{
  sel_lang=tc0.val-1
  t_page=sel_lang*pp_lang+1
  page t_page
}

The project .hmi file for looking into more details of this variant, especially the dynamic code for the navigation buttons, and for playing with it is here: multilingual2.HMI

Code based translation

This variant doesn’t require multiplying pages, and navigation might remain hard coded (static). It’s basically the the language selector start page and our 3 application pages in the default language. Localization is done when a page is loaded, in its PostInitialize event. The code checks for the selected language variable, and if it is > 0, thus different from the default language, the needed .txt attributes of the components are replaced.

program.s is thus much simpler:

int sel_lang
page 0                       //Power on start page 0

and the tc0 event code, too:

if(tc0.val>0)
{
  sel_lang=tc0.val-1
  page 1
}

The page structure is greatly simplified:

But for each page, there is now some code in the PostInitialize event. Here the example of sub page 1:

if(sel_lang==1)
{
  t0.txt="Sous-écran 1"
  b0.txt="Précédent"
  b1.txt="Suivant"
}else if(sel_lang==2)
{
  t0.txt="Unterseite 1"
  b0.txt="Zurück"
  b1.txt="Weiter"
}else if(sel_lang==3)
{
  t0.txt="Субэкран 1"
  b0.txt="Предыдущий"
  b1.txt="Следующий"
}

The full .hmi file, containing the code translation for all pages is here: multilingual3.HMI

The dynamic filter method

Inspired by some multilingual plugins for WordPress, I developed a simplified variant for the use with Nextion HMIs. Neither is the number of pages increased, nor do you have highly specialized and long PostInitialize code for each page. On the other side, it demands more RAM when a page is loaded because by default, each localizable component contains all languages already in its .txt attribute. And, since a utf8 character can require up to 6 bytes, you’ll have to be generous with the .txt_maxl attribute.

The idea is to set the .txt attribute of a component at design time to a string containing all language variants, with a specific separator, I use normally the vertical line. So, the txt attribute of the main screen’s title bar is for example “Main Screen|Ecran Principal|Hauptseite|Главный экран”. When a page is loaded, the universal PostInitialize event code loops through all components on this page, and if its type corresponds to a button or a text component, it replaces its .txt attribute with only the filtered part of the .txt string, making some tricky use of the spstr function and the sel_lang variable. So, we have just to copy/paste the following lines in the PostInitialize code pane and we are almost done. A temp variable used in the for() loop is globally declared in programs.s. We’ll just have to adapt the end value of the outer for() loop, there is no way to know the total number of components on a page at runtime.

for(temp=1;temp<3;temp++)
{
  if(b[temp].type==98||b[temp].type==116)
  {
    spstr b[temp].txt,b[temp].txt,"|",sel_lang
  }
}

And again, the full .hmi file to play and experiment is here: multilingual4.HMI

And now, happy localizing and happy Nextioning!