Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5833464
  • 博文数量: 671
  • 博客积分: 10010
  • 博客等级: 上将
  • 技术积分: 7310
  • 用 户 组: 普通用户
  • 注册时间: 2006-07-14 09:56
文章分类

全部博文(671)

文章存档

2011年(1)

2010年(2)

2009年(24)

2008年(271)

2007年(319)

2006年(54)

我的朋友

分类: C/C++

2008-08-23 22:35:39

Introduction

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:

  • Automatically selecting the most appropriate language at start-up according to user preferences.
  • Providing a submenu for language selection (in case the user is not happy with the default choice). See the picture above.

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.

Background

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.

A few words on resource DLLs

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).

The step-by-step method to support resource DLLs

Here are the steps required to add support for resource DLLs (and language menu) in your main application:

  1. Add LanguageSupport.h and LanguageSupport.cpp to your project.
  2. In MyApp.h and MyApp.cpp (assuming 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(); 
      ...
    }
  3. In your main menu, add a menu item labeled 'Language'. I usually add it in my Tools menu (if any), but it is up to you.
  4. In your 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); 
    }
  5. Add the menu handler for the languages menu item:

    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.

  6. Last step:

    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).

How to create the resource DLLs

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:

  • Create a Win32 DLL project: In Visual Studio 2003, choose File/New/Project..., enter a project name such as MyAppDEU to create a German version and click OK. Then, in the Application Settings tab, select DLL and Empty Project.
  • Turn it into a resource DLL (also known as resource-only DLL or satellite DLL): Open the project properties, select All Configurations in the configurations ComboBox and then open the tab Linker/Advanced. Set Resource Only DLL to Yes. Note to VC6 users: This setting does not exist in VC6 project property pages. You must manually add /NOENTRY in the linker settings edit box with the command line settings.
  • Create a copy of your EXE resource file and add it to the DLL project. I recommend you to rename your MyApp.rc file to MyAppDEU.rc (or whatever language acronym applies).
  • In the Resource View, right-click MyAppFRA.rc (MyAppFRA in Visual Studio 6) and open 'Resource Includes...'. Modify the path/filename of all the included resource files to include their translation. The MFC resources translation is stored in a sub-directory (per language): l.xxx\ where xxx is the three-letter acronym of the language. e.g.: Change #include "afxres.rc" to #include "l.deu\afxres.rc".
  • In the Resource View, open the version info (or create one if you don't have one) and set the language property of the Block Header to the right language (e.g.: German (Germany)). Make sure the language matches the three-letter acronym used in the DLL name.

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.

FAQ

Does CLanguageSupport work with Unicode? And with ANSI?

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.

Does it support on-the-fly language switch?

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.

The sample application

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).

How does CLanguageSupport work?

LoadLanguage(): What does that imply?

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).

CreateMenu(): Creation of the languages submenu

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.

OnLanguageSwitch(): Take user's language choice into account

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.

Why a custom class? Doesn't MFC handle all that?

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. :-(

Conclusion

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 ;-)

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found

阅读(1101) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~