Visual Studio C++ 应用程序国际化

去年开发了一个产品3D展示制作系统,最近有老外也要买。问题来了,当初开发的时候并没有考虑程序国际化的问题,老外把试用版拿去一运行,全是乱码!到网上找了一个C++程序国际化的方法,似乎比较麻烦,需要用dll动态加载什么,本人以前没有这方面经验,因此心虚。即便对dll很熟悉,也还有另一个问题,那种方法,需要动态设置界面上每一个控件的标题和文本,哪怕是一个按钮,也要调用SetWindowText方法——这无疑增加大量的代码。为了赶时间,我最终采用了手动替换.rc资源文件的方法:

(我的应用程序名为CameraAnimation,以下均以此为例)

第一步:为其它语言新建一个.rc文件

在Project目录下,找到CameraAnimation.rc并复制一份,重命名为CameraAnimation_zh_CN.rc(这个作为原来的中文资源文件先备份一下)

第二步:把.rc文件中的中文内容翻译成英文

.rc文件是应用程序的资源文件,里边包含了大部分界面的文本内容。它实际上是一个文本文件,可以用任何文本编辑器打开,当然在Visual Studio中也可以轻松地以文本方式打开,方法是:

解决方案资源管理器 | 资源文件 | 右键选择CameraAnimation.rc | 查看代码

然后,就把CameraAnimation.rc中的中文一个一个翻译成英文。

第三步:重新编译。

编译成功后,运行程序,界面上大部分中文都已变成英文,但是程序中很多动态赋值的内容还是会有中文,例如,MessageBox的显示的提示。

接下去要做的是把源文件中会显示到界面上的中文内容一个个搬到资源文件中,继续:

第四步:源码中找出会显示到界面上的中文

例如:

mButtonTest.SetWindowText(_T("你好,世界!"));

替换成:

    CString strHelloWorldMessage;
strHelloWorldMessage.LoadString(IDS_HELLO_WORLD);

mButtonTest.SetWindowText(strHelloWorldMessage);

这里用到的方法主要是CString的LoadString方法,它可以把.rc文件STRINGTABLE中定义的字符串加载进来,其中IDS_HELLO_WORLD是对应的字符串ID。

接下来(第五步和第六步)添加一个字符串资源:IDS_HELLO_WORLD,你可以通过Visual Studio的资源视图界面操作,但是我发现直接编辑.rc文件和Resource.h更便捷:

第五步:在.rc资源文件中,找到一个STRINGTABLE(当然也可以单独新建一个STRINGTABLE),在STRINGTABLE的BEGIN和END之间添加一个字符串IDS_HELLO_WORLD:

STRINGTABLE
BEGIN
IDS_HELLO_WORLD "Hello, world!"
END

第六步:在Resource.h中定义IDS_HELLO_WORLD

#define IDS_HELLO_WORLD        8888

注意:这里定义IDS_HELLO_WORLD的值不要和原来的重复!

第七步:把中文内容记录到CameraAnimation_zh_CN.rc,方法类同:

STRINGTABLE
BEGIN
IDS_HELLO_WORLD "你好,世界!"
END

到此为止,一个字符串的国际化操作就完成了,剩下的就是重复上述操作,搜寻源文件中出现的所以其它中文,一一进行修改。
但是很明显,按现在的方法,每个出现中文的地方,代码由原来的一行,变成三行,可以写个辅助方法简化一下:

第八步:重构,提炼方法GetMessage

新建一个Class:MessageSource,添加一个static方法GetMessage

MessageSource.h
#pragma once

#include <afx.h>    //包含这个头文件,主要和CString,以及之后的可变参数有关

class
MessageSource
{
public:
static CString GetMessage(UINT nID);
};
MessageSource.cpp
#include "MessageSource.h"

CString MessageSource::GetMessage(UINT nID)
{
CString strMessage;
strMessage.LoadString(nID);

return strMessage;
}

于是,上述加载消息的代码就可以简化成一行了:

 mButtonTest.SetWindowText(MessageSource::GetMessage(IDS_HELLO_WORLD));

第九步:消息文本参数传递

有时候(准确地说应该是经常),加载的消息文本需要在运行时传递一些参数。要实现动态传递消息参数的问题,主要面临的问题是,所传递的参数个数是不固定的,其次参数类型也是不确定的。

Java的做法是用首先在消息文本中插入一些占位符{0},{1}等等,然后调用ResourceBundle的getString加载该字符串,再借助MessageFormat进行格式化:

ResourceBundle localResource = ResourceBundle.getBundle("messages", locale);
String messageFormat = localResource.getString("test.hello"); //这里返回:Hello,{0}
String message = MessageFormat.format(messageFormat , new Object[] {"Java"}); //这里返回:Hello,Java

当然,像在Spring,Sturts中做消息国际化时,则不需要这么麻烦,它们一般内置了一些功能,但是基本方法是差不多的。

我早已习惯了Java这种{0},{1}的方法,因此很想在C++中也采用这种方法,但没成功。于是想到CString的Format方法,可以模仿:

1)将MessageSource::GetMessage的方法修改如下(同时修改MessageSource.h的方法定义):

#include "MessageSource.h"

CString MessageSource::GetMessage(UINT nID, ...)
{
CString strMessageFormat;
strMessageFormat.LoadString(nID);

va_list argList;
va_start(argList, nID); //注意:va_start的第二个参数是动态参数...之前的参数,这里是nID。

CString strMessage;
strMessage.FormatV(strMessageFormat, argList);

va_end(argList);

return strMessage;
}

2) 在需要传参数的消息文本中插入一些占位符,如%d(整数),%s(字符串)......

STRINGTABLE
BEGIN
IDS_HELLO_WORLD "Hello, %s!"
END

 3)然后在加载消息字符串时就可以传递参数了:

 mButtonTest.SetWindowText(MessageSource::GetMessage(IDS_HELLO_WORLD, _T("C++")));

第十步:编译新的语言

通过以上方法,实际上已经把所有应用程序中出现的文本都存在到一个.rc文件,因此,要编译新的语言版本就变得很简单:只要新建一个.rc,把其中所有内容翻译成新的语言,然后把原来的CameraAnimation.rc替换掉(注意:替换前要先备份!)重新编译即可。



posted @ 2012-04-06 09:18  maoruilin  阅读(2196)  评论(0编辑  收藏  举报