B4J Tutorial [ABMaterial] 1.09 Localization using dynamic pages

This is the second part of the tutorial on some big changes I'm making to ABMaterial in 1.09. It should be fully backwards compatible so no (or very, very little) changes will be needed from your side on your existing projects if you stay working the pre-1.09 way.

Read Part 1 as it gives a introduction on dynamic pages:

PART 2: Localization
If you live in a country like me (Belgium), when developing an app you are immidiately confronted with the fact you have to write your app for different languages. This topic was raised in the forum too.

I've created a framework within ABMaterial to cope with this and some helpful methods to do your translation.

As described in Part 1, dynamic pages was a must-have if you want to do localization. You can of course run 3 versions of your app, but that is not the ABMaterial way ;).

So let's go over the code steps we need to do to localize an ABMaterial web app.

Steps while you are programming:
1. For every term you want to be translatable, you'll use the page.XTR("Code", "DefaultText") method.

"code": I've found it useful to use a number, like '0001', '0002' etc. This maps the value between languages. Note that you can re-use codes on a per/page base. Meaning you can use a '0001' code in a page and a '0001' code in a module, having a different value. This makes it easier to code as you don't have to remember what the last code was you used throught the whole program, just per class, module etc...

"defaulttext": This has a double purpose. First, it makes your code still 'readable' as a coder. It looks very similar as if you used the string itself. Second it handles as a backup if it does not find a translation for this code.

page.NavigationBar.AddSideBarItem("Contacten", page.XTR("0001" , "Contacten" ), "mdi-action-dashboard", "../StartPage")

combo1.AddItem("combo1S0", page.XTR("0005","Alles"), BuildSimpleItem("S0", "mdi-action-account-circle", "{NBSP}{NBSP}" & page.XTR("0005","Alles")))

page.ShowToast("toast" & myToastId, "toastred", page.XTR("0014","Geen referenties gevonden"), 5000)

2. Done! :) Now, every time you run your WebApp in the IDE, your B4J code will be analysed and a couple of files will be generated:

a. BaseTranslations.lng
This file contains all the terms that need a translation. Here is an example of part of an extracted file for one of my own projects:
abmshared_0004;Prospecten in de buurt
prospectpage_0002;Rond huidige locatie
prospectpage_0003;Rond adres
prospectpage_0004;Volledig adres
prospectpage_0061;Kies een sector
prospectpage_0008;All Energy
prospectpage_0014;Geen referenties gevonden
prospectpage_0020;Geen referenties gevonden (GPS?)
prospectpage_0021;Connectie verloren. Herlaad de lijst

Pretty nice,no?

The observant reader will have noticed there are two abmshared_0001 codes. This is where the second file comes in:

b. BaseTranslations.optimizable
This file contains an error check and some hints on how you can optimize your translations.

The frist part shows you some optimizations per/page. Meaning it will not report if you've used two times 'ja' accross pages. I had to find a balance between speedy coding and cross page term use. I choose for coding speed. In the example, codes 0028 and 0024 both say 'Onderwerp' in the prospectpage, so we can actually re-use 0024 instead of 0028.

The second part will show where you have made an error. You're using the same code for two different terms.

Here is an example of such a file:
[Suggestions where you can re-use existing translations]
prospectpage_0028 'Onderwerp' -> prospectpage_0024
prospectpage_0029 'Commentaar' -> prospectpage_0025
prospectpage_0036 'Sluiten' -> prospectpage_0026
prospectpage_0047 'Sluiten' -> prospectpage_0026
prospectpage_0048 'Onderwerp' -> prospectpage_0024
prospectpage_0050 'Commentaar' -> prospectpage_0025
prospectpage_0051 'Annuleren' -> prospectpage_0030
prospectpage_0052 'Bewaren' -> prospectpage_0031
prospectpage_0054 'Ja' -> prospectpage_0033
prospectpage_0055 'Nee' -> prospectpage_0034
prospectpage_0056 'Gelieve eerst alle velden in te vullen!' -> prospectpage_0035
prospectpage_0057 'Sluiten' -> prospectpage_0026
prospectpage_0059 'Ja' -> prospectpage_0033
prospectpage_0060 'Nee' -> prospectpage_0034
[Duplicate use of the same translation code withing a B4J file]
abmshared_0001 used for both: 'Contacten' AND 'Referenties'

'Looks nice Alain, but how the hell do we use it?', I hear you ask...

Well, once you have created your program for one language, you can start thinking about translating it.

You can give BaseTranslations.lng to a translator. e.g. Dutch->English. Once he returns it, create a folder 'translations' in your /www/appName/ folder en put them there. Use ISO language codes. In our case we'll have two files:

'BaseTranslations.lng' becomes 'nl.lng'
'TranslatorsFile.lng' becomes 'en.lng'

Time for some coding again:

1. In each Page_Create() put the following code BEFORE the ConnectPage() method (see part 1 of the tutorial):
' NEW 1.09 has to be done on every page
    page.SetAcceptedLanguages(Array As String("en", "fr", "de", "nl"), "en")
    Dim ActiveFoundLanguage As String = page.DetectLanguage(ws.UpgradeRequest.GetHeader("Accept-Language"))
    ABM.LoadTranslations(File.DirApp & "/www/" & AppName & "/translations/")
    page.SetActiveLanguage(ActiveFoundLanguage,  "" )

The first line tells ABMaterial the languages you have translations for, and a default language to use if no translation file was found for that language.

Page.DetectLanguage will try to find the language of the browser of the user. If it is not one of the AcceptedLanguages, it will return the default one.

The third line loads all your translations

The last line sets the app in a language. You can use the one returned from DetectLanguage, or use one depending on e.g. the user that logged in.

You'll notice a second empty param in the page.SetActiveLanguage(ActiveFoundLanguage, "" ). This is usefull if you want to have seperate translations for e.g. different clients. Some clients call an person 'Person', others 'Employee'. Using this extra param you can use a different translation.

I give an example:
abmshared_0004;Neighborhood Prospects

You can create a file en_clientx.lng that contains:

The 'clientx' part is the param you can pass into SetActiveLanguage().

How it works:
If the extra param is used, ABMaterial will first look into this file. If found it will use it. If not it will fall back to the default value.

page.XTR("0001", "Contacts") will return 'Contacts' in both cases.
page.XTR("0003", "Agenda") will return "Agenda" if no extra param was passed, but "Calendar" if 'clientx' was passed.

This has a lot of potential use if you create a WebApp for different clients with different needs. It looks like you made a customized app especially for them, although all you had to do was create a new .lng file.

b. You may want to put this code in Websocket_Connected in case of the ABMApplication (where you login). That way you can present the login page in the language of the users browser. In all cases, it has to come BEFORE the ConnectPage() method where you create your components.

And basically this is it. You can see how little code is needed to localize you WebApp with ABMaterial!

And the best part: I've created the same system for defaults!
e.g. one client wants articleX as default value for a combo, clientY wants another default value for a combo. It works very similar: you use page.XDF("code", "DefaultValue"). It generates 'BaseDefaults.def' and 'BaseDefaults.optimizable'. You put them in /www/defaults/, load them with LoadDefaults(). Use page.SetActiveDefaults("defcode", "extraparam" ). Exactly the same story... :)

I've created a couple of very helpful methods to test your app in a different language using machine translation, but I'll talk about this more in a seperate article as this tutorial is already long enough.





Licensed User
Longtime User
I not sure i quite follow....but look promising... Thx


Active Member
Licensed User
Longtime User
Hi. It's working very well :)

What is the correct codification (UTF-8, ANSI,...) for the traduction files?. In spanish the especial characters are bad showed. For example:

"Nuestras Imágenes" <==> "Nuestras Imágenes"

Notepad++ said me the codificacion file is UTF-8

Thank you

Rubén Sánchez Peña


Licensed User
Longtime User
My encoding is:

It showed ok:



Active Member
Licensed User
Longtime User

Thank you Allain. I will check my server configuration. All looks fine in my W10 laptop, but the issue is in a english Windows 2003 server when i show spanish text. The issue is only with the traslated texts. The special characters in normal text are right displayed.


Edit: Another issue. In mobile devices Google Chrome identify the language page like English ever. Maybe because the HTML generated for pages begin with <html lang="en">.

Thank you for your excellent suport.

Rubén Sánchez Peña
Last edited:


Active Member
Licensed User
' NEW 1.09 has to be done on every page
page.SetAcceptedLanguages(Array As String("en", "fr", "de", "nl"), "en")
Dim ActiveFoundLanguage As String = page.DetectLanguage(ws.UpgradeRequest.GetHeader("Accept-Language"))
File.DirApp & "/www/" & AppName & "/translations/")
page.SetActiveLanguage(ActiveFoundLanguage, "" )

Do we really need to call ABM.LoadTranslations in every page ? I am calling it from the Initialize method in ABMApplication and I see the same result - my pages are translated...


Active Member
Licensed User
Have you defined ABM in a shared module, or private in the page?
I have defined ABM as private in ABMShared and also all of my pages, including ABMApplication. I forgot to place ABM.LoadTranslations in one page of my project and when I ran it it was translated. Then I removed all ABM.LoadTranslations from all my pages and add it only in Initialize method of ABMApplication and my pages translate correctly. This is why I asked if we really need to call ABM.LoadTranslations in every page before page.SetActiveLanguage ...


Licensed User
Longtime User
A word to the ones less aware like me...
when run from the IDE (under Windows) this line is incorrect:
 ABM.LoadTranslations(File.DirApp & "/www/" & AppName & "/translations/")
and should be
 ABM.LoadTranslations(File.DirApp & "\www\" & AppName & "\translations\")

The best is to put in a conditional #If debug for testing purposes


Active Member
Licensed User
@Cableguy you should use File.Combine and let java decide on which platform you run ...
ABM.LoadTranslations(File.Combine(File.Combine(File.Combine(File.DirApp, "www"), AppName), "translations"))


Licensed User
Longtime User
@Cableguy you should use File.Combine and let java decide on which platform you run ...
ABM.LoadTranslations(File.Combine(File.Combine(File.Combine(File.DirApp, "www"), AppName), "translations"))
this should/could have been discussed earlier, lol


Active Member
Licensed User
Longtime User
Hi Alain,
there is a method to set language by code?
do you have a small example?
regards Paolo


Licensed User
Longtime User
    #Region Check the Browser Language
    page.SetAcceptedLanguages(Array As String("en", "de", "it", "fr", "pt"), "en")
    BrowserBaseLanguage = page.DetectLanguage(ws.UpgradeRequest.GetHeader("Accept-Language"))
    #End Region
    #Region Load the translations files
    ABM.LoadTranslations(File.Combine(File.Combine(File.Combine(File.DirApp, "www"), "web"), "translations"))
    page.SetActiveLanguage(BrowserBaseLanguage, "" )
    Log("BrowserBaseLanguage : " & BrowserBaseLanguage)
    #End Region

In the code above, if you use page.SetActiveLanguage("es","") the page will load and show all text from the es.lng file