设计本地化字符串的最佳方式

22
这是一个比较普遍的问题,需要不同意见。我一直在尝试寻找一种好的方法来为 Windows MFC 应用程序和相关实用程序设计本地化字符串资源。我的愿望是:
  • 必须保留代码中的字符串字面值(而不是替换为宏 #define 资源 ID),以便消息仍然可以内联阅读
  • 必须允许本地化字符串资源(duh)
  • 不得强加其他运行时环境限制(例如:依赖于 .NET 等)
  • 应对现有代码的干扰最小(修改越少越好)
  • 应该可调试
  • 应生成由常见工具(即常见格式)可编辑的资源文件
  • 不应使用复制/粘贴注释块来保留代码中的字面字符串,或者任何其他可能导致脱节的内容
  • 最好允许静态(编译时)检查每个“标记”的字符串是否在资源文件中
  • 最好允许跨语言资源字符串池(针对各种语言的组件,例如本机 C++ 和 .NET)
我有一种方法基本上满足了我的所有愿望,除了静态检查,但我不得不开发一些自定义代码来实现它(并且有限制)。我想知道是否有人已经以特别好的方式解决了这个问题。
编辑: 我目前的解决方案如下:
ShowMessage( RESTRING( _T("Some string") ) );
ShowMessage( RESTRING( _T("Some string with variable %1"), sNonTranslatedStringVariable ) );

我有一个自定义工具,用于解析“RESTRING”块中的字符串,并将它们放入.resx文件中进行本地化。此外,我还写了一个独立的C# COM对象,用于从本地化资源文件中加载这些字符串,在没有可用的C#对象(或无法加载)时,我会回退到代码中的原始字符串。该宏会扩展为一个模板类,调用COM对象并执行格式化等操作。
总之,我认为这对参考很有用。
7个回答

4

我们使用英文字符串作为ID。

如果从国际化资源对象(从安装的I18N dll中加载)中查找失败,则默认使用ID字符串。

代码如下:

doAction(I18N.get("Press OK to continue"));

作为构建过程的一部分,我们有一个Perl脚本来解析所有源代码中的字符串常量。它会建立一个应用程序中所有字符串的临时文件,然后将其与每个本地资源字符串进行比较,以查看是否存在任何缺失的字符串,并生成相应翻译团队的电子邮件。
我们可以为每个本地设置多个dll。dll的名称基于RFC 3066语言[_territory][.codeset][@modifier]。
当加载I18N dll时,我们尝试从计算机中提取区域设置,并尽可能具体,但如果更具体的版本不存在,则回退到不太具体的本地变体。
例如:
在英国:如果本地设置是en_GB.UTF-8(我使用术语dll并非特指Windows),首先查找I18N.en_GB.UTF-8 dll。如果此dll不存在,则回退到I18N.en_GB。如果此dll不存在,则回退到I18N.en。如果此dll不存在,则回退到I18N.default。
唯一的例外情况是: 简体中文(zh_CN),其中回退是美式英语(en_US)。如果机器不支持简体中文,则不太可能支持全中文。

听起来和我正在做的很相似,但你如何提取所有字符串以创建资源DLL?还是手动完成的? - Nick
好的,所以你正在做与我非常相似的事情(除了我选择了resx而你使用本地资源库)。很酷的交易,这让我对我采取的方法感到更加放心。我会为更多的评论留下空间,但这似乎是一个不错的方法。 - Nick
2
GNU gettext已经做了所有这些事情,所以只是重新发明了(较差版本的)轮子。 - Milan Babuškov
1
我们发现,在其他语言中,有时英语字符串不够具体,无法匹配合适的单个字符串。如果应用程序的不同部分需要使用2个或多个不同的字符串来处理英文字符串,您将如何处理? - Greg Domjan
1
@Greg Domjan:在英文版本中有两个以上的字符串,“Press Button V1”和“Press Button V2”。在英文资源文件中,它们都映射到字符串“Press Button”,而在其他语言中,它们映射到特定的变体。因此,您需要添加代码来决定使用哪个字符串。 - Martin York
我在寻找确切的例子时遇到了麻烦,目前无法轻松访问已翻译的字符串。我的意思是,在第一个屏幕中使用英文中的“Foo”翻译为“Bar”,而在第二个屏幕中翻译为“Baz”,仅通过“Foo”查找不足以确定上下文。因此,当本地化指出这一点时,您是否会使用“Foo1”和“Foo2”,并且您还必须将这两个都更改为“Foo”? - Greg Domjan

2

简单的方法是在代码中仅使用字符串 ID - 不要使用字面字符串。 然后,您可以为每种语言生成不同版本的.rc 文件,要么创建仅资源 DLL,要么创建不同的语言版本。

有一些共享软件可用于帮助本地化 rc 文件,处理具有较长单词的语言的调整对话框元素,并警告缺少翻译。

一个更复杂的问题是单词顺序,如果您有几个数字在 printf 中,必须根据不同语言的语法以不同顺序排列。 Codeproject 上有一些扩展的 printf 类,让您指定像 printf("word %1s and %2s",var1,var2) 这样的内容,这样您就可以在必要时切换 %1s 和 %2s。


我正在一个采用这种解决方案的项目上工作。但是,如果我添加一个新字符串,那么就会生成一个新的定义,并且整个项目需要重新编译,这将浪费很多时间。 - Kiruahxh

1

我不太清楚在Windows上通常是如何处理本地化字符串的,但苹果的Cocoa框架处理本地化字符串的方式相当不错。他们有一个非常基本的文本格式文件,您可以将其发送给翻译人员,并使用一些预处理宏从文件中检索值。

在您的代码中,您将看到以您的母语呈现的字符串,而不是作为不透明ID。


这基本上就是我要找的东西,但需要使用纯C++(不使用框架)。 - Nick
是的,我想这可能就是你要找的。不幸的是,我不知道有任何纯C++版本的这种设计,但在我看来,编写一个似乎并不难。关键的简化在于拥有一个“genstrings”工具来扫描源代码并创建文件。 - Mark Bessey

1

你的解决方案与Unix/Linux中的"gettext"解决方案非常相似。实际上,你不需要编写提取例程。

我不确定为什么你想要_RESTRING宏来处理多个参数。我的代码(使用wxWidgets对gettext的支持)看起来像这样:MyString.Format(_("Some string with variable %ls"), _("variable"));。也就是说,String::Format(...)获得了两个单独翻译的参数。事后看来,Boost::Format可能更好,但它也允许boost::format(_("Some string with variable %1")) % _("variable");

(我们使用_()宏为简洁起见)


我希望宏能够处理内联可变参数格式,主要是为了方便。否则,我可能需要在其周围放置其他东西,可能还需要另一个字符串变量声明,这是浪费代码的。 - Nick
我们在多字节项目中与多语言资源一起使用gettext。问题是我们不能使用gettext的GPL许可证,我们不得不编写自己的许可证。另一个问题是关于格式化参数的字符串同步:printf(_("我想要这个:%.20s和这个:%f%.3s"));外部工具应该进行同步。 - blackbada_cpp

1

既然可以接受不同意见,那么我来分享一下我的做法。

我使用的本地化文本文件是一个简单的制表符分隔的文本文件,可以在Excel中加载并进行编辑。 第一列用于定义,每个向右的列都是相应的语言,例如:

ID              ENGLISH      FRENCH    GERMAN
STRING_YES      YES          OUI       YA
STRING_NO       NO           NON       NEIN

然后在我的makefile中有一个自定义的构建步骤,它生成了一个strings.h文件和一个strings.dat文件。在我的情况下,它为字符串ID构建了一个枚举列表,然后是一个二进制文件,其中包含文本的偏移量。由于在我的应用程序中用户可以随时更改语言,因此我将它们全部存储在内存中,但如果需要,您可以轻松地让预处理器为每种语言生成不同的输出文件。

我喜欢这个设计的原因是,如果有任何字符串缺失,那么我会得到一个编译错误,而如果在运行时查找字符串,则可能直到稍后才会发现代码中很少使用的部分缺少字符串。


0
在一个我本人负责的项目中, 我将所有需要本地化的内容放到了单个资源文件(.dll)当中。 在安装时,用户可以选择哪个文件与应用程序一同安装。
我只需要发送英文版的.dll文件给本地化团队,他们会为每种语言翻译并返回一个本地化的.dll文件给我,我只需将它们包含在构建过程中即可。
虽然这种方法不完美,但它确实起到了作用。

你有没有想过在代码中不使用资源ID来替换字符串的方法?你所描述的听起来像是口语化的方法,虽然可行,但并不是我所寻找的。 - Nick
不,我们不会在应用程序中硬编码字符串。我们所看到的是与资源ID相关的描述性常量。 - BoltBait

0

你想要一个高级实用程序,我一直想写但从未有时间去写。 如果你找不到这样的工具,你可能想要回退到我的CMsg()和CFMsg()包装类,它们允许非常容易地从资源表中提取字符串。(CFMsg甚至提供了一个FormatMessage单行包装器。 是的,在你寻找的那个工具不存在的情况下,将字符串的副本保存在注释中是一个好的解决方案。关于注释的异步化,记住字符串字面量很少被更改。

http://www.codeproject.com/KB/string/stringtable.aspx

顺便提一下,本地Win32程序和.NET程序具有完全不同的资源存储管理方式。你会很难找到一个适用于两者的共同解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接