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

Best way to design for localization of strings

本文关键字:设计方 最佳 本地化 字符串      更新时间:2023-10-16

这是一个一般性的问题,可以征求意见。我一直在尝试为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#对象来从带有回退的本地化资源文件中加载它们。如果C#对象不可用(或无法加载),我会回退到代码中的字符串。宏扩展到一个模板类,该类调用COM对象并进行格式化等操作。

不管怎样,我认为添加我现在拥有的内容以供参考会很有用。

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

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

代码看起来像:

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

作为构建过程的一部分,我们有一个perl脚本,用于解析字符串常量的所有源。它构建了一个包含应用程序中所有字符串的临时文件,然后将这些字符串与每个本地字符串中的资源字符串进行比较,看看它们是否存在。任何丢失的字符串都会生成一封发送给相应翻译团队的电子邮件。

我们可以为每个本地拥有多个dll。dll的名称基于RFC 3066
语言[_teritory][.codeset][@modifier]

我们尝试从机器中提取区域设置,并在加载I18N dll时尽可能具体,但如果不存在更具体的版本,则回退到不太具体的本地变体。

示例:

在英国:如果本地是en_GB。UTF-8
(我使用dll这个术语不严格,不是在特定的windows意义上)。

首先查找I18N.en_GB.UF-8dll。如果此dll不存在,则回退到I18N.en_GB。如果此dll未存在,则回滚到I18N.en如果此dll已不存在,将回退到I18N.default

此规则的唯一例外是:简体中文(zh_CN),其中回退为美式英语(en_US)。如果机器不支持简体中文,那么它就不太可能支持全中文。

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

有几个共享软件实用程序可以帮助rc文件本地化,这些实用程序可以为单词较长的语言处理对话框元素的大小调整,并警告缺少翻译。

一个更复杂的问题是语序,如果你在一个printf中有几个数字,对于不同语言的语法,这些数字的顺序必须不同。代码项目上有一些扩展的printf类,允许您指定printf("单词%1s和%2s",var1,var2),这样您就可以在必要时切换%1s和%2。

我不太了解在Windows上通常是如何做到这一点的,但在苹果的Cocoa框架中处理本地化字符串的方式非常好。它们有一个非常基本的文本格式文件,可以发送给翻译器,还有一些预处理器宏来从文件中检索值。

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

由于它是公开的,下面是我的操作方法。

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

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

然后在我的makefile中是一个cusom构建步骤,它生成一个strings.h文件和一个strings.dat。在我的例子中,它为字符串id构建一个枚举列表,然后为文本构建一个带有偏移量的二进制文件。由于在我的应用程序中,用户可以随时更改语言,我已经将它们全部存储在内存中,但如果需要,您可以很容易地让预处理器为每种语言生成不同的输出文件。

我喜欢这种设计的一点是,如果缺少任何字符串,那么我会得到一个编译错误,而如果在运行时查找字符串,那么你可能直到以后才知道代码中很少使用的部分中缺少的字符串。

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

我不知道为什么要用_RESTRING宏处理多个参数。我的代码(使用wxWidgets对gettext的支持)如下:MyString.Format(_("Some string with variable %ls"), _("variable"));。也就是说,String::Format(…)得到两个单独翻译的参数。事后看来,Boost::格式会更好,但它也会允许boost::format(_("Some string with variable %1")) % _("variable");

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

在一个我已本地化为10多种语言的项目中,我将所有要本地化的内容放入一个仅限资源的dll中。在安装时,用户选择与其应用程序一起安装的dll。

我只需要将英文dll交付给本地化团队。他们为我在构建中包含的每种语言都返回了一个本地化的dll。

我知道这并不完美,但它奏效了。

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

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

顺便说一句,本机Win32程序和.NET程序具有完全不同的资源存储管理。您将很难找到两者的通用解决方案。