Creating a Multilingual Scribble Application with the MS Resource Localization Toolset


Download Source Code and Example
(Unzip with directory preservation. If you just want to download the article code and compile it, click here to see further instructions)

Most programmers shy away from building applications that support more than one language, the programmer's native language (also called the 'domestic language').

The reason for this is that it is considered not very easy to maintain different languages during the development cycle, because each time a change is made in the programmer's native language, this change has to be made in whatever file that contains a localized version of the program's resources or text strings. As a result, localization is very often done at the end of the development cycle, when changes to the program's resources are not expected to happen anymore. But even at that point the question arises: "Is it worth to localize the resources at all? What happens with the next version of our application, will we be able to reuse the localization that was done for the current version?" It is obvious that the crucial problems are to find and to use the right tool, one that supports leveraging of existing localized versions of an application, and to use the right method for storing the localized resources.

So far, I have seen a variety of methods for storing localized resources, from simple ASCII text files to resource-only DLLs or even databases. In this tutorial I will show how to use a tool that comes for free, the "MS Resource Localization Toolset", and I will demonstrate how it can be seamlessly integrated into the VC Developer Studio and into every project at any time during the development cycle. As an extra bonus I will show how to create a localized version of MFC's Scribble sample application that will switch the user interface language at run time from English to German and vice versa. The method to store the localized resources (in this case the German resources) is creating a resource-only DLL that is being updated by a "Pre-link step" or a "Custom Build Step" in the VC IDE any time the domestic language's (English) resources are changed. This ensures that the German resource DLL will always contain exactly the same resources as the Scribble Executable that contains the domestic English resources except for those that have already been localized into German. This way the integrity of the DLL can always be guaranteed: A specific resource that can be loaded from the executable module can always be found in the German resource DLL. If that particular resource has not yet been localized with the Resource Localization Toolset, it will just still be in the domestic language.

This way, it is up to you to decide when to localize the contents of the DLL, you can do it any time you want to. Another major benefit of this method in general is that you need not bother about ANSI-Unicode conversion or code pages or even MBCS support: A Win32 resource is always Unicode. Furthermore, the multilingual Scribble application will run on all Win32 platforms (except Windows CE), including Windows NT 4, NT 3.51, Windows 95, Windows 98 and even on Windows 3.1x with Win32s installed (if you compile this sample with VC 4.0 or 4.1). The only drawback is: To use the MS Resource Localization Toolset you need a computer running Windows NT, because it requires API functions like BeginUpdateResource, UpdateResource and EndUpdateResource that are not supported on Windows 95. The sample was written for VC 5 but can easily be used with VC 4.x: Just copy scribble.vc4 to scribble.mak and scribenu.vc4 to scribenu.mak and you are ready to compile with VC 4.x. This tutorial assumes you are using VC 5 or VC4.x.

But enough of all this boring stuff, let's create a multilingual Scribble one step at a time:

Step One: Getting a Copy of the MS Resource Localization Toolset and Installing It

The first thing to do is to download the MS Resource Localization Toolset from ftp://ftp.microsoft.com/softlib/mslfiles/rltools.exe or from http://support.microsoft.com/download/support/mslfiles/RLTOOLS.EXE (You can get more information about the Resource Localization Toolset from: http://support.microsoft.com/support/kb/articles/Q110/8/94.asp?PR=CHS&T1=7d&FR=0&A=T&T=B&S=F& or from http://support.microsoft.com/support/kb/articles/q110/8/94.asp). Run this self-extracting-executable in a directory that is contained in your computer's PATH environment variable or run it in a temporary directory and copy these files to a directory in the PATH environment variable: RLMan.exe, RLEdit.exe, RLAdmin.exe, RLQuikEd.exe, DLGEdit.exe, RLMan.hlp, RLTools.hlp. A good place for these files could be the system32 directory of your NT installation.

Step Two: Copy the Scribble Sample and Compile a Slightly Modified Scribble Debug Build

Now we have all the tools at hand we need, it is time to copy all the files from the Scribble Tutorial's Step 8 into a separate directory which is assumed to be c:\scribble throughout this tutorial.

Open the scribble project file in the VC IDE and add a "&Language" popup menu to the "View" menu of Scribble's IDR_MAINFRAME and IDR_SCRIBBTYPE menu resources and insert two menu items with ID_ENGLISH and ID_GERMAN as IDs and "English" and "German" as the menu item text, respectively. We will add command and command-UI-Handlers for these menu items in another step. If you choose to make a Scribble version that uses the dynamically linked version of MFC, be sure to remove the /d "_AFXDLL" Project Option from the "Resources" tab of both the Debug and the Release Project Settings. This makes sure that all MFC resources are included in the resulting res file that is the resource compiler's output, which gives us a chance to localize these resources, too. Otherwise, all MFC resources would be loaded from mfc40.dll at run time which would be very confusing for the user who would see all resources properly localized in her native language except for instance the buttons for the Print Preview toolbar or other MFC resources which would only be found at runtime in mfc40.dll. Now compile Scribble to a debug build.

Step Three: Create a Resource DLL from scribble.res

In this step, we are going to create a Resource DLL from the scribble.res file that was created by the resource compiler in the previous step. This DLL will be the reference file that the Resource localization Toolset will use to create a localized German DLL. To create this DLL proceed like the following:

From the "File" Menu in Developer Studio choose "New". In the VC 5.0 Developer Studio the "New" Dialog appears where you should activate the second page ("Projects") and select "Win32 Dynamic Library".

In the "Project name" editline type "scribenu" and correct the "Location" editline to the directory that contains the Scribble project (e.g. "c:\scribble"). Leave the tick on the Win32 checkmark and click the OK. The scribenu project is now being created for you (if the VC 5 Developer Studio is not crashing now as it always does on my machine. If so, delete all scribenu files and retry to create that project). If you use VC 4.x click here to see instructions on how to do that with the VC 4.x Developer Studio.

Now select the "Settings" menu item from the "Project" menu (In VC 4.x from the "Build" menu") and select the "Win32 Debug" Project setting in the left pane (In VC 4.x the "scribenu - Win32 Debug (scribenu.dll)" setting). On the first tab ("General") change both the "Intermediate files" and the "Output files" editline to "debug" . Also,on the "Link" tab change the value for "Output file" in the "General" category to "debug/scribenu.dll".

Now clear out the "Object/library modules" editline and in the "Project Option" edit box add /NOENTRY and remove the ticks from the "Generate debug info" and "Link incrementally" checkboxes. Next select the "Customize" category of the "Link" tab and remove the tick from "Use program database".

Close the "Project settings" dialog by clicking "OK". Now we insert the scribble.res file we created in step two into this project: From the "Project " menu choose "Add to project "- "Files..." and select the scribble.res file from the debug directory (With VC 4.x it is the "Insert" - "Files into project" menu option). Do a "Rebuild All" and notice that the file "scribenu.dll" has been created for you in the debug directory. When using VC 5 you should now choose "Export Makefile..." from the "project" menu to have an external makefile named "scribenu.mak" for this project created.

For this project we do not bother about the release settings. We simply never need a release build of this DLL.

Step Four: Setting Up the Resource Localization Toolset's Project Files and Creating a Localized Version of the Resource DLL

There are three tools that we use from the Resource Localization Toolset: RLAdmin.exe, which is an administrative tool we only need to start a new project or to administer a project without RLAdmin's command line counterpart RLMan.exe, which is the second tool. The actual translation is being done with the third tool, RLEdit.exe. To set up the project for the German resource DLL first start RLAdmin. From RLAdmin's menu choose "Project"-"New". The "Name New Project File" File Dialog will appear. Browse to the scribble project directory (i.e. "c:\scribble"), type "scribble" in the File name edit line and click "Open". Now the "Select a Source File." File Dialog will appear. Change the directory to the Debug directory where the scribenu.dll file resides, choose this file (don't use "scribble.exe"!) and click "Open". You should now see the "New Master Project" dialog which should look like this:

Click OK and notice that the main window of RLAdmin now contains all resource strings of the scribenu.dll source file in blue color.

Now quit Rladmin and start RLEdit. RLEdit looks very similar to Rladmin and the proceedings in this application are also almost the same when setting up a new project for a particular language, in this case German: From RLEdit's menu choose "Project"-"New". The "Name New Project File" File Dialog will appear. Browse to the scribble project directory (e.g. c:\scribble), type "scribdeu" in the File name edit line and click "Open". Now the "Select a Master ProjectFile." File Dialog will appear. Choose the "scribble.mpj" file in the scribble project directory that was created when RLAdmin was shut down. Now you should see a dialog without a caption text where some values should be changed: Change the "Target File" value from "C:\scribble\scribenu.dll" to "C:\scribble\debug\scribdeu.dll" and finally set the "Glossary file" value to "C:\scribble\deu.TXT" and select "German" in the "Target Resource Language" combo box since we want a DLL with German resources. The dialog should now look like this:

Click OK and notice that the main window of RLEdit now contains all resources of the scribenu.dll source file in red color:

Now choose "Operations"-"Generate Target File" from RLEdit's menu and notice what has happened in c:\scribble\debug: A new DLL, scribdeu.dll, with exactly the same size as scribenu.dll has been created for you.

Step Five: Doing Some Translation in RLEdit

To translate a particular string in RLEdit just double click the string and fill out the "New Translation" edit line in the "Edit Token Text" dialog and click OK. Notice that the color of the translated text turns black and the text token is marked "CLEAN" in the second to right status bar pane. After translating some text tokens this way RLEdit might look like this:

It is a good idea to add each translated token to the Glossary by clicking the "Add to Glossary" button. The next time you need to translate the same string you can do so by simply clicking the "Translate" button (although I found out that this doesn't always work, maybe someone could find out why?). You can also download huge Glossaries from the MS ftp-Server (ftp://ftp.microsoft.com/developr/msdn/newup/glossary) and use them with the Resource Localization Toolset.

Step Six: Integrating the Resource Localization Toolset into the VC IDE and Scribble's Project File

In this step, we basically automate the task of creating a new German scribdeu.dll Resource DLL each time scribble's resources are changed.

The first thing to do is write a batch file that needs to be customized for you depending on where the bin directory of your VC installation is located. In my case this is the g:\devstudio\vc\bin directory. The batch file should look like this (don't forget to change the first line of the batch file according to your installation to call the vcvars32.bat file which sets the environment variables necessary to run nmake with an external makefile) and is saved in this tutorial as makeres.bat in the scribble project's directory (if all environment variables that vcvars32.bat sets are automatically set for you with your logon or on OS startup, you should comment the first two lines of this batch file):

call "g:\DevStudio\Vc\bin\vcvars32.bat"
@echo on
nmake /f scribenu.mak
rlman.exe -m scribble.mpj
rlman.exe -l scribdeu.prj
rlman.exe -p 1252 -n 7 1 -o 9 1 -w -r debug\scribenu.dll
                                      scribdeu.txt debug\scribdeu.dll
if exist release goto COPYDLL
md release
:COPYDLL
copy debug\scribdeu.dll release

For a detailed description of how this batch file works, click here.

In order to properly invoke this batch file from the VC IDE any time the resources are changed, we have to add a "Pre-link step" to the Scribble project (Note that with VC 4.x you need to add a "Custom Build Step" instead. Click here for instructions on how to make that Custom Build Step): From the "Project" menu choose "Settings", select the settings for the Win32 Debug Build in the left pane and activate the last tab ("Pre-link step") on the right side. In the "Pre-link description" edit line type something like "Resource DLL Creation". After that click on the first row of "Pre-link command(s):" and type "makeres.bat". The dialog should now look something like this:

Now click OK and do a rebuild all of Scribble's Debug build. If you want to see what the "Build" pane of VCs "Output" dialog bar should now issue, click here.

The reason why I prefer a "Pre-Link step" over a "Post-build step" is, that I want to see the errors and warnings of a build as the last line in the "Output" window, but you are free to change this if you like.

Step Seven: Writing the Code to Load the Resource DLL and to Switch Scribble's User Interface at Run Time

Now, at last, we are going to write some code: First add a private member variable m_hLanguageDLL of type HMODULE to the CMainFrame class and set it to NULL in CMainFrames constructor. This variable will hold the module handle of our German resource DLL. Now add two #include statements like this to the beginning of CMainFrame's implementation in mainfrm.cpp:

#include <afxpriv.h>
#include "childfrm.h"

We need the #include <afxpriv.h> directive because we use the private MFC message WM_SETMESSAGESTRING to change the idle message on the status bar ("For Help, press F1").

The next thing to do is to add a member function that allows us to change CMDIChildWnd's protected member variable m_hMenuShared. This menu will be destroyed when languages are changed and is always checked for integrity during MFC idle processing. We therefore must be able to change this handle to a reasonable value for the MFC framework. We do this by adding this member function to CChildFrame's public section in childfrm.h:

    HMENU &SharedMenu(void){return m_hMenuShared;};

In a previous step we added a "&Language" popup menu to the Scribble "View" menu and inserted two menu items with ID_ENGLISH and ID_GERMAN.

Now we add handler functions for these Menu IDs: Invoke the class wizard and add a single "OnLanguage" command handler for both ID_GERMAN and ID_ENGLISH to the CMainFrame class which should look like this:

void CMainFrame::OnLanguage()
{
    HMODULE hCurrentModule;

    if (m_hLanguageDLL)
    {
        FreeLibrary(m_hLanguageDLL); 
        m_hLanguageDLL=NULL;
        hCurrentModule=GetModuleHandle(NULL);
    }
    else
        hCurrentModule=m_hLanguageDLL=LoadLibrary(_T("scribdeu"));

    ASSERT(hCurrentModule); //is the resource DLL existent?
    
    AfxSetResourceHandle(hCurrentModule);

    CDocTemplate* pTemplate;
    POSITION pos;


    //Determining Scribbles CMultiDocTemplate:
    //if we had an appliction with more than one template, we would
    //have to make a loop over
    //CScribbleApp's CMultiDocTemplate like this:
    /*
#if _MFC_VER>=0x400
    pos = AfxGetApp()->GetFirstDocTemplatePosition();
    while (pos!=NULL && (pTemplate=AfxGetApp()->
           GetNextDocTemplate(pos))!=NULL)
        {
#else    
    for( pos = m_templateList.GetHeadPosition(); pos != NULL; )
        {
        pTemplate=(CDocTemplate *)m_templateList.GetNext( pos );
#endif
        ASSERT(pTemplate);
        ASSERT(pTemplate->IsKindOf(RUNTIME_CLASS(CMultiDocTemplate)));
        //Now change all MDI children's shared menus....

        }
    */

#if _MFC_VER>=0x400
    pos = AfxGetApp()->GetFirstDocTemplatePosition();
    ASSERT(pos);
    pTemplate=AfxGetApp()->GetNextDocTemplate(pos);
#else
    pos = m_templateList.GetHeadPosition(); 
    ASSERT(pos);
    pTemplate=(CDocTemplate *)m_templateList.GetNext( pos );
#endif
    ASSERT(pTemplate);
    ASSERT(pTemplate->IsKindOf(RUNTIME_CLASS(CMultiDocTemplate)));
    

    CMenu DefaultMenu,SharedMenu;
    DefaultMenu.LoadMenu(IDR_MAINFRAME);
    SharedMenu.LoadMenu(IDR_SCRIBBTYPE);

    CMenu *pMenu = GetMenu();
    ASSERT(pMenu );
    
    if (pMenu->m_hMenu==m_hMenuDefault)
        SetMenu(&DefaultMenu);
    else
    {
        SetMenu(&SharedMenu);
        ::SendMessage(m_hWndMDIClient, WM_MDISETMENU,NULL,
                      (LPARAM)GetWindowMenuPopup(SharedMenu.m_hMenu));
            //this re-populates the Window-Menu
    }
    ::DestroyMenu(m_hMenuDefault);
    ASSERT(((CMultiDocTemplate *)pTemplate)->m_hMenuShared);
    ::DestroyMenu(((CMultiDocTemplate *)pTemplate)->m_hMenuShared);

    m_hMenuDefault=DefaultMenu.m_hMenu;
    ((CMultiDocTemplate *)pTemplate)->m_hMenuShared=SharedMenu.m_hMenu;


    POSITION docpos=pTemplate->GetFirstDocPosition( );
    CDocument *pDocument=NULL;
    while (docpos)
    {
        pDocument=pTemplate->GetNextDoc(docpos);
        ASSERT(pDocument);
        POSITION viewpos = pDocument->GetFirstViewPosition();
        CWnd *pParent;
        while (viewpos != NULL)
        {
            CView* pView = pDocument->GetNextView(viewpos);
            if (pView && NULL!=(pParent=pView->GetParentFrame())
                && pParent->IsKindOf(RUNTIME_CLASS(CChildFrame)))
                ((CChildFrame *)pParent)->SharedMenu()=SharedMenu.m_hMenu;

        }
    }

    m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT));
        //Update the other panes of status bar
    SendMessage(WM_SETMESSAGESTRING, (WPARAM)AFX_IDS_IDLEMESSAGE);
    m_wndStatusBar.UpdateWindow();//Update 1st pane of status bar


    DefaultMenu.Detach();
    SharedMenu.Detach();
    
}

Now once again invoke the class wizard and add a single "OnUpdateLanguage" command-UI-handler for both ID_GERMAN and ID_ENGLISH to the CMainFrame class which should look like this:

void CMainFrame::OnUpdateLanguage(CCmdUI* pCmdUI) 
{
    BOOL bVal=(pCmdUI->m_nID==ID_GERMAN && !m_hLanguageDLL ||
               pCmdUI->m_nID==ID_ENGLISH && m_hLanguageDLL);
    pCmdUI->Enable(bVal);
    pCmdUI->SetCheck(!bVal);
}

Finally we have to add some code to CMainFrame's destructor which now should look like this:

CMainFrame::~CMainFrame()
{
    if (m_hLanguageDLL)
        FreeLibrary(m_hLanguageDLL);
}

Conclusion

If you now run the Scribble sample, you will see that all visual aspects of Scribble change to the chosen language. If you need to change the resources of your German resource DLL in a more advanced way, e.g. if you want to change the size of a particular user element item in a dialog you can do so from within RLEdit: From the "Operations" menu choose "Resize Dialogs" which starts Dlgedit.exe for you with the "Open Include File" dialog box started initially. Click the Cancel button in this dialog box (I know, this is really annoying :-( ) and choose the dialog you want to modify from a listbox. You can now modify that dialog visually like in the VC IDE. Please be aware, that you cannot modify dialogs with Dlgedit.exe that have extended styles or controls with extended styles. This is not a limitation of the Resource Localization Toolset itself, because RLEdit and RLMan *can* create dialogs with extended styles, it is a limitation of DlgEdit that ships with the Resource Localization Toolset but originates from the Platform SDK (maybe someone out there could take the DlgEdit sources that come with VC and the Platform SDK and make DlgEdit "extended style aware"?). For information on how to use extended styles in your applications dialogs and still use Dlgedit.exe click here.

I personally think, that using the MS Resource Localization Toolset can make life easier when you do multilingual development or development for international markets. Although there are some severe bugs in this tool you have to know and work around, I think it is worth using it. If you find bugs in the Resource Localization Toolset, please let me know, so I can keep a list of bugs and possible workarounds here.

What Else Could be Done...

Now that we're through this tutorial let's think about what still should be done to make the multilingual scribble sample more like a professional application. What about enabling Scribble for more than two languages? What about switching the help file, too (change CWinApp::m_pszHelpFilePath)? If we had inserted the 'Tip of the day' from the Component Gallery, what about enabling that for the additional languages? Because all this is pretty boring stuff and is beyond the scope of this basic tutorial I leave that for you to make it work in your own applications.

Upates of this Article

Initial version: 29 July 1998
1st Update: 02 August 1998: Sample now also updates the MDI window menu. Added the URL of glossary files.Documented DlgEdit's lack of support for extended style bits. Added new section ("What else could be done...")
Last updated: 02 August 1998

Goto HomePage
© 1997 Zafir Anjum 
Contact me: zafir@home.com 
1794


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

  • Instead of only managing projects organizations do need to manage value! "Doing the right things" and "doing things right" are the essential ingredients for successful software and systems delivery. Unfortunately, with distributed delivery spanning multiple disciplines, geographies and time zones, many organizations struggle with teams working in silos, broken lines of communication, lack of collaboration, inadequate traceability, and poor project visibility. This often results in organizations "doing the …

  • When it comes to desktops – physical or virtual – it's all about the applications. Cloud-hosted virtual desktops are growing fast because you get local data center-class security and 24x7 access with the complete personalization and flexibility of your own desktop. Organizations make five common mistakes when it comes to planning and implementing their application management strategy. This eBook tells you what they are and how to avoid them, and offers real-life case studies on customers who didn't let …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds