Jump to content
Fr0sT.Brutal

Localization of constant arrays - best practice?

Recommended Posts

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 by Fr0sT.Brutal

Share this post


Link to post

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.

 

  • Like 2

Share this post


Link to post

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
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 by Stefan Glienke
  • Like 1

Share this post


Link to post
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 by Fr0sT.Brutal
nvm, it was a glitch

Share this post


Link to post

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.

image.png.b5532a4167c662c70015530478ab61d3.png

Edited by programmerdelphi2k

Share this post


Link to post
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
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.

  • Like 1

Share this post


Link to post
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.

  • Like 1

Share this post


Link to post
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 by Fr0sT.Brutal

Share this post


Link to post
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

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 by Fr0sT.Brutal

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×