Click here to Skip to main content
Email Password   helpLost your password?

Sample Image

Introduction

Some countries and languages standardize on number and date formats that don't translate smoothly between cultures. It is important for C++/Windows developers to have strategies and techniques to handle this challenge and other challenges presented by diverging sets of localization API functions. The CtrSynch sample app illustrates how to keep the Windows API locale in synch with the C-runtime (CRT) locale so that functions like LoadString are in step with conversion routines like _tprintf.

Background

Have you ever encountered a situation where you need to read a double/floating point value from text formatted in another locale? For example, the number 1023.54 displays in English-US as 1,023.54 and in German-Germany as 1.023,54. This problem comes up often when sharing text-based information generated in Europe (Germany, France, Spain) and consumed in the US. The reverse is also true.

Say, a German company exports a Tab Separated text (TSV) file from a spreadsheet on a workstation running in the German-Germany locale. The file is emailed to an American firm, where values like 1.023,54 import as a decimal number between 1 and 2 rather than 1023. This is a very common scenario.

The first step in properly transferring double values (or dates formatted by locale defaults) is to include a locale identifier in the data. This can be accomplished using a file header, an LCID field in each data row, embedded logic in the file name, and so on. In my simple example, I just wrote a method to pack an LCID onto the end of the string containing the number. Conversely, I wrote a routine to parse it back out before reading the number. The final issue is the actual conversion of the text to doubles. My first instinct was to run SetThreadLocale, run the _tcstod function on the text, then return to the previous thread locale.

It doesn't work! I spent a lot of time trying to figure this out, and I hope to save you the effort!

It turns out, the C-runtime routine _tcstod (strtod in ANSI, wcstod in UNICODE) gets its locale context from the C-runtime function setlocale. SetThreadLocale does not talk to setlocale. Therefore, calling SetThreadLocale without calling setlocale puts you in a situation where LoadString will load from the current thread locale, but _tprintf will format in the locale the application started under. So, should you just call setlocale at the same time you call SetThreadLocale?

Well, I wish it was that simple! Here is what must happen in your code to keep the thread locale in step with the CRT's locale:

SetThreadLocale(1033);
setlocale(LC_ALL, "English_USA.1252");

You probably see the problem -- the two functions consume very different input parameters. After struggling with this, I found the solution is actually quite simple. It just required digging in the Windows API a bit. setlocale has two parameters, and the second is a three token string. The first is a language, the second is a country or region, and the third is a code page identifier. It turns out, these three values are readily acquired through the Windows API GetLocaleInfo. Therefore, given an LCID value, one may call GetLocaleInfo to find its language name (in English), it's region (in English), and its code page. A snippet:

LPCTSTR CCrtLocaleSwitch::loadLocaleId(LCID lcid, _bstr_t& bstrRetBuf)
{
    TCHAR arcBuf[128];
    memset(arcBuf, 0, sizeof(arcBuf));

    //We should check the return code, but skipped for brevity...

    //Loading the English name fo the locale

    GetLocaleInfo( lcid, LOCALE_SENGLANGUAGE, arcBuf, 127);
    bstrRetBuf = arcBuf;
    memset(arcBuf, 0, sizeof(arcBuf));
    //Loading the English name for the country/region

    GetLocaleInfo( lcid, LOCALE_SENGCOUNTRY, arcBuf, 127);
    if( *arcBuf )
    {
        bstrRetBuf += TEXT("_");
        bstrRetBuf += arcBuf;
    }
    //Loading the code page

    memset(arcBuf, 0, sizeof(arcBuf));
    if( (GetLocaleInfo( lcid, LOCALE_IDEFAULTANSICODEPAGE, arcBuf, 127)
        || GetLocaleInfo( lcid, LOCALE_IDEFAULTCODEPAGE, arcBuf, 127))
        &&  *arcBuf )
    {
        bstrRetBuf += TEXT(".");
        bstrRetBuf += arcBuf;
    }
    return bstrRetBuf;
}

The function above creates the string that is acceptable for setlocale. This allows you to keep the C-runtime's locale in synch with the Windows API locale state.

One final note regarding the sample application -- the sample classes are designed to restore state when they go out of scope. Regardless of how you exit a function, whether it's a normal return or an exception event, the previous locale will be restored. For brevity, I did not always check return values when calling Windows API or CRT functions, so please bear with my laziness!

Using the code

The sample application was written in Visual C++ 7.1. The two main reusable classes, CTempLocale and CSmartBuf, should be compatible with other compilers. The simplest way to use these classes is to put them in a folder in your header file search path, then add the following to your stdafx.h file:

#include <comdef.h>

#include <TempLocale.h>

The application itself is rather useless, but it illustrates keeping the CRT in synch with the Windows thread locale. When you select a new culture on the left, the window caption changes to "Hello World" in the selected language. Since MFC's CString internally calls LoadString, this functionality gets its locale from the Windows SetThreadLocale function. At the time the caption changes, the number in the lower left is reformatted per the selected locale, and that formatting gets its locale from the last call to the CRT setlocale function. On the right, you can select a target culture to translate the displayed number into, and it is displayed in the lower right. This lower-right number illustrates one possible way to attach LCID info to text containing a decimal number.

Conclusion

The Windows API provides routines to load resources in the current thread's locale. The CRT provides routines to convert numbers to text and back again. The two sets of APIs don't share a locale status; therefore, C++ developers must build a way to keep them in synch. This article demonstrated a way to handle this task.

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralLittle flaw in the code, when using chinese language
aron.sinoai
4:28 11 Feb '10  
Thanks for the article, it was quite helpful for me.
I've noticed a small issue, when I was testing it with chinese language.
Instead of asking for LOCALE_SENGCOUNTRY you should ask for LOCALE_SABBREVCTRYNAME, otherwise it won't work.
General[Message Removed]
immetoz
3:06 4 Oct '08  
Spam message removed
GeneralDialogs
MilanTomic78
23:58 12 Oct '05  
Does SetThreadLocale() or setlocale() affect text in dialogs? I have troubles displaying my app (GUI) in another language. I have translated my app into 2 languages: English and Slovenian (Eastern Europe). If user set in Control Panel -> Regional Options locale to be Slovenian, then my app works fine, even without SetThreadLocale() and setlocale() (I need only strings in GUI). If user doesn't set locale to be Slovenian, then it doesn't show properly special characters (accents).

What should I do?

This is what I tried:

SetThreadLocale(1250);
setlocale( LC_ALL, "Slovenian" );
GeneralSetThreadLocale question
Gary Wheeler
11:50 3 Mar '05  
Sorry if this is a bit off-topic, but I have a question about SetThreadLocale.

Here's my situation. I'm running English XP Pro with the multilingual pack installed for Japanese. I can log in as an English user (the normal case), and using SetThreadLocale, cause my app to 'speak' Japanese. All well and good.

I then use the Control Panel Regional settings applet to change the UI language to Japanese, log out and then back in. At this point, the Windows UI is in Japanese. If I run my application and use SetThreadLocale to set English as the language, it still displays in Japanese. The return value from SetThreadLocale was TRUE, and GetLastError() returns 0, indicating that the call was successful.

Any ideas?


Software Zen: delete this;
GeneralRe: SetThreadLocale question
Chris Grimes
18:00 3 Mar '05  
Sorry to answer a question with a bunch of case statements, but the answer depends on what you mean by 'displays it in Japanese'.   If you're referring to resources, such as dialogs and strings from string tables, these will load based on the current thread locale at the time of loading.

If you are referring to date or number formatting, this depends on the CRT locale.

If you have, for example, a window or toolbar loaded and visible, merely changing the thread locale does not change what's on screen; what you do afterward can change it, though.   If you respond to the change of locale by totally reloading the window and toolbar, then in theory, the resources should display compatibly with the current locale.   Note that this means you need to effectively destroy and recreate the window and toolbar using different resource templates.

Hope this helps, even though it's not a direct answer!


Chris Grimes
GeneralRe: SetThreadLocale question
Gary Wheeler
3:00 4 Mar '05  
Thanks for the answer, Chris; I think I understand what SetThreadLocale is supposed to do. Here's a little better explanation of what I'm seeing.

My application includes both English and Japanese resources. The app displays English resources properly if I don't use SetThreadLocale. When logged in with the UI language set to English, I can use SetThreadLocale to set the locale to Japanese at startup, and my app will use the Japanese resources. When I log in with the UI language set to Japanese, and I use SetThreadLocale to set the locale to English, I still get the Japanese resources. This is on Windows XP Pro, English, with the multilingual user pack installed (Japanese language).

I'm wondering if this is caused by the multilingual pack (which isn't the same thing as using a localized version of Windows).

This app is getting installed on PC's running Windows XP localized to Japanese. Our manufacturing people want to run tests with the app in English (since none of them speak Japanese Smile ).

If you have any suggestions, I'd appreciate it.


Software Zen: delete this;
GeneralRe: SetThreadLocale question
Chris Grimes
12:33 5 Mar '05  
Without running an interactive debug session and seeing the code, it's only possible to speculate.   I would suggest that you   write a message including the thread LCID to a log file or debug monitor right before your windows are created.   Do this from the thread creating the application windows.   The thread locale will likely be Japanese.   The question is why the thread starts up in Japanese.   Maybe SetThreadLocale fails, or maybe the SetThreadLocale call is nested in an if or case block such that it does not run.   Put messages on both sides of the call to verify that it runs and succeeds.

Further, could there be a piece of code that resets the thread locale?   A destructor of a some class could do this under the radar.

Maybe the window creation process itself resets the thread locale to the Windows UI locale, which could easily be detected from within your window procedure or a message handler.   This behavior could vary between MLP and localized Windows.

One other thing, what about the language bar?   I'm exposing my ignorance here, but I'm not sure what effect it has on a running application and how it interacts with regional settings.

Regards,
Chris
GeneralRe: SetThreadLocale question
Atul Kumar Dwivedi
3:37 12 Nov '05  
Please provide little more information so that I can help you little better. What resources you are using in your application. Does the resources are in japanese or in english, or both.



Atul Kumar Dwivedi
India
Generalsetlocale in a thread
BadJerry
3:02 22 Feb '05  
Hi Chris,

Very interesting article and I wished I had read found something that clear years ago.It would have saved me months of weird (and failed) experiments. I have a question though... I have a program that deals with different locales at run-time and it is multithreading!

So here is my question: is there a setlocale function that would be spread specific? Is there a way to do this without having to synchronise all my threads whenever I want CString::Format(_T("%g"),... for instance in English or French?

Any help/idea/pointer welcome!

Best regards,
BadJerry
GeneralRe: setlocale in a thread
Martin Richter
1:18 24 Feb '05  
AFAIK setlocale is thread local, so each thread can call the CRT setlocale function without changing the locale of another thread.
This is the case if the multithreading CRT is in use.
GeneralRe: setlocale in a thread
Left2
7:04 15 Mar '05  
You are wrong.
At least as I see from MSVC 7.1 CRT source code, setlocale change the locale for all threads.
GeneralRe: setlocale in a thread
Jeffrey Walton
7:09 7 Dec '07  
Hi Left2,

Left2 wrote:
You are wrong.
You might want to look at Configure Thread Locale[^].

Jeff
GeneralSetThreadLocale ...
CP Visitor
7:28 17 Feb '05  
... sounds like 'Set Thread Local Storage' but is obviously something entirely different Sigh


Last Updated 16 Feb 2005 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010