分类: C/C++
2008-08-23 22:35:39
In today's world, localization and translation of software has become an important feature since it dramatically helps in boosting sales. As far as Win32/MFC applications are concerned, managing different languages for your app requires the use of satellite DLLs.
This article describes an easy-to-use method to support multiple languages in your C++/MFC applications. It shows how to add support for satellite DLLs (also known as resource DLLs) in your app by simply adding a few of lines of code. This includes:
It also explains how to create satellite/resource DLLs, although this is already covered in many other articles. By the way, I am a developer of , a localization tool that (among other things) creates resource DLLs for you, freeing you from the hassle of managing Visual Studio projects for all these resource DLLs.
There are on CodeProject that deal with localization and resource DLLs. ( is a very good introduction to localization of MFC apps.) was published after I started writing mine! However I decided to go on and publish mine because I believe the topic of language selection menu was not covered in any of the articles on CodeProject. Also, other articles don't cover MFC7.
It is commonly accepted that the most flexible method to support multiple languages in your app is to use the so-called resource DLLs (also known as satellite DLLs). The idea is to create one DLL per language. The DLL contains a copy of all your application resources translated into one given language. Therefore, if your app's original version is English and you translate it to French, German and Japanese, you'll end up with three resource DLLs: The English resources stay in the .exe and there is one DLL for French, one for German and one for Japanese. Whenever you make a new translation of your app, you simply need to add one more DLL to your installer.
At start-up, the application decides which language it should use (according to user preferences) and accordingly loads the resource DLL. Resource DLLs can be created using a dedicated Visual Studio project. Or they can be created by using the localization tools such as . One nice thing about is that the developer need not worry about the creation and maintenance of resource DLLs: Just hit the Build button and it creates them for you!
By the way, packing all the languages into a single EXE is theoretically possible. But it just doesn't work :-( The reason is that most high level APIs that load resources (such as LoadString()
, DialogBox()
et al won't let you specify the language you want. And has stopped working the way you expect since Windows 2000 (and it never existed on Win9x).
Here are the steps required to add support for resource DLLs (and language menu) in your main application:
CMyApp
is your application class), add the lines shown in bold: #include "LanguageSupport.h" // The class that handles // the gory details class CMyApp : public CWinApp { public: CLanguageSupport m_LanguageSupport; //<-- Language switching support ... };
BOOL CMyApp::InitInstance() { // CWinApp::InitInstance(); <-- Comment out this line to // prevent MFC from doing its own // resource DLL processing. See // explanation below. ... // You already have this line, don't you ! SetRegistryKey(_T("MyCompany")); // loads the correct resource DLL // according to user preferences m_LanguageSupport.LoadBestLanguage(); ... }
CMainFrame
class, add an update menu handler for the Language menu item. Let's assume you named it OnUpdateToolsLanguage()
. Fill it as follows: // UPDATE_COMMAND_UI handler void CMainFrame::OnUpdateToolsLanguage(CCmdUI *pCmdUI) { // Creates the languages submenu theApp.m_LanguageSupport.CreateMenu(pCmdUI); }
In MainFrm.h, add the handler declaration somewhere in the protected part of CMainFrame
:
afx_msg void OnLanguage(UINT nID);
In MainFrm.cpp, add the handler definition and its message map entry:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ... // These IDs are declared in LanguageSupport.h ON_COMMAND_RANGE(ID_LANGUAGE_FIRST, ID_LANGUAGE_LAST, OnLanguage) END_MESSAGE_MAP() void CMainFrame::OnLanguage(UINT nID) { // User selected a language in the language sub menu theApp.m_LanguageSupport.OnSwitchLanguage(nID); }
Note: This handler cannot be added using the wizard because it is a command range menu handler: On handler for all language items in the Language submenu.
In the String table (resources), add a string named IDS_RESTART
with the text "Please restart %1". (Note: You can replace %1 with your app's name).
(By the way, I have written about String table and how to easily extract and format strings).
First of all, the CLanguageSupport
class assumes that the DLLs are named MyAppXXX.dll where MyApp.exe is the name of your executable file and XXX is the 3-letter acronym of the language they contain (e.g.: FRA stands for French, DEU for German and JPN for Japanese). Also, both your EXE and the DLLs should have a version info resource whose language matches the three letters acronym in the file name.
The easiest way to create the DLL is to use the , since the tool creates the dialog for you (you simply have to check 'Satellite DLL' in the properties). But of course, I won't assume that every one uses my tool ;-), so here's the manual way of doing it:
#include "afxres.rc"
to #include "l.deu\afxres.rc"
.
You can now compile the DLL. I suggest you to edit the output settings of the DLL project to copy the file side-by-side with your main EXE (i.e. in the same directory). You now have a resource DLL. Of course, it's not translated yet but that's the job of the translator.
Start your app; open the Tools menu (or the menu where you created the Language item). The submenu should contain English and German. Follow the same procedure to create DLLs for the other translations you need. Once your DLL is available, the only thing you need to do is copy it side by side with your app (.exe) and it will automatically be taken into account for language selection.
Yes! CLanguageSupport
works like a charm in both Unicode and ANSI builds. As explained below, it even makes its best to check for support for the targeted languages on the user's computer.
Yes! But this requires some work on your side that CLanguageSupport
can't do for you, such as updating the menus, views and the control bars, making sure your app doesn't cache any resource (such as texts from the string table),... By default, CLanguageSupport
displays a message box asking the user to restart the application. To enable on-the-fly language switch, modify the menu handler call as follows (use 'true
' as the second optional argument to the call):
void CMainFrame::OnToolsLanguage(UINT nID) { // Loads the language selected by user theApp.m_LanguageSupport.OnSwitchLanguage(nID, true); // TODO: Update/reload your UI to reflect the language change }
In addition, you must add the code that updates the current display of your app as (menus, views,...). tells you a little more about this part of the job but be aware there is going to be some custom work based on your app architecture and contents.
It consists of a simple MFC AppWizard-generated project in which I have followed the step-by-step method described above to add a Language submenu. I have also created two resource DLLs (French and German) whose translation is more or less complete. (The French one is pretty much completed. The German one is about half done.)
In order to test the app, you should first compile the three projects (the EXE + the 2 DLLs). Start the project and see in which language the app starts. If you have either French or German Windows, the app will start in French or German. Otherwise it will start in English. The Language menu is located under Tools. The zip file contains the project files (and workspace/solution) for both VC6 and VS.NET. I have also included a copy of the executable files (the EXE and the resource DLLs - ANSI Release build).
Credits: The sample app's About dialog uses 's CStaticLink
class (with minor modifications).
The LoadBestLanguage()
function performs two tasks: The identification of the language which is the preferred language and the loading of the DLL.
Identifying the language to load : If the user had earlier selected a language in the language menu, we load it (we know it by looking up in the registry). If he has never made a selection before, we look for a few possible languages that fit into the user's preferences. As soon as we find a resource DLL for our app that matches that language, we load it. If we don't find a match, we eventually fall back on the original version of the app: The language stored in the EXE itself.
Loading the DLL is rather a simple task: We load the DLL using LoadLibrary()
and set it as the default resource container using AfxSetResourceHandle(hDll)
.
This function looks for the DLLs available in the directory of the EXE whose name matches the pattern MyAppXXX.dll. It then looks for its language in the DLL's version info resource. (It doesn't identify it by the three letter DLL because... there's no simple way to find the language given the acronym. Brute enumeration of the languages supported by Windows would be the only solution, which according to me is a horrible method).
It then builds the menu according to the list of languages found. CreateMenu()
tries to display each language name in its own language (native name). It is careful enough to check if the language is supported by the Windows version of the user, in order to avoid displaying garbage (such as displaying Japanese on an English Win9x or NT4 system). If it finds that Windows can't display the language name; it falls back on the language name in the current user's language (as set in the Regional Options applet of the Control Panel).
Note: This detection is not 100% perfect: The fact that a language (actually the charset) is supported by Windows doesn't necessarily mean that the fonts for that language are installed. In such a case, the menu may display garbage. Now, this is probably not a major issue since your app wouldn't display well in that language anyway.
This one is the simplest function. All it does is store the user's choice into the registry (HKCU\MyCompany\MyApp\Settings : Language = (DWORD) LangId). It also asks the user to restart the app to load the new language. If caller wants to switch languages on the fly, the DLL for the new language is loaded right away.
MFC7.1 (Visual Studio 2003) does some part of that work: It does the same kind of work at start-up (in CWinApp::InitInstance()
. That is why we must comment out the call in the appwizard-generated code: We don't want MFC and our code to step on each other's toes). But there's one important thing MFC doesn't care about: It doesn't support manual selection of the language, which also means that MFC doesn't offer support for a language menu.
It's a pity because there are many scenarios where the user could make a better choice than what MFC does for him. Imagine for example an app available in English and French. On an Italian or Spaniard's computer, MFC would choose English. But many Italians and Spaniards understand French better than English. It would be a shame to prevent them from selecting a language they understand better. That is why it's important to have a language selection menu in addition to automated language detection.
Things are even worse: You'd think we could re-use the MFC code and simply tweak it to take user selection into account. Bad luck: Part of this code is in private
/static
MFC functions that can neither be called nor overridden. Some of the functions are virtual
but since overrides can't call the static
/private
helper functions, we pretty much have to rewrite everything from scratch. :-(
Thanks to CLanguageSupport
, managing resource DLLs should be really easy.
Creating the DLLs (and managing the corresponding projects) is not difficult at all but it's certainly a boring task. If you are serious about localization, I recommend you to give a look into the tools such as that not only helps you manage the translation of your apps but also creates the resource DLLs for you.
One last note: I hope you don't mind the ads ;-)