【问题标题】:Is it possible to combine resourcestrings in Delphi?是否可以在 Delphi 中组合资源字符串?
【发布时间】:2016-06-15 07:03:14
【问题描述】:

我想替换看起来像的代码

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.';        

类似这样的:

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;

但这会导致错误E2026 Constant expression expected.,这是我所理解的。

不过,我想知道是否有解决方案,允许我以上述方式定义RESSTR_ERRx_TRYAGAIN。 (目标是在不触及所有使用 RESSTR_ERRx_TRYAGAIN 的地方的情况下消除额外的翻译)。

到目前为止,我唯一的想法是以下,但我不想使用它,因为这很丑:

var
    RESSTR_ERR1_TRYAGAIN: string;
    //...

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

【问题讨论】:

  • 真正的字符串比较复杂,这个例子只是为了说明。
  • 我会声明函数来返回字符串
  • 您可以在运行时格式化字符串:Format('%S %S',[RESSTR_ERR1,RESSTR_TRYAGAIN])
  • @LURD 已确认。但是我想避免修改所有已经使用资源字符串的地方。
  • 然后按照大卫的建议去做。将RESSTR_ERRx_TRYAGAIN 设为函数。

标签: delphi delphi-xe4


【解决方案1】:

resourcestring 字符串在运行时解析。每次您引用资源字符串时,实际发生的是您正在调用 LoadResString() API 从应用程序资源中加载(可能)翻译后的字符串。

const 声明是常量,必须在编译时完全定义。

即使是类型化的常量(技术上是一个变量,受编译器设置的影响)也必须在编译时完全定义,即使以后可能会在运行时进行修改。没有根据运行时应用的翻译自动更新常量的机制。

即使您可以按照您尝试的方式组合资源字符串,也不会保存任何翻译,因为任何声明的资源字符串组合本身都必须是资源字符串,需要对该组合进行单独的翻译:

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

当然,正如您所发现的,第三个声明是不可能的,但即使是这样,它也不会为您节省额外的翻译。

您不能使用常量来保存组合的翻译值的原因是它们不是常量:

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

如果您主要关心减少声明常量的工作,那么您可以使用这个:

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

resourcestring
  foobar = foo + bar;

但是您仍然需要提供所有生成的资源字符串及其完整的常量部分,因此无法实现避免额外翻译的目标。

常量解决方案的问题

任何试图启用组合 resourcestring 声明的解决方案都需要为该特定组合单独翻译,而几乎没有收益(实际上创造了更多的工作:额外的翻译)。

任何试图使用资源字符串的编译时值声明常量的解决方案都不会在运行时进行翻译。

initialization 的变通方法有一个更微妙的复杂性,即您的初始化伪常量将在初始化时包含资源字符串的翻译值。如果您使用像 Sisulizer 这样允许 i18n 资源运行时更改的东西,那么任何此类更改都不会反映在您的伪常量中。

你仍然可以使用这种技术,但是将你的初始化代码放在一个你在 initialization 和调用的函数中.

运行时问题的运行时解决方案

如上所述,根本问题是您试图声明一个 compile-time 常量,该常量由仅在 run-time 解析的值组成。

您需要的是一种方便、可靠的机制,可以在运行时合并两个运行时值。

如果“重试”示例是一个孤立的案例,那么将特定资源字符串附加到某个其他指定字符串(可能是资源字符串,也可能不是)的简单函数可能就足够了:

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

如果您有许多这样的可能组合,那么您可能会选择使用许多静态实用程序方法来实现一个类:

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

【讨论】:

  • 你是对的,我原来的解决方案,它试图在编译时设置字符串确实编译但没有翻译。我修改了my answer 以使其正常工作。 +1 详细说明。
【解决方案2】:

可以使用记录和运算符重载:

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;

用法:

ShowMessage(RESSTR_ERR1_TRYAGAIN);

我用 dxgettext 测试过,翻译正确。

(将Part1Part2 定义为string 的最初想法并不总是有效(正如Deltics 指出的那样):如果在运行时切换语言,它会编译但不能正确翻译。 )

【讨论】:

  • 在您的示例中,TSpaceSeparatedResourceStrings 应该是 class 还是 record?该代码缺少该定义,并且两者都可以具有重载的运算符。
  • @RemyLebeau Awrr,那一点代码中有很多错误:一条记录​​。操作员功能也是错误的......当然这不是记录助手。修好了。
  • 很聪明,我喜欢!
  • 这本质上只是 OP 已经提出的 initialization 部分方法的更复杂的替代方案。 initialization 部分被简单地移动到每个常量的(更麻烦的)声明中。但就像initialization 方法一样,如果在运行时更改语言,它将无法正常运行(这可能是也可能不是现在或将来的问题,但这是一个潜在的问题)。如果 const 解决方法不允许允许更改语言(如果需要),则至少可以重新初始化显式初始化的变量。
  • @Deltics 这不正确!每次使用(例如调用ShowMessage 时)都会调用LoadResString,而不是在初始化部分。如果在运行时更改了语言(如果底层系统支持语言的运行时切换),它将正常运行。我已经修改了我原来的答案来解决这个问题。
猜你喜欢
  • 2016-11-13
  • 2011-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-01
  • 1970-01-01
相关资源
最近更新 更多