Recent

Author Topic: Including translation files in the executable  (Read 2617 times)

fatmonk

  • Sr. Member
  • ****
  • Posts: 252
Including translation files in the executable
« on: April 07, 2021, 03:15:55 pm »
I know this has been discussed in other topics, but I wanted to start a new Topic to hopefully pull together a current full answer to this.

The aim:
  • Build a multi-language application
  • That is cross-platform compilable
  • Where the language translation files are included in the executable to make the application single-file-portable
  • Where the system language is used to determine the language used i nteh application UI

I have managed to this with other applications where the translation files are stored in a 'languages' sub-folder, but reading the docs it seems it should be possible to embed the .po or .mo files as resources.

I have hunted through the following three resources to try to figure this out but they all seem to refer to different versions of Lazarus/FPC and seem to have been updated at different times in different places so I can't work out a coherent method of doing this.

https://wiki.lazarus.freepascal.org/Step-by-step_instructions_for_creating_multi-language_applications - this is a good guide to getting multi-language working and switchable via the command line when the langauge files are in a sub-folder

https://wiki.lazarus.freepascal.org/Translations_/_i18n_/_localizations_for_programs - this seems to be a bit outdated, but mentions switching via the command line (or run options in the IDE)

https://wiki.freepascal.org/Everything_else_about_translations - this talks about a couple of methods for including the translation files in the executable, but seems to be talking about older versions of Laz/FPC and so doesn;t directly relate to th elatest version particulary using the TDefaultTranslator class (uses DefaultTranslator).

First some specific questions...

  • If I include the translation files as resources via Project->Project Options->Resources as RCDATA resources (as per one of those linked documents) do I include the .po or .mo versions of the files?
  • I understand that if the files are included as resources I am unable to switch language via the command line / run options - is that correct?
  • Is there anything else I need to do other than include the langauge files as resources and specify DefaultTranslator in the uses clause to enable the languages to be used when the system/OS language matches?

Note that translations are working exactly as expected in my application at the moment with the language files in a languages sub-directory as specified in i18n settings in Project->Project Options->i18n. I can switch languages via the command line no problem.. I just want to package them in the executable and be able to test them (my machine is set to English so I don't know how to test that the system language activates a translation)

Thanks,

-FM

alpine

  • Hero Member
  • *****
  • Posts: 1038
Re: Including translation files in the executable
« Reply #1 on: April 09, 2021, 01:28:59 am »
Hello fatmonk,

I'm using the following steps to achieve the result:
  • Including PO files as a RC_DATA resources, each resource named as TRANSLATION.xx where the xx is the language code
  • Adding the following unit translateu.pas into project
  • Changing language with TranslateFromResource('bg') or whatever language resource included

There are issues, though. One of them is that if there is a translation of a specific item in, say 'bg' language, and there is no such translation in 'en' language (because it is the base for the form resource), translating to 'bg' and then back to 'en' won't change the item and it will stay in 'bg' form. But this is actually a general problem of a PO I18N way.
To cope with that, the PO files for all languages must have all items translated, even the base/design language. 

Here is the translateu.pas source:

Code: Pascal  [Select][+][-]
  1. unit translateu;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils;
  9.  
  10. function TranslateFromResource(const Language: String; ForceUpdate: Boolean =
  11.   True; const ResFile: String = 'TRANSLATION'): Boolean;
  12.  
  13. implementation
  14.  
  15. uses
  16.   LCLTranslator, LResources, Translations, LCLType, Forms;
  17.  
  18. function TranslateFromResource(const Language: String; ForceUpdate: Boolean;
  19.   const ResFile: String): Boolean;
  20. var
  21.   Res: TResourceStream;
  22.   PoFile: TPOFile;
  23.   LocalTranslator: TUpdateTranslator;
  24.   I: Integer;
  25. begin
  26.   Res := TResourceStream.Create(HInstance, ResFile + '.' + Language, RT_RCDATA);
  27.   try
  28.     PoFile := TPOFile.Create(Res);
  29.     Result := TranslateResourceStrings(PoFile);
  30.     LocalTranslator := TPOTranslator.Create(PoFile);
  31.     if Assigned(LRSTranslator) then
  32.       LRSTranslator.Free;
  33.     LRSTranslator := LocalTranslator;
  34.     if ForceUpdate then
  35.     begin
  36.       for I := 0 to Pred(Screen.CustomFormCount) do
  37.         LocalTranslator.UpdateTranslation(Screen.CustomForms[I]);
  38.       for I := 0 to Pred(Screen.DataModuleCount) do
  39.         LocalTranslator.UpdateTranslation(Screen.DataModules[I]);
  40.     end;
  41.   finally
  42.     Res.Free;
  43.   end;
  44. end;
  45.  

Hope that helps.

Regards,
« Last Edit: April 09, 2021, 01:30:32 am by alpinistbg »
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

fatmonk

  • Sr. Member
  • ****
  • Posts: 252
Re: Including translation files in the executable
« Reply #2 on: April 09, 2021, 08:11:44 pm »
Thanks Alpinist,

I'll have a look at all that and see if that will work for me.

It's a shame Lazarus/FPC can't look in the included resources for the po/mo files as part of its search path... That would make things much easier.

-FM

alpine

  • Hero Member
  • *****
  • Posts: 1038
Re: Including translation files in the executable
« Reply #3 on: April 09, 2021, 10:04:47 pm »
Thanks Alpinist,

I'll have a look at all that and see if that will work for me.
You're welcome

It is a shame, but I'll confess - I have a previous experience with the Visual Studio and there the i18n resources for the forms were organized slightly in a different way. The form has a 'Language' design-time property and the key for the translation was the actual property name, e.g. label1.caption, not the label1.caption value itself. In that way other type of properties can be stored for each language, for example - label1.width, which is quite handy since non-english captions are usually longer. In the designer, when you change the 'Language' prop, all captions get reloaded, and you can edit them in place. When you resize, the new width goes into the specific resource file, etc.

But all that is too much to expect from the Lazarus, I mean - because it must be totally revamped. 


 
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
Re: Including translation files in the executable
« Reply #4 on: April 09, 2021, 10:15:41 pm »
One of the problems of the traditional visual studio translation system is that when language resource dlls were not entirely in sync with the exe the result would be quite messed up.


alpine

  • Hero Member
  • *****
  • Posts: 1038
Re: Including translation files in the executable
« Reply #5 on: April 10, 2021, 12:14:06 am »
One of the problems of the traditional visual studio translation system is that when language resource dlls were not entirely in sync with the exe the result would be quite messed up.
Yeah, quite a fragile environment. And the translation was the least thing that can go wrong. Not to mention its way of handling databases.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

fatmonk

  • Sr. Member
  • ****
  • Posts: 252
Re: Including translation files in the executable
« Reply #6 on: April 12, 2021, 06:50:11 pm »
I can't actually see anything wrong with that code, but I'm getting a compile error as folows:
translateu.pas(46,0) Fatal: Syntax error, "BEGIN" expected but "end of file" found

Hmmm...

Hello fatmonk,

I'm using the following steps to achieve the result:
  • Including PO files as a RC_DATA resources, each resource named as TRANSLATION.xx where the xx is the language code
  • Adding the following unit translateu.pas into project
  • Changing language with TranslateFromResource('bg') or whatever language resource included

There are issues, though. One of them is that if there is a translation of a specific item in, say 'bg' language, and there is no such translation in 'en' language (because it is the base for the form resource), translating to 'bg' and then back to 'en' won't change the item and it will stay in 'bg' form. But this is actually a general problem of a PO I18N way.
To cope with that, the PO files for all languages must have all items translated, even the base/design language. 

Here is the translateu.pas source:

Code: Pascal  [Select][+][-]
  1. unit translateu;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils;
  9.  
  10. function TranslateFromResource(const Language: String; ForceUpdate: Boolean =
  11.   True; const ResFile: String = 'TRANSLATION'): Boolean;
  12.  
  13. implementation
  14.  
  15. uses
  16.   LCLTranslator, LResources, Translations, LCLType, Forms;
  17.  
  18. function TranslateFromResource(const Language: String; ForceUpdate: Boolean;
  19.   const ResFile: String): Boolean;
  20. var
  21.   Res: TResourceStream;
  22.   PoFile: TPOFile;
  23.   LocalTranslator: TUpdateTranslator;
  24.   I: Integer;
  25. begin
  26.   Res := TResourceStream.Create(HInstance, ResFile + '.' + Language, RT_RCDATA);
  27.   try
  28.     PoFile := TPOFile.Create(Res);
  29.     Result := TranslateResourceStrings(PoFile);
  30.     LocalTranslator := TPOTranslator.Create(PoFile);
  31.     if Assigned(LRSTranslator) then
  32.       LRSTranslator.Free;
  33.     LRSTranslator := LocalTranslator;
  34.     if ForceUpdate then
  35.     begin
  36.       for I := 0 to Pred(Screen.CustomFormCount) do
  37.         LocalTranslator.UpdateTranslation(Screen.CustomForms[I]);
  38.       for I := 0 to Pred(Screen.DataModuleCount) do
  39.         LocalTranslator.UpdateTranslation(Screen.DataModules[I]);
  40.     end;
  41.   finally
  42.     Res.Free;
  43.   end;
  44. end;
  45.  

Hope that helps.

Regards,

fatmonk

  • Sr. Member
  • ****
  • Posts: 252
Re: Including translation files in the executable
« Reply #7 on: April 12, 2021, 07:18:33 pm »
Ah, I think I've spotted it now that I've shut down for the evening!

Missing an end. at the end of the unit!

Will try again tomorrow.

-FM

alpine

  • Hero Member
  • *****
  • Posts: 1038
Re: Including translation files in the executable
« Reply #8 on: April 14, 2021, 11:48:24 am »
Ah, I think I've spotted it now that I've shut down for the evening!

Missing an end. at the end of the unit!

Will try again tomorrow.

-FM

Sorry about that, it wasn't intentional
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
Re: Including translation files in the executable
« Reply #9 on: April 14, 2021, 12:08:11 pm »
Even if something is automatic, a programmer should be able to suppress it.

Under Delphi I use dxgettext, but in the Netherlands, the windows version and preferred application language often don't match (e.g. prefer English for operator screens, but Dutch windows versions).  So I suppress it, and maintain my own language persistence inside those apps.

In time I'll also need such a thing in Lazarus, but I haven't massively dived into its localization support yet.

kupferstecher

  • Hero Member
  • *****
  • Posts: 583
Re: Including translation files in the executable
« Reply #10 on: April 14, 2021, 12:34:12 pm »
Hello Fatmonk,

I do it like described in the wiki and have no problems with it (Actually its nearly the same code like  y.ivanov's).

https://wiki.freepascal.org/Everything_else_about_translations#Compiling_po_files_into_the_executable_and_change_language_while_running

About the command line, I do it manually, check the command line options at program start up and call the procedure to change the language if necessary. With this at least its fully under my control.

Is there anything else I need to do other than [...]

There is the issue, that Lazarus doesn't know when the resource was changed so it isn't automatically compiled. I just change the unit (e.g. adding an empty line) to tell Lazarus the unit should be recompiled on the next build. Perhaps there is a better method. Anyone?

alpine

  • Hero Member
  • *****
  • Posts: 1038
Re: Including translation files in the executable
« Reply #11 on: April 14, 2021, 01:02:37 pm »
There is the issue, that Lazarus doesn't know when the resource was changed so it isn't automatically compiled. I just change the unit (e.g. adding an empty line) to tell Lazarus the unit should be recompiled on the next build. Perhaps there is a better method. Anyone?
I have the same problem with the changed resources. I also touch some whitespace to force the make, but that affects the version control and I must revert if it isn't an actual change.  :(
I would be happy if someone shared a workaround.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

 

TinyPortal © 2005-2018