Resource DLLs and Language Selection Menu

Introduction

In today's world, localization and translation of your software becomes an important feature because it dramatically helps boost sales. As far as Win32/MFC applications are concerned, managing different languages for your app requires using satellite DLLs.

This article describes a very 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) to your app by simply adding a few of lines of code. This includes:

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

It also explains how to create the satellite/resource DLLs, although this is already covered in many other articles. By the way, I am the developer of appTranslator, a localization tool that, among other things, can create the resource DLLs for you, freeing you from the hassle of managing Visual Studio projects for all these resource DLLs.

Background

There are a couple of articles on that deal with localization and resource DLLs. There is even one that shows a language menu, but this menu is hard-coded. My article describes an automated language menu that looks up the list of available languages and automatically builds the menu accordingly.

A Few Words about Resource DLLs

It is commonly accepted that the most flexible method to support multiple languages in your app is to use 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 startup, the application decides which language it should use (according to user preferences) and loads the resource DLL accordingly.

Resource DLLs can be created by using a dedicated Visual Studio project. Better yet, they can be created by localization tools such as appTranslator. One nice thing about appTranslator is that the developer doesn't have to worry about the creation and maintenance of resource DLLs: Just clicks the Build button and it creates them for you!

By the way, packing all languages into a single EXE is theoritically possible, but it just doesn't work. That' because most high-level APIs that load resources—such as LoadString(), DialogBox(), and so forth—won't let you specify the language you want. SetThreadLocale() 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's the list of steps to add support for resource DLLs (a languages 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:
  3. #include "LanguageSupport.h"
    
    class CMyApp : public CWinApp
    {
    public:
       CLanguageSupport m_LanguageSupport;
       ...
    };
    
    BOOL CMyApp::InitInstance()
    {
       // Comment out this line to prevent MFC from doing its own
       // resource DLL processing. See explanation below.
       // CWinApp::InitInstance();
       ...
       // You have this line, don't you!
       SetRegistryKey(_T("MyCompany"));
    
       // Load the resource DLL according to user preferences
       m_LanguageSupport.LoadBestLanguage();
       ...
    }
    
  4. 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.
  5. In your CMainFrame class, add an update menu handler for the Language menu item. Assume you named it OnUpdateToolsLanguage(). Fill it as follows:
  6. void CMainFrame::OnUpdateToolsLanguage(CCmdUI *pCmdUI)
    {    // Note: This is the UPDATE_COMMAND_UI handler, not the
         // COMMAND handler!
    
       // Creates the languages submenu (only the first time the
       // menu is opened!)
       theApp.m_LanguageSupport.CreateMenu(pCmdUI);
    }
    
  7. Add the menu handler for the languages menu item:
  8. In MainFrm.h, add the handler declaration somewhere in the protected part of CMainFrame:
  9.  afx_msg void OnLanguage(UINT nID);

    In MainFrm.cpp, add the handler definition and its message map entry:

    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ...
    ON_COMMAND_RANGE(ID_LANGUAGE_FIRST,ID_LANGUAGE_LAST, OnLanguage)
    // These IDs are declared in LanguageSupport.h
    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 languages items in the Language submenu.
  10. Last step:

    In the String Table (resources), add a string named IDS_RESTART with the text "Please restart %1".

    Note: You can replace %1 by your app's name.

How to Create the Resource DLLs

First of all, the CLanguageSupport class assumes the DLLs are named MyAppXXX.dll, where MyApp.exe is the name of your executable file and XXX is the three-letter acronym of the language they contain (for example, 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 match the three-letter acronym in the file name.

The easiest way to create the DLL is to use appTranslator because 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 everyone uses my tool, so here's the manual way to do 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, on the Application Settings tab, select DLL and Empty Project.
  • Turn it into a resource DLL (also known as a Resource-only DLL or Satellite DLL): Open the project properties, select All Configurations in the configurations combobox, and then open the Linker, Advanced tab. Set Resource Only DLL to Yes.
  • Note to VC6 users: This setting does not exist in the 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 rename your MyApp.rc file to MyAppDEU.rc (or whatever language acronym applies).
  • Modify your path: In the Resource View, right-click MyAppFRA.rc (MyAppFRA in Visual Studio 6) and open 'Resource Includes...'. Modify the path/filename of all included resource files to include their translation. The MFC resources translations are stored in a sub-directory per language: l.xxx\ where xxx is the three-letter acronym of the language. For example, change #include "afxres.rc" to #include "l.deu\afxres.rc".
  • Set the language properties: 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 (for example, German (Germany)). Make sure the language matches the three-letter acronym used in the DLL name.

You now can compile the DLL. I suggest you edit the output settings of the DLL project to copy the file side-by-side with your main exe (in other words, in the same directory). You now have a resource DLL. Of course, it's not translated yet but that's the translator's job. By the way, did I mention that I'm the author of a great localization tool that (among other things) completely handles the whole resource DLL creation process?

Start your app and open the Tools menu (or whatever menu where you created the Language item). The submenu should contain English and German.

Follow the same procedure to create DLLs for whatever 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.

Resource DLLs and Language Selection Menu

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 does its best to check support of targetted languages on the user's computer.

Does it support on-the-fly language switching?

Yes! However, there is some work on your side that CLanguageSupport can't do for you, such as updating the menus, views, and control bars, making sure your app doesn't cache any resource (such as texts from the string table), and so forth

By default, CLanguageSupport displays a message box asking the user to restart the application. To enable on-the-fly language switching, modify the menu handler call as follows (use 'true' as the 2nd 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, ...).

The Sample Application

It consists in a simple MFC AppWizard-generated project in which I followed the step-by-step method described above to add a Language submenu. I also created two resource DLLs (French and German) whose translation is more or less completed (The French one is pretty much completed. The German one is about half done).

To test the app, you should first compile the three projects (the EXE + the rwo 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 project files (and workspace/solution) for both VC6 and VS .NET. I 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 Paul DiLascia's CStaticLink class (with minor modifications).

How Does CLanguageSupport Work?

LoadLanguage(): What does that imply?

The LoadBestLanguage() function consists of two tasks: the identification of the language the preferred language and the loading of the DLL.

Identifying the language to load: Either the user earlier selected a language in the language menu; then, of course, you load it (you know it by looking up the Registry). Apparently, (s)he never made such a selection before. Then, you are going to look for quite a few possible languages that should fit user preferences. As soon as you find a resource DLL for your app that matches that language, you load it. If you don't find any match, you 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: You load the DLL using LoadLibrary() and set it as the default resource container by using AfxSetResourceHandle(hDll).

CreateMenu(): Creating the languages submenu

This function looks up all the DLLs available in the directory of the EXE whose name matches the pattern MyAppXXX.dll. It then looks up 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 languages supported by Windows woud be the only solution, which I find 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 whether the language is supported by the user's Windows version, 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 (the charset, actually) 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 because your app wouldn't display well in that language anyway.

OnLanguageSwitch(): Take the 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 the 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 indeed do some part of that work: It does the same kind of work at startup in CWinApp::InitInstance(). This is why you must comment out the call in the appwizard-generated code: You don't want MFC and your code to step on each other's toes. There's one important thing MFC doesn't care about: It doesn't support manual selection of the language. This also means that MFC doesn't offer support for a language menu.

It's a pitty because there are many scenarios where the user could do a better choice than the one MFC makes 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. This is why it's important to have a language selection menu in addition to automated language detection.

Things are even a little worse: You'd think you 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 overriden. Some of the functions are virtual but because overrides can't call the static/private helper functions, you 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 give a look into tools such as appTranslator that not only help you manage the translations of your apps but also create the resource DLLs for you.



About the Author

Serge Wautier

My name, is Serge Wautier. I'm the author of appTranslator, a great localization tool for your Visual C++ applications
http://www.apptranslator.com

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

  • Live Event Date: August 14, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Data protection has long been considered "overhead" by many organizations in the past, many chalking it up to an insurance policy or an extended warranty you may never use. The realities of today make data protection a must-have, as we live in a data driven society. The digital assets we create, share, and collaborate with others on must be managed and protected for many purposes. Check out this upcoming eSeminar and join eVault Chief Technology …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds