Forum > Translations

My multilingual method for apps

(1/4) > >>

Raul_ES:
Hello,

I've been working in a project for a while and I'm not sure if the multilingual architecture I am currently implementing is really good idea. Sometime ago I had a look to I18n and PO files, but I remember it as a bit confusing and painful to edit, mantein, etc. For some reason I didn't felt confortable using it. Maybe I shall give it a try again someday, but I decided to develop my own method as an alternative and to learn more about pascal. Check it please and let me know what you think.

The method works as follows:

We define a file called constants.inc that contains several constants like LANGUAGES_AVAIBLE = 9 (i.e)
and ES = 1, CA = 2, EU = 3, GA = 4 ... up to the number you need.

The lpr/ppr file will hold a global variable (LANGUAGE : integer) to define a common default language for
all forms in the application:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---(...){$R *.res}{$INCLUDE ../include/constants.inc}(...) var  // crear variables globales  LANGUAGE : integer; cvar; export; (...) begin  LANGUAGE := ES; // default language (...) 
Then, for every form in the application we must create an include file (i.e unit_mainform_language.inc)
with the following structure (i.e for eight local languages in Spain and english), that will contain
translations for all the labels, captions, titles, list items, hints, etc, for *that* particular form:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---{         1              2            3              4           5          6           7           8            9 +---------------------------------------------------------------------------------------------------------------------+ |                 |           |              |              |        |           |           |            |           | | Español (es_ES) |  Catalán  |  Valenciano  |  Mallorquín  | Aranés |  Euskera  |  Gallego  | Asturiano  | English   | |                 |           |              |              |        |           |           |            |           | +---------------------------------------------------------------------------------------------------------------------+}// the blank ones have not yet been traslated or can be interpreted as equivalent to other's language word (#2 OR #1) uEButton_close_Caption: Array[1..LANGUAGES_AVAIBLE] of String = ('Cerrar','Tancar','','',  '','Itxi','Pechar','','Close');  Label_copyrightLogo_Caption: Array[1..LANGUAGES_AVAIBLE] of String = ('Todos los derechos reservados.','Tots els drets reservats.','','','','Eskubide guztiak erreserbatuta.','Todos os dereitos reservados.','','All rights reserved.');(...) 
Items in lists, comboboxes and such are declared in two dimensional arrays, something like this:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---ComboBox_AccessControlType_ListItems: Array[1..LANGUAGES_AVAIBLE,1..7] of String = (('Identificador Empleado','PIN','Persona-Clave','Máquina-Clave','Tarjeta','SmartCard','DNI Electrónico'),('Identificador Empleat', 'PIN', 'Persona-Clau', 'Màquina-Clau', 'Targeta', 'SmartCard', 'DNI Electrònic'),('','','','','','',''),('','','','','','',''),('','','','','','',''),('Langilearen Identifikatzailea', 'PIN', 'Pertsona-Pasahitza', 'Makina-Pasahitza', 'Txartela', 'SmartCard', 'NAN elektronikoa'),('Identificador de empregado', 'PIN', 'Contrasinal de persoa', 'Contrasinal de máquina', 'Tarxeta', 'SmartCard', 'Identificación electrónica'),('','','','','','',''),('Employee Identifier', 'PIN', 'Person-Password', 'Machine-Password', 'Card', 'SmartCard', 'Electronic ID')); 

Then in the form's code (i.e unit_whateverform.pas) we must define:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---(...)var  whateverForm: TwhateverForm;   LANGUAGE : Integer; cvar; external; // it's a global variable(...) We must place {$INCLUDE ../include/constants.inc} somewere to access the language codes or we may declare those constants as global at the lpr/ppr
file.

Then we will load language translation on creation time to the form:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TwhateverForm.FormCreate(Sender: TObject);var  //LANGUAGE : Integer; uncommenting this will give an Access violation under linux but not under Windows.    i : integer;  {$INCLUDE ../languages/unit_whateverform_languages.inc} begin // start to assign translated words  uEButton_Help.Caption := BitBtn_Help_Caption[LANGUAGE];  uEButton_Help.Hint := BitBtn_Help_Hint[LANGUAGE];   uEButton_Configuration.Caption := BitBtn_Configuration_Caption[LANGUAGE];  uEButton_Configuration.Hint := BitBtn_Configuration_Hint[LANGUAGE];   uEButton_Start.Caption := BitBtn_Start_Caption[LANGUAGE];  uEButton_Start.Hint := BitBtn_Start_Hint[LANGUAGE];   uEButton_Quit.Caption := BitBtn_Quit_Caption[LANGUAGE]; (...)  
And we also add items to lists, etc in the selected language:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---// we then load translated items to lists, comboboxes, etc   for i := 1 to length(ComboBox_AccessControlType_ListItems[LANGUAGE]) do  begin     ComboBox_AccessControlType.Items.Add(                          ComboBox_AccessControlType_ListItems[LANGUAGE][i]);  end;  for i := 1 to length(ComboBox_SessionType_ListItems[LANGUAGE]) do  begin     ComboBox_SessionType.Items.Add(                          ComboBox_SessionType_ListItems[LANGUAGE][i]);  end;(...) 
Ok, so that's all. Let me know what you think please.

The main benefits I see this way are:

1) Adding new languages to a *_language.inc file is as easy as using any text editor, copy paste a line "a","b","c"... into Google Translator (if that language is supported) and paste back the result to the inc file at the right position in the array.

2) I believe this can be scripted into an automathed process to add as many languages as supported by Google or other
online services.

3) Easier to distribute plain text files and easier for contributors to edit (I think so)

4) Easier to read and mantein (imho). One file for form with all supported languages. Any one can read the line in english and translate to it's own language.

5) Easier to distribute as inc files are compiled into the executable. No need to distribute and install *.po files

6) You can change language on the fly, creating a loadLanguage() procedure to be called on creation time and at any moment the user wants to. Interesting in a country with more than one oficial language, a POS terminal shared with several operators, etc

7) add your own if you see any...


Bad things I see:

1) words are stored in arrays and data structures that consume memory resources.

2) all languages shall be loaded into memory (probably you dont need to have portuguese, chinese, dutch and arab at the same time, but yes for bilingual regions: finnish-swedish, spanish-catalan, spanish-euskara...)

3) all languages shall be kept in memory while running, at least co-oficial languages, to allow on-the-fly switching. Non required elements can be freed from the array.

4) add what you see here


regards,

jwdietrich:
I think that this is a good idea. I use a similar, albeit less elegant, approach in my apps.

Dismissing .po files is also beneficial for cross-platform development. .po files are not well supported on certain platforms, e.g. macOS.

PascalDragon:

--- Quote from: Raul_ES on April 02, 2020, 02:21:42 pm ---Ok, so that's all. Let me know what you think please.

The main benefits I see this way are:

1) Adding new languages to a *_language.inc file is as easy as using any text editor, copy paste a line "a","b","c"... into Google Translator (if that language is supported) and paste back the result to the inc file at the right position in the array.

2) I believe this can be scripted into an automathed process to add as many languages as supported by Google or other
online services.

3) Easier to distribute plain text files and easier for contributors to edit (I think so)

4) Easier to read and mantein (imho). One file for form with all supported languages. Any one can read the line in english and translate to it's own language.

5) Easier to distribute as inc files are compiled into the executable. No need to distribute and install *.po files

6) You can change language on the fly, creating a loadLanguage() procedure to be called on creation time and at any moment the user wants to. Interesting in a country with more than one oficial language, a POS terminal shared with several operators, etc

7) add your own if you see any...


Bad things I see:

1) words are stored in arrays and data structures that consume memory resources.

2) all languages shall be loaded into memory (probably you dont need to have portuguese, chinese, dutch and arab at the same time, but yes for bilingual regions: finnish-swedish, spanish-catalan, spanish-euskara...)

3) all languages shall be kept in memory while running, at least co-oficial languages, to allow on-the-fly switching. Non required elements can be freed from the array.

4) add what you see here

--- End quote ---

Ehm... you are aware of resourcestrings? (also documented here) You can use ObjPas.SetResourceStrings and ObjPas.SetUnitResourceStrings to roll your own storage system (as is done with the GetText unit for PO/MO files) while at the same type using the Object Pascal mechanism for this. And by studying LCLTranslator you can also hook yourself into the LCLs translation mechanism (which is also build upon the before mentioned units).

jwdietrich:

--- Quote from: PascalDragon on April 02, 2020, 02:55:14 pm ---Ehm... you are aware of resourcestrings? (also documented here) You can use ObjPas.SetResourceStrings and ObjPas.SetUnitResourceStrings to roll your own storage system (as is done with the GetText unit for PO/MO files) while at the same type using the Object Pascal mechanism for this. And by studying LCLTranslator you can also hook yourself into the LCLs translation mechanism (which is also build upon the before mentioned units).

--- End quote ---

Is there a possibility to create a self-contained multilingual executable with ResourceStrings? I cannot find much information on the possible "own storage system" mentioned by you. Is there a way to implement a mechanism like the one described by @Raul_ES, where the translated strings are stored in an .inc file?

kupferstecher:

--- Quote from: jwdietrich on April 02, 2020, 05:13:40 pm ---Is there a possibility to create a self-contained multilingual executable with ResourceStrings?

--- End quote ---
At least with the i18n options and po-files its possible.
https://wiki.freepascal.org/Everything_else_about_translations#Compiling_po_files_into_the_executable_and_change_language_while_running

Navigation

[0] Message Index

[#] Next page

Go to full version