Click here to Skip to main content
Click here to Skip to main content

Yet another way to localize your MFC apps

, 27 Jun 2005 CPOL
Rate this:
Please Sign up or sign in to vote.
Another way to localize your MFC application.

Introduction

This article will show you another way to localize your MFC apps. I'm sure it's not the best way, but it works for me.

History

There are many, many articles on the Internet on this topic (even on this site). Maybe you know the way Microsoft planned to do this in MFC, using resource DLLs: if you put all your program resources in a DLL, and you call it, for instance, "appSPA.dll", then when your app runs in Spanish Windows, it loads this DLL and your app is shown in Spanish.

This sounds good at first. But let's put this into practice. Let's say I have an application I want to translate to five languages (at least). Quickly I will realise I'll have to face two big problems:

  1. I have to give my DLL to a poor translator that knows nothing about programming, DLL libraries, ... How will he/she manage to do it? Not without our help and our time...
  2. And the big one: If I make changes to a resource, I have to replicate those changes to all the other language DLLs! What a nightmare! I'll surely forget some changes... what a mess!

Another approach

To get rid of the first problem, you must use plain text files, so that's what I have done. Then, my application loads them and does the translation automatically. You only have to put all your program strings in the app resources (this is a must!).

And how do we get rid of the second one ? That's easier! You only have to make all your dialogs translate themselves, and the app menu too.

Some code...

To illustrate all this, I've made a class called CTranslator. This class can translate any string, any dialog, any menu, any message box, ...

First, we must load our language file (remember, it is plain text!) using LoadLanguage(). Now we can use all the other methods...

To translate a string:

CString str = GetTranslatedString(IDS_STRING);

Let's see how it works....

First we load the string from the resources...

CString CTranslator::GetTranslatedString(UINT uiID)
{
    CString strOriginal;

    strOriginal.LoadString(uiID);

    if (strOriginal.IsEmpty())
    {
        return _T("");
    }

    return GetTranslatedString(strOriginal);
}

Here is how the real translation occurs...

CString CTranslator::GetTranslatedString(CString strOriginal)
{
    if (strOriginal.IsEmpty())
    {
        return _T("");
    }

    CString s = strOriginal;

    TakeOutBlanksArroundTab(s);

    s = "[" + s + "]";

    int iIndex = -1;

    const int iInfoSize = (int)m_info.size();

    for (int i=0; i<iInfoSize; i++)
    {
        char szAux[256];
        strcpy(szAux, m_info[i].szOriginal);

        if (strcmp(m_info[i].szOriginal, LPCTSTR(s)) == 0)
        {
            iIndex = i;
            break;
        }
    }

    if (iIndex == -1)
    {
        return strOriginal;
    }

    CString strTranslated(m_info[iIndex].szTranslated);

    if (strTranslated.IsEmpty())
    {
        return strOriginal;
    }

//     TRACE2("Original : [%s]\tTranslated : [%s]\n", LPCTSTR(strOriginal), 
//            LPCTSTR(strTranslated));

    return strTranslated;
}

The original text is between brackets (see attached file "french.txt" to see what I mean). In m_info, we store the strings, the original version, and the translated version. We only have to search...

To translate a dialog, simply put in OnInitDialog() a call to TranslateDialog(this);.

But how is the translation of a dialog done? There is a function that searches for all dialog controls and tries to translate them.

void CTranslator::TranslateDialog(CDialog *pDialog)
{
    CString strOriginal(_T(""));

    for (int iID = 0; iID < _APS_NEXT_CONTROL_VALUE; iID++)
    {
        pDialog->GetDlgItemText(iID, strOriginal);

        if (!strOriginal.IsEmpty())
        {
            CString s = GetTranslatedString(strOriginal);

            // TRACE2("%s is translated to %s\n", LPCTSTR(strOriginal), LPCTSTR(s));

            if (!s.IsEmpty() && strOriginal != s)
            {
                pDialog->SetDlgItemText(iID, s);
            }
        }
    }
    
    // Also translate dialog caption

    strOriginal = _T("");

    pDialog->GetWindowText(strOriginal);

    if (!strOriginal.IsEmpty())
    {
        CString s = GetTranslatedString(strOriginal);

        if (!s.IsEmpty() && strOriginal != s)
        {
            pDialog->SetWindowText(s);
        }
    }
}

Also, I've managed to use AfxMessageBox without any problems... Simply use this function (instead of ::AfxMessageBox):

int CTranslator::AfxMessageBox(LPCTSTR lpszText, UINT nType, UINT nIDHelp)
{
    CString strOriginal = CString(lpszText);

    CString s = GetTranslatedString(strOriginal);

    return ::AfxMessageBox(s, nType, nIDHelp);
}

int AFXAPI CTranslator::AfxMessageBox(UINT nIDPrompt, UINT nType, UINT nIDHelp)
{
    CString strOriginal;

    strOriginal.LoadString(nIDPrompt);

    CString s = GetTranslatedString(strOriginal);

    return ::AfxMessageBox(s, nType, nIDHelp);
}

void CTranslator::AfxFormatString1(CString & rString, UINT nIDS, LPCTSTR lpsz1)
{
    CString strFormat = GetTranslatedString(nIDS);
    
    AfxFormatStrings(rString, strFormat, &lpsz1, 1);
}

void CTranslator::AfxFormatString2(CString & rString, UINT nIDS, LPCTSTR lpsz1, LPCTSTR lpsz2)
{
    LPCTSTR rglpsz[2];
    rglpsz[0] = lpsz1;
    rglpsz[1] = lpsz2;

    CString strFormat = GetTranslatedString(nIDS);
    
    AfxFormatStrings(rString, strFormat, rglpsz, 2);
}

And for translating the menu, I've created a recursive method...

void CTranslator::TranslateMenu(CFrameWnd *pFrameWnd)
{
    ASSERT(pFrameWnd != NULL);

    // Destroy previous menu.

    pFrameWnd->SetMenu(NULL);

    if (::IsMenu(pFrameWnd->m_hMenuDefault))
    {
        ::DestroyMenu(pFrameWnd->m_hMenuDefault);
    }

    // Load default spanish menu

    CMenu *pMenu = new CMenu();

    ASSERT(pMenu != NULL);

    pMenu->LoadMenu(IDR_MAINFRAME);

    ::SetMenu(pFrameWnd->GetSafeHwnd(), pMenu->GetSafeHmenu());

    pFrameWnd->m_hMenuDefault = pMenu->GetSafeHmenu();

    // Now we can translate the menu...

    TranslateMenu(pMenu);

    // Detach the menu handle as we do not want to loose it.

    pMenu->Detach();

    delete pMenu;
    
    pMenu = NULL;

    // Redraw the new menu

    pFrameWnd->DrawMenuBar();
}

void CTranslator::TranslateMenu(CMenu *pMenu)
{
    CString strOriginal(_T(""));
    CString strTranslated(_T(""));

    WORD wMenuState;

    if (pMenu == NULL || !::IsMenu(pMenu->m_hMenu))
    {
        return;
    }

    int iSize = pMenu->GetMenuItemCount();

    // loop all the menu items in this level

    MENUITEMINFO menuItemInfo;

    for (int i=0; i<iSize; i++)
    {
        wMenuState = (WORD) pMenu->GetMenuState(i, MF_BYPOSITION);

        BOOL bIsPopup = wMenuState & MF_POPUP;

        // Get the menu string
        // pMenu->GetMenuString(i, strOriginal, MF_BYPOSITION);

        ZeroMemory(&menuItemInfo, sizeof(MENUITEMINFO));

        menuItemInfo.cbSize = sizeof(MENUITEMINFO);
        menuItemInfo.fMask = MIIM_TYPE;

        pMenu->GetMenuItemInfo(i, &menuItemInfo, TRUE);

        if (menuItemInfo.cch > 0)
        {
            menuItemInfo.cch++;
            menuItemInfo.dwTypeData = new char [menuItemInfo.cch];

            pMenu->GetMenuItemInfo(i, &menuItemInfo, TRUE);
            
            strOriginal = CString(menuItemInfo.dwTypeData);

            delete [] menuItemInfo.dwTypeData;
            menuItemInfo.dwTypeData = NULL;
        }
        else
        {
            strOriginal = _T("");
        }

        if (!strOriginal.IsEmpty())
        {
            strTranslated = GetTranslatedString(strOriginal);

            if (!strTranslated.IsEmpty() && strTranslated != strOriginal)
            {
                UINT uiID = 0;
                UINT uiFlags = MF_STRING | MF_BYPOSITION;

                uiID = pMenu->GetMenuItemID(i);

                if (bIsPopup)
                {
                    uiFlags |= MF_POPUP;
                    
                    HMENU hPopupMenu = pMenu->GetSubMenu(i)->m_hMenu;

                    pMenu->ModifyMenu(i, uiFlags, (UINT)hPopupMenu, strTranslated);
                }
                else
                {
                    pMenu->ModifyMenu(i, uiFlags, uiID, strTranslated);
                }
            }
        }

        if (bIsPopup) // is a popup, so add in the submenus
        {
            CMenu *pSubMenu = pMenu->GetSubMenu(i);

            if (pSubMenu != NULL && ::IsMenu(pSubMenu->m_hMenu))
            {
                TranslateMenu(pSubMenu);
            }
        }
    }
}

But this won't work if you use Bent Cockrum's Cool Owner Drawn Menus... In the sample file, you'll find the TranslateBCMenu method that does the same work as TranslateMenu, but for this type of menu.

That's it?

Of course, that's not all... There are questions unsolved....How do you make this language file? How do you update it? Well... I've done a little application called res2txt that scans all program resources and builds/updates the language file... I'm planning an article explaining how this is done.

Final notes

For this code to work, you can't have all your dialog static text controls called "IDC_STATIC", as Microsoft Visual Studio does. They must have different IDs.

There are a lot of people who are uncomfortable having plain text files with their application. You can simply compress or encrypt (or both!) your language files before distributing them (in fact, this is what I do).

As suggested by Mihai Nita, I'd like to point out that this localization approach is only for Latin (Roman) languages. I have not tried translating Asiatic or Arabic languages. You must have noticed that the source code above is not UNICODE friendly at all!

Also, if you have any comments, doubts, criticisms, ... feel free to post them here, I'll check and I'll try to answer all of them.

Thanks

I'd like to thank Brent Corkum for his Cool Owner Drawn Menus.

And also thanks to CodeProject, where learning is possible...

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Isildur
Web Developer
Spain Spain
No Biography provided

Comments and Discussions

 
GeneralOpensource solutions to localize application PinmemberRolf Kristensen21-Mar-11 1:23 
GeneralMy vote of 5 Pinmemberandwan02-Feb-11 6:32 
GeneralYet another way to localize your MFC apps Pinmembermauri.ziggy19-Dec-10 21:55 
GeneralRe: Yet another way to localize your MFC apps PinmemberIsildur10-Jan-11 11:07 
QuestionHow About Using a XML file instead of Text File PinmemberShridhar127-Aug-10 2:21 
AnswerRe: How About Using a XML file instead of Text File PinmemberIsildur27-Aug-10 23:51 
GeneralNice article PinmemberHillaryy26-Nov-07 4:37 
Hi
 
This is the nice article I found regarding localization and I am very much averse to maintain multiple resource files ..Instead , we can maintain text files..
 
Thanks for sharing this information..
 
Best Regards
Hillary
 
Hillary
GeneralThanks for the source PinmemberGeneral Diensten27-Apr-06 0:42 
GeneralIt's not really a bad idea at all PinmemberJoey Bloggs28-Jun-05 23:05 
GeneralRe: It's not really a bad idea at all PinmemberIsildur29-Jun-05 1:41 
GeneralRe: It's not really a bad idea at all PinmemberDavid Patrick29-Jun-05 3:38 
GeneralRe: It's not really a bad idea at all PinmemberIsildur4-Jul-05 12:52 
GeneralBad idea PinmemberMihai Nita27-Jun-05 21:21 
GeneralRe: Bad idea PinmemberIsildur28-Jun-05 1:48 
GeneralRe: Bad idea Pinmembersudhir mangla28-Jun-05 3:34 
GeneralRe: Bad idea PinmemberIsildur28-Jun-05 3:38 
GeneralRe: Bad idea PinmemberNaya200518-Aug-05 17:49 
GeneralRe: Bad idea PinmemberMihai Nita28-Jun-05 9:33 
GeneralRe: Bad idea PinmemberIsildur28-Jun-05 12:25 
GeneralProfessional tools Pinmembersmm2004-Aug-05 23:58 
GeneralRe: Professional tools PinmemberDuncan Mackay27-Feb-10 5:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411022.1 | Last Updated 27 Jun 2005
Article Copyright 2005 by Isildur
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid