使用GnuGetText时,如何正确本地化Delphi默认ResourceStrings?
使用GnuGetText时本地化Delphi默认ResourceStrings的正确方法
问题背景
原本通过内存修改的方式替换Delphi默认ResourceStrings来本地化错误信息,在空白应用中可以正常工作:
implementation uses System.SysConst; procedure HookResourceString(rs: PResStringRec; newStr: PChar); var oldprotect: DWORD; begin VirtualProtect(rs, SizeOf(rs^), PAGE_EXECUTE_READWRITE, @oldProtect); rs^.Identifier := Integer(newStr); VirtualProtect(rs, SizeOf(rs^), oldProtect, @oldProtect); end; procedure TForm1.FormCreate(Sender: TObject); var test: integer; begin HookResourceString(@SInvalidInteger, '''%s'' no es un valor entero valido'); test := StrToInt('test'); // 触发InvalidInteger错误 end;
但在项目的uses中添加gnuGetText后,代码会在System.SysUtils.ConvertErrorFmt处抛出"ERROR"异常,无法触发自定义翻译的异常。需要保留GnuGetText用于应用其他部分的本地化,因此需要找到兼容的系统ResourceStrings本地化方案。
原因分析
GnuGetText会接管Delphi的ResourceString加载逻辑(比如修改LoadResString的行为),导致直接修改ResStringRec.Identifier的内存Hook方法失效——GnuGetText会优先从自身翻译表读取字符串,而非使用你修改的内存地址指向的内容。
正确解决方案
方案1:利用GnuGetText原生机制翻译系统ResourceStrings
这是最推荐的方式,完全兼容GnuGetText的工作流程:
- 提取系统ResourceString的原始文本:比如
SInvalidInteger的默认文本是'%s is not a valid integer',可通过LoadResString(@SInvalidInteger)获取原始值。 - 在PO翻译文件中添加对应条目:
msgid "%s is not a valid integer" msgstr "'%s' no es un valor entero valido" - 注册系统ResourceString到GnuGetText:
在应用初始化阶段(比如initialization块),调用RegisterResourceString将系统ResourceString变量与msgid关联:
这样GnuGetText在加载uses System.SysConst, gnugettext; initialization RegisterResourceString(@SInvalidInteger, '%s is not a valid integer'); // 其他需要本地化的系统ResourceStrings也按此方式注册 end.SInvalidInteger时会自动使用翻译后的文本,无需内存Hook。
方案2:替换系统错误处理函数
如果方案1无法覆盖所有场景,可以通过HookConvertErrorFmt函数,在抛出异常前手动翻译错误信息:
- 实现自定义错误处理函数:
uses System.SysUtils, gnugettext; procedure CustomConvertErrorFmt(ResString: PResStringRec; const Args: array of const); var OriginalStr, TranslatedStr: string; begin OriginalStr := LoadResString(ResString); // 使用GnuGetText翻译原始字符串 TranslatedStr := _(OriginalStr); // 格式化翻译后的内容 TranslatedStr := Format(TranslatedStr, Args); raise EConvertError.Create(TranslatedStr); end; - Hook系统函数:
使用内存Hook库(如Delphi Detours)或手动修改函数地址,将ConvertErrorFmt替换为自定义函数:uses Detours; initialization InterceptCreate(@ConvertErrorFmt, @CustomConvertErrorFmt); finalization InterceptRemove(@ConvertErrorFmt); end.
方案3:配置GnuGetText忽略系统ResourceStrings(可选)
部分版本的GnuGetText提供了忽略系统单元ResourceStrings的选项,可通过设置全局变量禁用其对系统ResourceStrings的接管,这样你原来的内存Hook方法就能继续生效:
uses gnugettext; initialization IgnoreSystemResourceStrings := True; // 需确认你的GnuGetText版本是否支持该变量 end.
内容的提问来源于stack exchange,提问作者Marc Guillot
相关产品推荐
相关产品推荐

