Click here to Skip to main content
15,881,173 members
Articles / Desktop Programming / MFC

Yet another way to localize your MFC apps

Rate me:
Please Sign up or sign in to vote.
3.50/5 (12 votes)
27 Jun 2005CPOL3 min read 53.6K   463   15   21
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:

C++
CString str = GetTranslatedString(IDS_STRING);

Let's see how it works....

First we load the string from the resources...

C++
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...

C++
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.

C++
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):

C++
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...

C++
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)


Written By
Web Developer
Spain Spain
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralOpensource solutions to localize application Pin
Rolf Kristensen21-Mar-11 0:23
Rolf Kristensen21-Mar-11 0:23 
GeneralMy vote of 5 Pin
andwan02-Feb-11 5:32
andwan02-Feb-11 5:32 
GeneralYet another way to localize your MFC apps Pin
whiteduke19-Dec-10 20:55
whiteduke19-Dec-10 20:55 
GeneralRe: Yet another way to localize your MFC apps Pin
Isildur10-Jan-11 10:07
Isildur10-Jan-11 10:07 
QuestionHow About Using a XML file instead of Text File Pin
Shridhar127-Aug-10 1:21
Shridhar127-Aug-10 1:21 
AnswerRe: How About Using a XML file instead of Text File Pin
Isildur27-Aug-10 22:51
Isildur27-Aug-10 22:51 
GeneralNice article Pin
Hillaryy26-Nov-07 3:37
Hillaryy26-Nov-07 3:37 
GeneralThanks for the source Pin
General Diensten26-Apr-06 23:42
General Diensten26-Apr-06 23:42 
GeneralIt's not really a bad idea at all Pin
Joey Bloggs28-Jun-05 22:05
Joey Bloggs28-Jun-05 22:05 
GeneralRe: It's not really a bad idea at all Pin
Isildur29-Jun-05 0:41
Isildur29-Jun-05 0:41 
GeneralRe: It's not really a bad idea at all Pin
David Patrick29-Jun-05 2:38
David Patrick29-Jun-05 2:38 
GeneralRe: It's not really a bad idea at all Pin
Isildur4-Jul-05 11:52
Isildur4-Jul-05 11:52 
GeneralBad idea Pin
Mihai Nita27-Jun-05 20:21
Mihai Nita27-Jun-05 20:21 
I am sorry to disappoint you, but the ones that created the whole resource idea did it for a good reason.

First, there is more to translation than strings. For DBCS languages you have to change fonts and font sizes. You have to change images (icons, bitmaps, etc.)
You have to change alignment. For RTL languages the dialogs and the full application has to be flipped.

Also, this approach does not scale well. There is no way you can design a dialog big enough to fit any language you throw at it, or, if you do, it will look ugly like hell. Have you considered text expansion? How much? Do you think dialogs are big enough for 30 languages?

Another bad idea is using the English string as ID (the fact that popular libraries like gettext use it does not make it good). The same English string is translated differently if it is a button or if it is a label. Also, depends on the gender, number, case. Looks like your language is Spanish, so how would you translate “New”, as “Nuevo” or “Nueva”?
Or how do you translate “Scan”, like “scan a page” or “scan the disk”?

You mention problems with updating files and so on. This is at the “amateur” level. Same as a programmer using notepad and a .bat file to “build” the application. Programmers now have IDEs, build systems, intellisense, version control, you name it. Translators use TM tools, terminology management tools, WYSIWYG resource editors.

Guys, please, before creating so called “solutions”, read a bit what others have done, think why they did it that way. I see at least an article per months explaining a new way to do this. Like nobody translated anything before.
You really think MS translated full OSes and Office suites with .rc files because they are a bunch of idiots, who have no clue how to create an .ini file with strings?
Search and read some articles on internationalization and localization. Then come up with something better, if you can. If not, do your own stuff, but don’t advise others to follow you.

GeneralRe: Bad idea Pin
Isildur28-Jun-05 0:48
Isildur28-Jun-05 0:48 
GeneralRe: Bad idea Pin
Sudhir Mangla28-Jun-05 2:34
professionalSudhir Mangla28-Jun-05 2:34 
GeneralRe: Bad idea Pin
Isildur28-Jun-05 2:38
Isildur28-Jun-05 2:38 
GeneralRe: Bad idea Pin
Naya200518-Aug-05 16:49
Naya200518-Aug-05 16:49 
GeneralRe: Bad idea Pin
Mihai Nita28-Jun-05 8:33
Mihai Nita28-Jun-05 8:33 
GeneralRe: Bad idea Pin
Isildur28-Jun-05 11:25
Isildur28-Jun-05 11:25 
GeneralProfessional tools Pin
smm2004-Aug-05 22:58
smm2004-Aug-05 22:58 
GeneralRe: Professional tools Pin
Duncan Mackay27-Feb-10 4:07
Duncan Mackay27-Feb-10 4:07 

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

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