Lazarus

Programming => General => Topic started by: paule32 on March 03, 2025, 08:13:34 am

Title: localization don't effect in application
Post by: paule32 on March 03, 2025, 08:13:34 am
Hi,
for localization my application, I have following code unit:

Code: Pascal  [Select][+][-]
  1. {$mode delphi}
  2. unit globals;
  3.  
  4. interface
  5.  
  6. const KB_F1  = 112;
  7. const KB_F2  = 113;
  8. const KB_F3  = 114;
  9. const KB_F4  = 115;
  10. const KB_F5  = 116;
  11. const KB_F6  = 117;
  12. const KB_F7  = 118;
  13. const KB_F8  = 119;
  14. const KB_F9  = 120;
  15. const KB_F10 = 121;
  16. const KB_F11 = 122;
  17. const KB_F12 = 123;
  18.  
  19. function GetSystemLanguage: string;
  20. procedure LoadLanguage(const LangCode: string);
  21.  
  22. implementation
  23. uses
  24.   SysUtils, LCLIntf, LCLType, LCLProc, Translations, GetText, Dialogs;
  25.  
  26. function GetSystemLanguage: string;
  27. begin
  28.   Result := GetEnvironmentVariable('LANG');
  29.   if Result = '' then
  30.     Result := 'en'
  31.   else
  32.     Result := Copy(Result, 1, 2);
  33. end;
  34.  
  35. procedure LoadLanguage(const LangCode: string);
  36. var
  37.   PoFilePath: string;
  38. begin
  39.   PoFilePath := ExtractFilePath(ParamStr(0)) + 'locale/' + LangCode + '.mo';
  40.   if FileExists(PoFilePath) then
  41.     TranslateUnitResourceStrings('default', PoFilePath)
  42.   else
  43.     ShowMessage('translator file not found: ' + PoFilePath);
  44. end;
  45.  
  46. end.

when I call:

Code: Pascal  [Select][+][-]
  1. ...
  2. resourcestring
  3.   rsDone = 'done.';
  4.  
  5. procedure TForm1.FormCreate(Sender: TObject);
  6. begin
  7.   LoadLanguage('de');
  8.   ShowMessage(rsDone);
  9. end;
  10. ...

The string rsDone will not be translated.

This is my *.po file, that I have compile with msgfmt to a *.mo file:

Code: Pascal  [Select][+][-]
  1. msgid ""
  2. msgstr ""
  3. "Project-Id-Version: 1.0.0\n"
  4. "POT-Creation-Date: 2025-02-21 20:33+0100\n"
  5. "PO-Revision-Date: 2025-02-21 20:15+0100\n"
  6. "Last-Translator: Jens Kallup <paule32@gmail.com>\n"
  7. "Language-Team: German <paule32@gmail.com>\n"
  8. "MIME-Version: 1.0\n"
  9. "Content-Type: text/plain; charset=utf-8\n"
  10. "Content-Transfer-Encoding: 8bit\n"
  11.  
  12. msgid "done."
  13. msgstr "fertig."
  14.  

what I doing wrong ?
I don't get any Error Messages
Title: Re: localization don't effect in application
Post by: paule32 on March 03, 2025, 12:41:29 pm
Update:
I found a solution with the Code below.
Now, I can use:

Code: Pascal  [Select][+][-]
  1. ShowMessage(tr('done.'));

to localize my Application.
Here the Code

Code: Pascal  [Select][+][-]
  1. {$mode delphi}
  2. unit globals;
  3.  
  4. interface
  5. uses
  6.   SysUtils, LCLIntf, LCLType, LCLProc, Translations, GetText;
  7.  
  8. type
  9.   TMoTranslate = class(TMoFile)
  10.   private
  11.     FLangID: String;
  12.   public
  13.     constructor Create(ALang: String; ACheck: Boolean = false); overload;
  14.     destructor Destroy; override;
  15.     function getLangID: String;
  16.   end;
  17.  
  18. function tr(AString: String): String;
  19. implementation
  20.  
  21. var
  22.   motr: TMoTranslate;
  23.  
  24. function tr(AString: String): String;
  25. begin
  26.   if motr = nil then
  27.   motr := TMoTranslate.Create('de', false);
  28.   result := motr.Translate(AString);
  29. end;
  30.  
  31. constructor TMoTranslate.Create(ALang: String; ACheck: Boolean);
  32. var
  33.   filePath: String;
  34. begin
  35.   if ACheck then
  36.   begin
  37.     FLangID := GetEnvironmentVariable('LANG');
  38.     if FLangID = '' then
  39.     FLangID := 'en' else
  40.     FLangID := Copy(FLangID, 1, 2);
  41.   end else
  42.   begin
  43.     FLangID := ALang;
  44.   end;
  45.  
  46.   filePath := ExtractFilePath(ParamStr(0)) + 'locale/' + FLangID + '.mo';
  47.   if FileExists(filePath) then
  48.   begin
  49.     inherited Create(filePath);
  50.   end else
  51.   begin
  52.     raise Exception.Create('translator file not found: ' + filePath);
  53.   end;
  54. end;
  55.  
  56. destructor TMoTranslate.Destroy;
  57. begin
  58.   inherited Destroy;
  59. end;
  60.  
  61. function TMoTranslate.getLangID: String;
  62. begin
  63.   result := FLangID;
  64. end;
  65.  
  66. end.
Title: Re: localization don't effect in application
Post by: wp on March 03, 2025, 02:34:03 pm
You should have posted a small test project with all required files demonstrating the issue. So, I had to create one by myself and had to make many assumptions on what you were doing exactly. So, next time please...

But anyway I saw that TranslateUnitResourceStrings does not translate the .mo files, while it does for the .po files. I think this is a bug, and you should file a bug report, attach a demo project (again) and show your solution.
Title: Re: localization don't effect in application
Post by: paule32 on March 03, 2025, 03:20:39 pm
the Solution is simple:

copy the Source Code of my last Posting into globals.pas
Create a Program file:

Code: Pascal  [Select][+][-]
  1. {$mode delphi}
  2. program test;
  3. uses
  4.   globals, Dialogs;
  5. begin
  6.   ShowMessage(tr('done.'));
  7. end.

Dialogs is need for ShowMessage
The used *.mo files, you need msgfmt.exe Tool - on my Side it comes with MingW64 / MSYS2.
To compile the .po file to .mo file: simply on Console:

Code: Pascal  [Select][+][-]
  1. msgfmt -o executable_path/locale/de.mo de.po

Here is the skeleton/stub/header of the structure of a .po file:

# Sharp signs are one line comments
# the first msgid and msgstr must be empty - I think it is for self references the mo file itself
msgid = ""
msgstr = ""
"Project-Id-Version: 1.13.2\n"
"POT-Creation-Date: 2025-02-21 20:33+0100\n"
"PO-Revision-Date: 2025-02-21 20:15+0100\n"
"Last-Translator: Jens Kallup <paule32@gmail.com>\n"
"Language-Team: German <paule32@gmail.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"

# now, you can define the String-ID msgid and the translation for it as msgstr
# this will translate ShowMessage(tr('Hello World')); to ShowMessage('The life is short');
msgid = "Hello World"
msgstr = "The life is short"

The rest is trival (on Console):

Code: Pascal  [Select][+][-]
  1. fpc -Mdelphi test.pas

Here is a make-up Version of globals.pas:

Code: Pascal  [Select][+][-]
  1. {$mode delphi}
  2. unit globals;
  3.  
  4. interface
  5. uses
  6.   SysUtils, LCLIntf, LCLType, LCLProc, Translations, GetText, Dialogs;
  7.  
  8. type
  9.   TMoTranslate = class(TMoFile)
  10.   private
  11.     FLangID: String;
  12.   public
  13.     constructor Create(ALang: String; ACheck: Boolean = false); overload;
  14.     destructor Destroy; override;
  15.     function getLangID: String;
  16.   end;
  17.  
  18. function tr(AString: String): String;
  19. implementation
  20. uses
  21.   {$IFDEF WINDOWS}
  22.   Windows;
  23.   {$ENDIF}
  24.  
  25. var
  26.   motr: TMoTranslate;
  27.  
  28. function tr(AString: String): String;
  29. begin
  30.   if motr = nil then
  31.   motr := TMoTranslate.Create('de', false);
  32.   result := motr.Translate(AString);
  33. end;
  34.  
  35. constructor TMoTranslate.Create(ALang: String; ACheck: Boolean);
  36. [code=pascal]{$IFDEF WINDOWS}
  37. const LOCALE_NAME_MAX_LENGTH = 2;
  38. var
  39.   LangID: LCID;
  40.   Buffer: array[0..LOCALE_NAME_MAX_LENGTH] of Char;
  41. {$ENDIF}
  42. var
  43.   filePath: String;
  44. begin
  45.   {$IFDEF WINDOWS}
  46.   if ACheck then
  47.   begin
  48.     // Windows: GetThreadLocale gibt die Locale ID zurück
  49.     case GetThreadLocale of
  50.       $0001: FLangID := 'ar';
  51.       $0002: FLangID := 'bg';
  52.       $0003: FLangID := 'ca';
  53.       $0004: FLangID := 'zh';
  54.       $0005: FLangID := 'cs';
  55.       $0006: FLangID := 'da';
  56.       $0007: FLangID := 'de';
  57.       $0008: FLangID := 'el';
  58.       $0009: FLangID := 'en';
  59.       $000A: FLangID := 'es';
  60.       else begin
  61.         FLangID := 'en';
  62.       end;
  63.     end;
  64.     if GetLocaleInfo(LangID, LOCALE_SISO639LANGNAME, Buffer, SizeOf(Buffer)) > 0 then
  65.     begin
  66.       FLangID := Buffer;
  67.     end;
  68.   end else
  69.   begin
  70.     FLangID := ALang;
  71.   end;
  72.   {$ELSE}
  73.   if ACheck then
  74.   begin
  75.     FLangID := GetEnvironmentVariable('LANG');
  76.     if FLangID = '' then
  77.     FLangID := 'en' else
  78.     FLangID := Copy(FLangID, 1, 2);
  79.   end else
  80.   begin
  81.     FLangID := ALang;
  82.   end;
  83.   {$ENDIF}
  84.  
  85.   filePath := ExtractFilePath(ParamStr(0)) + 'locale/' + FLangID + '.mo';
  86.   if FileExists(filePath) then
  87.   begin
  88.     inherited Create(filePath);
  89.   end else
  90.   begin
  91.     raise Exception.Create('translator file not found: ' + filePath);
  92.   end;
  93. end;
  94.  
  95. destructor TMoTranslate.Destroy;
  96. begin
  97.   inherited Destroy;
  98. end;
  99.  
  100. function TMoTranslate.getLangID: String;
  101. begin
  102.   result := FLangID;
  103. end;
  104.  
  105. end.

And yes, I realize that the Documentation is a little bit buggy - because they provide *.po information.
And if you use plain/raw .po files the RTL of Lazarus/FPC will raise a Exception.
Title: Re: localization don't effect in application
Post by: wp on March 03, 2025, 06:33:02 pm
Why don't you simply attach a zip file containing all the required files? This avoids any confusion and misunderstandings (For example, I cannot get your recent code to compile, it is definitely lacking the LCL requirement).

Anyway, I played with my test project and found that the solution is much simpler: There are two equally named functions "TranslateUnitResourceStrings", one in unit GetText for .mo files, and one in unit translations for .po files. So, when you correctly prefix the procedure by the unit name you have the correct behaviour:
Code: Pascal  [Select][+][-]
  1. procedure LoadLanguage(const LangCode: string; ATranslationExt: String);
  2. var
  3.   TranslatedFilePath: string;
  4. begin
  5.   TranslatedFilePath := ExtractFilePath(ParamStr(0)) + 'locale/' + LangCode + ATranslationExt;
  6.   if FileExists(TranslatedFilePath) then
  7.     case ATranslationExt of
  8.       '.po': Translations.TranslateUnitResourceStrings('globals', TranslatedFilePath);
  9.       '.mo': GetText.TranslateUnitResourceStrings('globals', TranslatedFilePath);
  10.       else   raise Exception.Create('Illegal translator file format.');
  11.     end
  12.   else
  13.     raise Exception.Create('Translator file not found: ' + TranslatedFilePath);
  14. end;
Title: Re: localization don't effect in application
Post by: Thaddy on March 03, 2025, 06:38:54 pm
it is definitely lacking the LCL requirement
Yes. I gave up on helping him some moons ago.
Maybe he has better ears listening to you.
Title: Re: localization don't effect in application
Post by: CM630 on March 04, 2025, 09:18:15 am
I googled, but I found no reason for using .MO files instead of .PO files in Lazarus. Possibly they improve performance, but I seriously doubt that it is noticeable and that it can justify the pains. Am I missing something?
Title: Re: localization don't effect in application
Post by: wp on March 04, 2025, 10:14:41 am
Maybe he does not want somebody to modify the translation files?
Title: Re: localization don't effect in application
Post by: paule32 on March 04, 2025, 02:40:27 pm
that has not to do with changing the code or not.
it has to do with splitting the data from the executable.
I could use TDictionary<> but then I have to link in all supported languages into the executable.
and this is bad programming - after C++ Builder 1.0 or Delphi 1.0
and this is by design a good idea (split data & exe).

btw: each item to a string becomes a ID.
this ID is stored into the mo header.
so, you have a performance lesser - if you like speed.
Title: Re: localization don't effect in application
Post by: CM630 on March 08, 2025, 10:52:45 pm
that has not to do with changing the code or not.
it has to do with splitting the data from the executable.
I could use TDictionary<> but then I have to link in all supported languages into the executable.
and this is bad programming - after C++ Builder 1.0 or Delphi 1.0
and this is by design a good idea (split data & exe).
...
I do not understand you. Either you want to do something very complex, or you are trying to do something simple, but you are mistaken that it is complex.
POs work fine, as far as my experience is considered.
POs are separate files, they are not parts of the EXEs.
Title: Re: localization don't effect in application
Post by: paule32 on March 09, 2025, 12:29:35 pm
I dont can say: Do this so, and do the other thing other than me.
Each Developer and Companies have their own CGL - Code Guide Lines.

When you use PO Files, then do it - but I can say, that it costs more cyvcles to traverse through a Text File instead Loading a Header and doing a Look-Up where the String-ID resides in the MO File.
Then you only need to look through the ID's and jump to the binary Position.

And yes, PO and MO Files are not Part of the Executable - like I said before.
And yes, I had a Typo in the last Post of me:
- I write: ... perfomance lesser ...

That was realy dumb Typo, because it shall mean instead:
... performance better ...
Title: Re: localization don't effect in application
Post by: wp on March 09, 2025, 02:27:57 pm
I had forwarded your question via devs mailing list to Maxim Ganetsky who maintains the translation system. And this is what he answered:
Quote
This is normal when using MO files, because it searches translation not via its ID (like is done for PO files), but via current string value. In our case MO files contain a list of English values and a list of corresponding translations (they don't contain translation ID). So this will work if your initial interface language is English. If it is already in some other language, it becomes impossible to find translation.

So user should remove all MO files and just use PO ones, which don't have such limitation.

Also it should be noted that no .en.po file is needed if user wants to switch back to English in case if original values of resource strings are in English too (the language will be switched to the original of resource strings, but POT file should be present for this to work).

If you insist on using .mo files: In an earlier mail to a similar issue he had recommended to leave LCLTranslator alone and to use only resource strings (which then you must assign manually to the gui controls).

If you have further issues file a bug report, I know for sure that he is reading the Gitlab tracker regularly.
TinyPortal © 2005-2018