多语言实现

多语言示例

本地化与国际化

先解释两个名词,本地化国际化

  • 本地化 在软件工程中一般将外文界面的软件翻译为本土语言称为本地化,在中国也叫汉化。
  • 国际化 与本地化相反,一般将本土语言翻译成外文称为国际化

其实二者没什么本质区别,在软件工程上都是实现软件的多语言支持。

多语言要求

对c++程序,实现多语言的基础是资源化,所有需要实现多语言的字段,都应该存在于资源中而不应该以明文的方式写在代码中。

例如:

acutPrintf(_T("测试资源"));	// 错误

// 正确的示范:
CString str;
str.LoadString(IDS_STRING1);
acutPrintf(str);

一. 资源副本

实现多语言的方案有很多,我们先说一种最简单的实现方案,这种方案的实现方式是分别编译不同语言版本的程序。这种方案实现简单,我们就以一个简单的mfc程序为例,代码在 ML_Demo_MultiVersion里。

  1. 创建一个简单的MFC 对话框程序

    image-20200304191526832
  2. 创建不同的语言配置

    image-20200304191617110

    image-20200304191726815

  3. 为资源创建不同语言的副本

    image-20200304191845976

    image-20200304191930775

    image-20200304192012713

  4. 在资源ID上右键属性,为不同副本设置条件

    image-20200304193032211image-20200304193102937

    注意,它们ID相同,但语言不同,条件也不相同(语言相同其实也没有关系)

    原有语言也要设置条件,Condition

  5. 修改不同语言对应的资源内容

    image-20200304192136001

  6. 在项目上右键属性,修改资源配置

    image-20200304193353651

    image-20200304193315500

    注意默认配置也要修改

  7. 现在分别运行两个配置可以看到效果

    Debug_enu:

    image-20200304193513865

    Debug:

    image-20200304193554124

  8. 总结:

    这种方式实现的多语言实现简单,只需要新增加配置,创建已有资源的副本就可以。但维护困难,适合已经完成的项目

    这种方式多语言的资源存在于同一个rc文件中,如果要对资源进行修改,必须同时修改多处,很容易遗漏,也没有太好的办法使用各种本地化工具进行维护。

二、翻译多个资源文件

资源副本存在于同一个rc文件中,不方便管理,我们的第二个方案,是使用不同的rc文件来管理资源。本示例代码在 MLDemo_MultiRc 文件夹中。我们还是以mfc对话框应用程序为例。

  1. 重复方案一中的步骤 1和2,创建 debug_enu配置。

    image-20200304191526832

    image-20200304191617110image-20200304191726815

  2. 复制资源文件

    在工程文件夹下,找到MLDemoMultiRc.rc,复制到同名文件夹下的 MLDemoMultiRc_en.rc

    image-20200305110450963

    注意,在同文件夹中复制,它可以和原rc文件共用同一个resource.h

  3. 将新的资源文件加入工程

    image-20200305103757280

    在资源文件夹上右键添加现有项,添加新的资源文件。

  4. 修改资源

    在资源界面下选择英文资源文件,修改其中的资源使用的字体

    image-20200305110540970

  5. 为不同配置选择资源

    此时,编译项目会提示资源重复:

    image-20200305104122315

我们需要为不同的配置配置字体,在解决方案资源管理器视图分别选择两个rc文件,点击属性,为不同的配置排除不正确的字体:

image-20200305104346809

img

注意这里是排除而不是选择,所以要为中文配置排除其它语言的资源,为英文配置排除中文资源。

  1. 编译运行

    image-20200305104536611image-20200305104555426

到目前为止,所有操作都是手动完成,与方案一并无区别。也存在当资源更改好维护不便的问题。但是由于不同语言的资源存在于不同的文件,我们可以借助工具。

三、 使用工具翻译多个资源文件

多语言工具有很多,我们在这里使用经典的 Lingobit Localizer 。在方案2的基础上优化:

  1. 打开 Localizer软件,新建项目

image-20200305105020713

  1. 选择本地化的原始语言,我们是中文,目标语言是英文:

image-20200305105119360

  1. 选择要本地化的文件 MLDemoMultiRc.rc

image-20200305105220305

  1. 完成后可以看到资源文件中的所有条目:

image-20200305105413746

  1. 我们忽略代码条目,将中文条目翻译成英文:

image-20200305105949210

  1. 配置翻译文件目标路径:

image-20200305110135475

  1. 生成目标文件:

点击创建或运行,选择要输出的语言。

image-20200305110259920

此时可以看到已经生成了MLDemoMultiRc_en.rc文件

image-20200305110357304

  1. 重新编译运行

image-20200305111827172

  1. 当资源发生改变时,比如添加一个字符串

image-20200305111931157

在Localizer中点击扫描:

image-20200305112159220

可以看到新添加的资源已经被导入进来了。

  1. 总结

    使用工具可以方便的管理翻译资源,避免重复劳动,也避免了难维护的缺点。这种方式可以为不同的语言管理不同的资源。一般已经可以满足我们的需求。

四、资源dll

以上三个方案,都是直接将代码编译为不同的语言版本,需要分别向客户提供分发包,或者将程序分为多个语言目录,无法实现同一个程序加载、切换多个不同的语言版本。

要实现在同一个编译版本中实现多语言,就需要使用资源dll。核心思想是将资源编译为单独的dll。

仍然以mfc对话框工程类型为例,资源dll的示例代码存放在 MLDemo_ResDll中。

  1. 创建mfc对话框应用程序

    image-20200305141203067

  2. 使用Localizer 创建多语言资源

    image-20200305141421351

    这次与之前不同,我们添加两种目标语言: 繁体中文和英文

    image-20200305141715900

    生成位置设置在语言对应的子目录中。

  3. 切换当前翻译语言进行翻译

image-20200305142447291

image-20200305142607442

  1. 创建资源dll项目

    在解决方案中添加新建项目:

    image-20200305142656792

    选择动态链接库项目,项目名称与语言一致,这里是 zh-TWen

    image-20200305142819273

    并删除所有代码文件

    image-20200305143224690

  2. 使用Localizer翻译资源

    翻译后,资源文件刚好生成在两个对应的工程目录中

    将其添加到对应工程中

    image-20200305143421573

    此时编译工程会报错

    image-20200305143456429

    这是因为resource.h在主工程目录中,我们在包含目录中添加该目录

    image-20200305143551575

    注意,是资源选项

    还要设置dll入口点为无,否则会报错

    2>LINK : error LNK2001: 无法解析的外部符号 __DllMainCRTStartup@12
    2>W:WorkgitslocalizationdemoMLDemo_ResDllDebugen.dll : fatal error LNK1120: 1 个无法解析的外部命令

    image-20200305143743791

  3. 配置项目生成路径,最终生成目录如下所示:

    -- MLDemo_ResDll.exe
    -- zh-TW
    ----- ML_Demo_ResDll.dll
    -- en
    ----- ML_Demo_ResDll.dll
    

    image-20200305144309656

到现在为止,资源dll已经创建,下面应该修改代码来为不同的语言配置不同的资源

  1. 加载资源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();

运行可以看到,三个语言版本的界面会依次弹出。

我们可以通过配置、注册表等方式在程序运行时,用于控制当前语言版本。

image-20200305154028664

image-20200305154043649

image-20200305154053656

原文地址:https://www.cnblogs.com/xzh1993/p/12424727.html