VC实现多语言化有多种方法,下面简要介绍了几种方法并进行了比较。
方法1:为每个资源项(对话框、字符串、菜单等)创建一个或者多个副本,并将其内容更改为目标语言,并在程序启动时调用SetThreadLocale()函数设置语言环境,以后程序就会自动调用指定语言版本的资源了。如需从资源加载一个字符串,代码如下:
CString s;
s.LoadString(IDS_MESSAGE);
//Do whatever with s…
但是LoadString()函数不是直接返回字符串,使用它会增加代码行数,以下函数可解决此问题:
static __inline
CString LS(int ids)
{
CString s;
s.LoadString(ids);
return s;
}
AfxMessagebox(LS(IDS_MESSAGE));
1 2 3 4 5 6 7 8 9 |
|
方法2:使用一个纯资源DLL来实现不同语言的动态加载。
新建一个DLL项目,将exe项目的res文件夹、rc文件和resource.h头文件一并拷贝到新项目目录,然后编新项目生成资源DLL文件。在exe项目启动时调用AfxLoadLibrary和AfxSetResourceHandle指示需要加载资源的来源。
下一节将以此方法为例详细说明如何实现多语言化。
方法3:使用GNU gettext机制
首先创建基于不同语言的po文件,里面分别存放着对同一ID的不同语言的描述,然后将其编译为mo文件供gettext函数使用。po文件是可读的,推荐用poedit软件来编辑,而mo文件是二进制的。在使用gettext机制的程序里,容易看到以下代码片段:
#define _(String) gettext (String)#define LOCALEDIR "/usr/share/locale/"#define PACKAGE "foonly" int main(int argc, char* argv[]) {setlocale (LC_ALL, "");bindtextdomain (PACKAGE, LOCALEDIR);textdomain (PACKAGE);printf(_("Hello, GetText!\n"));return 0;}
但是gettext机制还没有移植到Windows下,所以具体过程并不像上面说的这么容易。然而可贵的是思想,我们可以用ini文件来实现一个简单的gettext机制,当然需要付出多写几百行代码的代价 :(
参考文章:http://www.linuxdiyf.com/viewarticle.php?id=101358
方法4:使用Xml文件。如果你了解Android开发,那么你可能早就想到了这种方法。在Android应用程序中,岂止是国际化问题,就连界面布局都是基于Xml的,Google已经为开发者做好了基础工作(InFlate函数),开发者只需要按照规范来编写XML文件就可以了。但是有点同方法3一样,VC并没有提供这种机制,必须要开发者自己实现才行,比如使用libxml2库。用这种方法的好处是,普通翻译人员就可以修改xml文件来辅助开发。这几种方法的比较:
方法 | 来源 | 优点 | 缺点 | 使用频率 |
资源副本 | VC默认 | 无需编写额外的代码和新建项目,实现快 | 当要翻译成的语言版本较多时,会造成资源臃肿和运行时内存占用量大;不利于多人合作翻译 | 一般 |
资源DLL | VC默认 | 保持原项目资源的简洁;利于人员分工进行翻译;减小运行时内容占用; | 当要添加新的资源时需要同步所有语言项目中的资源 | 较高 |
gettext | GNU Linux | 代码比较简练;提高可移植性;简化翻译过程;减小运行时内容占用; | 需要开发人员来实现此机制 | 较少 |
xml | Android | 使程序更加模块化;提高可移植性;简化翻译过程;减小运行时内容占用; | 需要开发人员来实现此机制 | 较少 |
以下内容基于VC 6.0。要实现界面多语言化,必须要先配置项目使其支持Unicode编码,文章《VC下的Unicode编程》 对此有详细介绍。
首先创建一个基于MFC的工程,在选择语言时选择 中文[简体,中国]。
项目框架选择对话框、单文档和多文档都可以,这里就选个基于对话框的吧,然后立即修该项目属性使其支持Unicode。
MFC已经为我们加入了一些资源,包括2个对话框、1个String Table等。为了节省空间修改了对话框的大小。
现在项目中的资源情况如下:
然后再新建一个DLL项目,为了方便管理可以将其添加到当前工作空间中。
立即修改此DLL项目使其支持Unicode,然后设置项目依赖性(Project->Dependencies…) ,资源DLL要在exe项目之前编译:
为了将生成的资源DLL自动拷贝到exe项目执行目录下,还需要修改DLL项目的Post-build Step:
然后将主项目目录下的res文件夹、resource.h、TestMultiLang.rc文件拷贝到ResENG项目目录下,res文件夹和resource.h直接替换,将ResENG.rc删除后再将TestMultiLang.rc重命名为ResENG.rc。
切换到资源视图就会发现这两个项目的资源内容是一样的了,将ResENG项目的资源更改为英文如下:
为了让程序在启动的时候加载英文语言资源,需要在CTestMultiLangApp::InitInstance()函数中Dlg创建之前添加如下语句:
HINSTANCE hLanguageDll = AfxLoadLibrary(_T("ResENG")); if (hLanguageDll) AfxSetResourceHandle(hLanguageDll);
1 2 3 |
|
最后重新编译TestMultiLang项目,运行就会发现对话框已经是英文界面了 :)
以上程序仅为示例,为了能使程序自动选择合适的语言,还需要做许多工作。比如要在程序中添加程序语言切换菜单或语言选择下拉列表框,并将用户喜好保存在ini配置文件中,然后在程序启动时自动读取此ini文件加载相应的资源DLL;如果用户未设置语言,则默认根据操作系统语言加载合适资源,如果不存在针对此语言的资源DLL,就使用最国际化的语言——英语。将实现如上功能的代码包装成一个函数如下:
void CTestMultiLangApp::LoadLanguage(){CString strPath, strIni, strLang, strDLL;HINSTANCE hLanguageDll, hLanguageNow;CIni ini; //保存本身的资源句柄static HINSTANCE hOriginalHandle = ::AfxGetResourceHandle(); //读取ini配置文件GetModulePath(strPath);strIni = strPath + "data\\config\\language.ini";ini.SetPathName(strIni);strLang = ini.GetString(_T("Default"), _T("Prefer"), _T("")); //根据用户喜好来设置if (strLang == _T("English"))//英文hLanguageDll = ::AfxLoadLibrary(_T("ResENG"));else if (strLang == _T("Chinese"))//中文hLanguageDll = hOriginalHandle;else{//用户未指定,则根据系统选择合适语言,默认为英文WORD wLangPID = PRIMARYLANGID(GetSystemDefaultLangID()); if (wLangPID == LANG_CHINESE)hLanguageDll = hOriginalHandle;else if (wLangPID == LANG_ENGLISH)hLanguageDll = ::AfxLoadLibrary(_T("ResENG"));elsehLanguageDll = ::AfxLoadLibrary(_T("ResENG"));} //保存已加载的资源DLL句柄hLanguageNow = ::AfxGetResourceHandle(); //加载新的资源DLLif(hLanguageDll)::AfxSetResourceHandle(hLanguageDll); //释放之前加载的资源DLLif (hLanguageNow != hOriginalHandle)FreeLibrary(hLanguageNow);}
因为在切换界面语言的过程中需要频繁地销毁和创建对话框,所以我们也把创建对话框的代码包装成一个函数:
BOOL CTestMultiLangApp::OpenWindow(){ CTestMultiLangDlg dlg; m_pMainWnd = &dlg; dlg.DoModal();}
1 2 3 4 5 6 |
现在在InitInstance()函数里简单得调用一下这两个函数就可以了:
BOOL CTestMultiLangApp::InitInstance()
{
AfxEnableControlContainer();
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
LoadLanguage();
if (!OpenWindow())
return FALSE;
return FALSE;
}
然后要实现用户界面的语言选择消息响应函数。该函数要将用户的选择保存到ini文件里,销毁当前窗口,然后再重新加载资源并创建新的窗口:
void CMainFrame::OnSelLanguage(UINT nID)
{
CString strPath, strIni, strLang;
CIni ini;
GetModulePath(strPath);
strIni = strPath + "data\\config\\language.ini";
ini.SetPathName(strIni);
if (nID == ID_EDT_LANG_ENG)
strLang = _T("English");
else if (nID == ID_EDT_LANG_CHN)
strLang = _T("Chinese");
//写入配置文件
ini.WriteString(_T("Default"), _T("Prefer"), strLang);
//销毁当前窗口
CTestMultiLangApp*pApp = (CTestMultiLangApp*)AfxGetApp();
pApp->m_pMainWnd = NULL;
this->DestroyWindow();
//创建新的窗口
pApp->LoadLanguage();
pApp->OpenWindow();
}
上面这段代码中pApp->m_pMainWnd = NULL这一句是关键,它切断了销毁当前程序消息向上的路由,因此进程不会被销毁,才有了机会重新创建新的窗口。如果项目是基于单文档/多文档的程序的话,还要加一句pApp->m_pDocManager = NULL才可以。
重新编译项目,现在语言就可以动态切换了。
如果项目不仅要支持中英文这两种语言,比如还需要支持德语,这里介绍两种方法来实现。
第一种方法显而易见————新建一个资源DLL项目。理论上这是可行的,但是有个棘手的问题:如果需要支持的语言很多,那么对程序资源的任何更改都需要同步更新到其他所有资源项目!
第二种方法是借用工具,由一个资源DLL制作出其他各语言版本的资源DLL。这个工具软件会把资源DLL中的字符串、对话框、菜单项等资源提取出来并保存到一个po文件里,之后使用poedit就可以进行翻译了。翻译完成后再根据此po文件和原资源DLL生成新的资源DLL。
工具软件截图如下: