多语言示例
本地化与国际化
先解释两个名词,本地化 和国际化
- 本地化 在软件工程中一般将外文界面的软件翻译为本土语言称为本地化,在中国也叫汉化。
- 国际化 与本地化相反,一般将本土语言翻译成外文称为国际化
其实二者没什么本质区别,在软件工程上都是实现软件的多语言支持。
多语言要求
对c++程序,实现多语言的基础是资源化,所有需要实现多语言的字段,都应该存在于资源中而不应该以明文的方式写在代码中。
例如:
acutPrintf(_T("测试资源")); // 错误
// 正确的示范:
CString str;
str.LoadString(IDS_STRING1);
acutPrintf(str);
一. 资源副本
实现多语言的方案有很多,我们先说一种最简单的实现方案,这种方案的实现方式是分别编译不同语言版本的程序。这种方案实现简单,我们就以一个简单的mfc程序为例,代码在 ML_Demo_MultiVersion
里。
注意,它们ID相同,但语言不同,条件也不相同(语言相同其实也没有关系)
原有语言也要设置条件,
Condition
注意默认配置也要修改
- 现在分别运行两个配置可以看到效果
Debug_enu:
Debug: - 总结:
这种方式实现的多语言实现简单,只需要新增加配置,创建已有资源的副本就可以。但维护困难,适合已经完成的项目。
这种方式多语言的资源存在于同一个rc
文件中,如果要对资源进行修改,必须同时修改多处,很容易遗漏,也没有太好的办法使用各种本地化工具进行维护。
二、翻译多个资源文件
资源副本存在于同一个rc
文件中,不方便管理,我们的第二个方案,是使用不同的rc
文件来管理资源。本示例代码在 MLDemo_MultiRc
文件夹中。我们还是以mfc对话框应用程序为例。
注意,在同文件夹中复制,它可以和原rc文件共用同一个
resource.h
。
我们需要为不同的配置配置字体,在解决方案资源管理器
视图分别选择两个rc文件,点击属性,为不同的配置排除不正确的字体:
注意这里是排除而不是选择,所以要为中文配置排除其它语言的资源,为英文配置排除中文资源。
到目前为止,所有操作都是手动完成,与方案一并无区别。也存在当资源更改好维护不便的问题。但是由于不同语言的资源存在于不同的文件,我们可以借助工具。
三、 使用工具翻译多个资源文件
多语言工具有很多,我们在这里使用经典的 Lingobit Localizer 。在方案2的基础上优化:
- 打开
Localizer
软件,新建项目
- 选择本地化的原始语言,我们是中文,目标语言是英文:
- 选择要本地化的文件
MLDemoMultiRc.rc
:
- 完成后可以看到资源文件中的所有条目:
- 我们忽略代码条目,将中文条目翻译成英文:
- 配置翻译文件目标路径:
- 生成目标文件:
点击创建或运行,选择要输出的语言。
此时可以看到已经生成了MLDemoMultiRc_en.rc
文件
- 重新编译运行
- 当资源发生改变时,比如添加一个字符串
在Localizer中点击扫描:
可以看到新添加的资源已经被导入进来了。
- 总结
使用工具可以方便的管理翻译资源,避免重复劳动,也避免了难维护的缺点。这种方式可以为不同的语言管理不同的资源。一般已经可以满足我们的需求。
四、资源dll
以上三个方案,都是直接将代码编译为不同的语言版本,需要分别向客户提供分发包,或者将程序分为多个语言目录,无法实现同一个程序加载、切换多个不同的语言版本。
要实现在同一个编译版本中实现多语言,就需要使用资源dll。核心思想是将资源编译为单独的dll。
仍然以mfc对话框工程类型为例,资源dll的示例代码存放在 MLDemo_ResDll
中。
- 创建资源dll项目
在解决方案中添加新建项目:
选择动态链接库项目,项目名称与语言一致,这里是zh-TW
和en
并删除所有代码文件 - 使用Localizer翻译资源
翻译后,资源文件刚好生成在两个对应的工程目录中
将其添加到对应工程中
此时编译工程会报错
这是因为resource.h
在主工程目录中,我们在包含目录中添加该目录
注意,是资源选项
- 还要设置dll入口点为无,否则会报错
2>LINK : error LNK2001: 无法解析的外部符号 __DllMainCRTStartup@12
2>W:\Work\gits\localizationdemo\MLDemo_ResDll\Debug\en.dll : fatal error LNK1120: 1 个无法解析的外部命令
- 配置项目生成路径,最终生成目录如下所示:
-- MLDemo_ResDll.exe
-- zh-TW
----- ML_Demo_ResDll.dll
-- en
----- ML_Demo_ResDll.dll
到现在为止,资源dll已经创建,下面应该修改代码来为不同的语言配置不同的资源
- 加载资源dll
HINSTANCE _hResInstance = nullptr;
BOOL LoadResDll(LPCTSTR szLan)
{
if (nullptr != _hResInstance)
{
::FreeLibrary(_hResInstance);
_hResInstance = nullptr;
}
CString strExePath;
AfxGetModuleFileName(AfxGetInstanceHandle(), strExePath);
TCHAR szDrive[_MAX_DRIVE];
TCHAR szDir[_MAX_DIR];
TCHAR szFName[_MAX_FNAME];
TCHAR szExt[_MAX_EXT];
_tsplitpath_s(strExePath, szDrive, szDir, szFName, szExt);
CString strDir;
strDir = szDrive;
strDir += szDir;
strDir += szLan;
CString strResDll = strDir + _T("\\") + szFName + _T(".dll");
_hResInstance = ::LoadLibraryEx(strResDll, nullptr, LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_AS_DATAFILE);
}
为了测试,我们简单的依次弹出不同语言的对话框:
LoadResDll(_T("chs")); // 其实没有这种语言,会加载失败
if (nullptr != _hResInstance)
{
AfxSetResourceHandle(_hResInstance);
}
CMLDemoResDllDlg dlg;
INT_PTR nResponse = dlg.DoModal();
LoadResDll(_T("en")); // 其实没有这种语言,会加载失败
if (nullptr != _hResInstance)
{
AfxSetResourceHandle(_hResInstance);
}
nResponse = dlg.DoModal();
LoadResDll(_T("zh-TW"));
if (nullptr != _hResInstance)
{
AfxSetResourceHandle(_hResInstance);
}
nResponse = dlg.DoModal();
运行可以看到,三个语言版本的界面会依次弹出。
我们可以通过配置、注册表等方式在程序运行时,用于控制当前语言版本。