Alois Heimer
Alois Heimer

Reputation: 1832

Is it possible to combine resourcestrings in Delphi?

I want to replace code that looks like

resourcestring
    RESSTR_ERR1_TRYAGAIN = 'Error 1. Please try again.';
    RESSTR_ERR2_TRYAGAIN = 'Error 2. Please try again.';
    RESSTR_ERR3_TRYAGAIN = 'Error 3. Please try again.';        

with something like this:

resourcestring
    RESSTR_ERR1 = 'Error 1.';
    RESSTR_ERR2 = 'Error 2.';
    RESSTR_ERR3 = 'Error 3.';        
    RESSTR_TRYAGAIN = 'Please try again.'; 

    RESSTR_ERR1_TRYAGAIN = RESSTR_ERR1 + ' ' + RESSTR_TRYAGAIN; //error
    RESSTR_ERR2_TRYAGAIN = RESSTR_ERR2 + ' ' + RESSTR_TRYAGAIN;
    RESSTR_ERR3_TRYAGAIN = RESSTR_ERR3 + ' ' + RESSTR_TRYAGAIN;

But this leads to error E2026 Constant expression expected., what I do understand.

Nevertheless, I wonder if there is a solution, that allows me to define RESSTR_ERRx_TRYAGAIN in the way described above. (The goal is to eliminate the additional translations without touching all the places where RESSTR_ERRx_TRYAGAIN is used).

My only idea until now is the following, but I don't want to use this, because this is rather ugly:

var
    RESSTR_ERR1_TRYAGAIN: string;
    //...

initialization
    RESSTR_ERR1_TRYAGAIN := RESSTR_ERR1 + ' ' + RESSTR_TRYAGAIN;
    //...

Upvotes: 1

Views: 837

Answers (2)

Deltics
Deltics

Reputation: 23056

resourcestring strings are resolved at runtime. Every time you reference a resourcestring what is actually happening is that you are calling the LoadResString() API to load the (potentially) translated string from the application resources.

const declarations are constants, which must be fully defined at compile-time.

Even a typed constant (technically a variable, subject to compiler settings) must be initially fully defined at compile time, even though it may be potentially later modified at runtime. There is no mechanism to automatically update constants based on translations that are applied at runtime.

Even if you could combine resource strings in the way that you are trying, you do not save any translation because any declared combination of resource strings would itself have to be a resource string, requiring a separate translation for that combination:

resourcestring
   foo = 'foo.';  // Requires translation of 'foo.'
   bar = 'bar';   // Requires translation of 'bar'
   foobar = foo + bar   // Would require translation of 'foo.bar'

Of course, as you have discovered, that third declaration is not possible, but it would not save you the additional translation even if it were.

The reason you cannot use a constant to hold the combined, translated values is that these are not constant:

resourcestring
   foo = 'foo.';  // Requires translation of 'foo.'
   bar = 'bar';   // Requires translation of 'bar'

const
   foobar = foo + bar   // When referenced, foo and bar are actually calls to a function and so are not constant

If you are concerned primarily with reducing work in declaring your constants, then you could use this:

const
  foo = 'foo.';
  bar = 'bar';

resourcestring
  foobar = foo + bar;

But you still then need to provide for all the resulting resource strings with their complete constant parts and so fails to achieve the goal of avoiding additional translations.

Problems with Constant Solutions

Any solution which contrives to enable declaration of a combined resourcestring will demand a separate translation for that specific combination achieving little/no benefit (actually creating more work: the additional translations).

Any solution which contrives to declare a constant using the compile-time values of the resource string will not be translated at run-time.

Your work-around with initialization suffers from a rather more subtle complication which is that your initialized pseudo-constants will contain the translated values of the resource strings at the time of initialization. If you are using something like Sisulizer which allows runtime changes of i18n resources then any such changes will not be reflected in your pseudo-constants.

You could still use this technique,but place your initialization code in a function which you call at initialization and if/when-ever the translation language is changed at runtime.

Runtime Solutions for Runtime Problems

As explained, the underlying problem is that you are trying to declare a compile-time constant composed of values that are only resolved at run-time.

What you need instead is a convenient, reliable mechanism to combine two runtime values at runtime.

If the 'try again' example is an isolated case then a simple function to append a specific resource string to some other specified string (which may be a resource string, or may not) might suffice:

function MessageWithTranslatedTryAgain(const aMessage: String): String;
begin
  if aMessage[Length(aMessage)] <> '.' then
    result := aMessage + '. ' + RESSTR_TRYAGAIN
  else
    result := aMessage + ' ' + RESSTR_TRYAGAIN;
end;

If you have a number of such possible combinations then you might instead choose to implement a class with a number of static utility methods:

type
  Translate = class
  public
    class function MessageWithTryAgain(const aMessage: String): String;
    class function MessageWithContinue(const aMessage: String): String;
    // etc
  end;

Upvotes: 5

yonojoy
yonojoy

Reputation: 5566

It is possible using a record and operator overloading:

interface

type
    TSpaceSeparatedResourceStrings = record
        Part1: PResStringRec;
        Part2: PResStringRec;
        class operator Implicit(From: TSpaceSeparatedResourceStrings): string;
    end;

resourcestring
    RESSTR_ERR1 = 'Error 1.';
    RESSTR_ERR2 = 'Error 2.';
    RESSTR_ERR3 = 'Error 3.';
    RESSTR_TRYAGAIN = 'Please try again.';             

const
    RESSTR_ERR1_TRYAGAIN: TSpaceSeparatedResourceStrings 
      = (Part1: @RESSTR_ERR1; Part2: @RESSTR_TRYAGAIN);    
    RESSTR_ERR2_TRYAGAIN: TSpaceSeparatedResourceStrings 
      = (Part1: @RESSTR_ERR2; Part2: @RESSTR_TRYAGAIN);    
    RESSTR_ERR3_TRYAGAIN: TSpaceSeparatedResourceStrings 
      = (Part1: @RESSTR_ERR3; Part2: @RESSTR_TRYAGAIN);    


implementation

class operator TSpaceSeparatedResourceStrings.Implicit(From: TSpaceSeparatedResourceStrings): string;
begin
    Result := LoadResString(From.Part1) + ' ' + LoadResString(From.Part2);
end;

Usage:

ShowMessage(RESSTR_ERR1_TRYAGAIN);

I tested this with dxgettext and it is translated correctly.

(The original idea to define Part1 and Part2as string does not always work (as Deltics pointed out): It does compile but is not translated correctly if the language is switched at runtime.)

Upvotes: 2

Related Questions