Fr0sT.Brutal 900 Posted June 21, 2023 (edited) I approached to localization just now. From what I read the most recommended way is to have all string constants in resourcestring sections and change them through hooks and resource magic. Okay. But I also have many constant arrays type TErrCode = (errThis, errThat) TErrMessages = array[TErrCode] of string = ('This', 'That') How is best to have them localized? The only option that comes to mind is res... SErrThis = 'This' SErrThat = 'That' TErrMessages = array[TErrCode] of string = (SErrThis, SErrThat) Another is have them translated when needed: ShowMessage(_Loc(ErrMessages[ErrCode]) But it will cause more brackets and is vulnerable to forgetting. Edited June 21, 2023 by Fr0sT.Brutal Share this post Link to post
Stefan Glienke 2002 Posted June 21, 2023 If you don't need language swapping while the application is running then go with the const array using resourcestrings. I say so because the const array is initialized on startup with the localized values from the resourcestrings. If you change language later they will not change. 2 Share this post Link to post
Fr0sT.Brutal 900 Posted June 21, 2023 Yes, language change via restart is OK. I also found the way to modify constants const src_arr: array[1..3] of string = ('foo', 'bar', 'quz'); PString(@src_arr[1])^ := 'translated'+Inttostr(2); // imitate dynamically-created string AFAICT no issues happen in my test project Share this post Link to post
Stefan Glienke 2002 Posted June 21, 2023 (edited) 2 hours ago, Fr0sT.Brutal said: Yes, language change via restart is OK. I also found the way to modify constants const src_arr: array[1..3] of string = ('foo', 'bar', 'quz'); PString(@src_arr[1])^ := 'translated'+Inttostr(2); // imitate dynamically-created string AFAICT no issues happen in my test project That "works" (i.e. you wont get a GPF) because typed consts are no real consts but rather read-only variables (as in you cannot directly assign to them regularly). But I am sure this will cause a memory leak - a static one but ReportMemoryLeaksOnShutdown should complain about it. Edited June 21, 2023 by Stefan Glienke 1 Share this post Link to post
programmerdelphi2k 237 Posted June 21, 2023 as said above by Glienke after a reportmemoryleaks = true Share this post Link to post
Fr0sT.Brutal 900 Posted June 21, 2023 (edited) 54 minutes ago, Stefan Glienke said: But I am sure this will cause a memory leak - a static one but ReportMemoryLeaksOnShutdown should complain about it. Well, kind of. After that assignment we have string with refcnt=1 stored in an array item. Another assignment will clear the previous value and allocate new one with same refcnt=1 - OK here. So the only leak will be on app exit where the last values won't be cleared because typed consts aren't finalized (I suppose?). Anyway, clearing the assigned items with pstring(@src_arr[1])^ := ''; invokes memory cleanup. Funny however, FastMM4 won't catch string leak in this setup... I use Fulldebugmode and every tracking option turned on and it doesn't report memory leak neither with built-in FastMM nor with full one from GH. Edited June 21, 2023 by Fr0sT.Brutal nvm, it was a glitch Share this post Link to post
programmerdelphi2k 237 Posted June 21, 2023 (edited) and if you use this .... a "tmp" to do it and after you just "empty it"... then, the memory leak doesnt happens... implementation {$R *.dfm} const src_arr: array [1 .. 3] of string = ('foo', 'bar', 'quz'); procedure TForm1.Button1Click(Sender: TObject); var LTmpToTheTask: PString; // PUnicodeString; begin Memo1.Lines.AddStrings(src_arr); Memo1.Lines.Add(''); // // PString(@src_arr[1])^ := src_arr[1] + ' translated: Hello'; // imitate dynamically-created string // LTmpToTheTask := @src_arr[1]; // a hack LTmpToTheTask^ := 'hello world'; // Memo1.Lines.AddStrings(src_arr); // LTmpToTheTask^ := ''; // PString(LTmpToTheTask)^ := ''; // PUnicodeString(LTmpToTheTask)^ := ''; end; initialization ReportMemoryLeaksOnShutdown := true; finalization end. Edited June 21, 2023 by programmerdelphi2k Share this post Link to post
Fr0sT.Brutal 900 Posted June 21, 2023 2 minutes ago, programmerdelphi2k said: and if you use this Yes, clearing the items removes mem leak. Weird but I still can't see leak report. Share this post Link to post
programmerdelphi2k 237 Posted June 21, 2023 1 minute ago, Fr0sT.Brutal said: Weird but I still can't see leak report. here in RAD 11.3 just repormemoryleaks = true and show it! Share this post Link to post
Remy Lebeau 1394 Posted June 21, 2023 4 hours ago, Fr0sT.Brutal said: I also found the way to modify constants const src_arr: array[1..3] of string = ('foo', 'bar', 'quz'); PString(@src_arr[1])^ := 'translated'+Inttostr(2); // imitate dynamically-created string FYI, you don't have to resort to that pointer hack if you enable the Writable Typed Constants compiler option. 1 Share this post Link to post
Anders Melander 1782 Posted June 22, 2023 On 6/21/2023 at 11:04 AM, Fr0sT.Brutal said: How is best to have them localized? Like this: https://bitbucket.org/anders_melander/better-translation-manager/src/f96e7dcdba22667560178d32aebb5137484107f0/Source/amLocalization.Model.pas?at=master#lines-578 // ----------------------------------------------------------------------------- // // Strings // // ----------------------------------------------------------------------------- resourcestring sTranslationValidationWarningEmptyness = 'Source or translation is empty and the other is not'; sTranslationValidationWarningAccelerator = 'Accelerator count mismatch'; sTranslationValidationWarningFormatSpecifier = 'Format specifier count mismatch'; sTranslationValidationWarningLineBreak = 'Linebreak count mismatch'; sTranslationValidationWarningLeadSpace = 'Leading space count mismatch'; sTranslationValidationWarningTrailSpace = 'Trailing space count mismatch'; sTranslationValidationWarningTerminator = 'Translation is terminated differently than source'; sTranslationValidationWarningPipe = 'Pipe character count mismatch'; sTranslationValidationWarningSurround = 'Surround character mismatch'; const // Note: Must use PResStringRec or values will be of the language active at the time System._InitResStrings was called, // which means that the user language selection will not affect the values as it should. sTranslationValidationWarnings: array[TTranslationWarning] of PResStringRec = ( @sTranslationValidationWarningEmptyness, @sTranslationValidationWarningAccelerator, @sTranslationValidationWarningFormatSpecifier, @sTranslationValidationWarningLineBreak, @sTranslationValidationWarningLeadSpace, @sTranslationValidationWarningTrailSpace, @sTranslationValidationWarningTerminator, @sTranslationValidationWarningPipe, @sTranslationValidationWarningSurround); and then use the strings like this: https://bitbucket.org/anders_melander/better-translation-manager/src/f96e7dcdba22667560178d32aebb5137484107f0/Source/amLocalization.Dialog.Main.pas?at=master#amLocalization.Dialog.Main.pas-2600 Item.Caption := LoadResString(sTranslationValidationWarnings[Warning]); FWIW, I believe this is also the way the VCL/RTL itself does it. 1 Share this post Link to post
Fr0sT.Brutal 900 Posted June 23, 2023 (edited) On 6/21/2023 at 8:14 PM, Remy Lebeau said: you don't have to resort to that pointer hack if you enable the Writable Typed Constants compiler option. Thanks, I'll consider it 22 hours ago, Anders Melander said: Like this: Thanks! I'm now making my own translation system and already got used to res strings incl pointers to them. However, AFAIU there's a drawback in their organization - their codes are consistent only within single binary, any change in their order or number and rebuilding causes part of them to shift. So these numbers couldn't be used as IDs in translation files. Of course DRC file could help but some automated check must be run to control translation files consistency or just use built-in values as keys (I'm now building the latter approach). I also try to realize how can I distinguish one string key from another when using res strings (case of synonyms in English - different words in other languages). I also took a look at hooking LoadResString (shame that everyone has to do these tricks !) and I find it Windows-only. While currently I don't need another platforms but in theory it could be Linux. I'm curious what people do with res strings on that platform? Edited June 23, 2023 by Fr0sT.Brutal Share this post Link to post
Anders Melander 1782 Posted June 23, 2023 1 hour ago, Fr0sT.Brutal said: Thanks! I'm now making my own translation system and already got used to res strings incl pointers to them. However, AFAIU there's a drawback in their organization - their codes are consistent only within single binary, any change in their order or number and rebuilding causes part of them to shift. So these numbers couldn't be used as IDs in translation files. Of course DRC file could help but some automated check must be run to control translation files consistency or just use built-in values as keys (I'm now building the latter approach). If only someone had already solved all those problems... 1 hour ago, Fr0sT.Brutal said: While currently I don't need another platforms but in theory it could be Linux. I'm curious what people do with res strings on that platform? I haven't tried it but I would just compile for Linux and see what it produces. Share this post Link to post
Fr0sT.Brutal 900 Posted June 29, 2023 (edited) Well, finally I translated the app. No hooks involved - just my functions. Luckily I have most of units using base unit where I placed the localizer. Alas, translation is possible only once - changing on-the-fly is not nesessary and it requires too much boring things like thread safety, reinit of controls and so on to implement it just for the case. I also implemented that overwriting of constant arrays. As a conclusion: resourcestrings are cool idea allowing completely transparent substitution. And it's a real shame this mechanism couldn't be customized without Windows-only memory hacks. Edited June 29, 2023 by Fr0sT.Brutal Share this post Link to post