Friday, October 28, 2011

Convert a Locale Name to LCID in C++

I've spent most of the afternoon today figuring out how to convert a locale name, such as "en-US", into a locale identifier (LCID). Locale names are also called RFC 1766 language codes, or ISO 639 language names. As it turns out, this is easy to with the function LocaleNameToLCID(), but this function does not exist in Windows XP.

Microsoft provides a redistributable library that adds this functionality for XP, but it's 2MB and yet another module to install. Since my entire installation is 4MB, this isn't an option for me. You can read about that library in the article Mapping Locale Data on Downlevel Systems. In that article, they discuss the function DownlevelLocaleNameToLCID().


For handy reference, Microsoft provides a list of Language Identifier Constants and Strings on MSDN.

If you then need to convert the LCID to a CODEPAGEID, you can use this call to retrieve the code page for a particular LCID:

GetLocaleInfo(lcid, LOCALE_IDEFAULTCODEPAGE, ...)

Additional information on this topic can be found on The Old New Thing blog, including a discussion of using the MLang library.

// This is a version of LocaleNameToLCID() that works on Windows XP. 
// On Windows Vista or later, it automatically delegates to the 
// built-in version of LocaleNameToLCID(). 
// This function is not case-sensitive. 

// Tested with Unicode builds only, not tested under ANSI builds, but should be pretty close. 
// Example value for szLocale is "en-US" 
LCID CompatibleLocaleNameToLCID(LPCSTR szLocale)
{
CString strValue(szLocale);
CStringW wstrValue(strValue);

static  bool  bDone;
LCID (WINAPI * pfnLocaleNameToLCID)(LPCWSTR, DWORD);
if  (!bDone)
{
bDone = true ;
*(FARPROC*)&pfnLocaleNameToLCID = ::GetProcAddress(GetModuleHandleA("Kernel32" ), "LocaleNameToLCID" );
}

if  (pfnLocaleNameToLCID)
return  LocaleNameToLCID(wstrValue, 0);
 

CComPtr<IMultiLanguage> iLang;
HRESULT hr = iLang.CoCreateInstance(CLSID_CMultiLanguage);
LCID lcid;
if  (SUCCEEDED(hr) && SUCCEEDED(iLang->GetLcidFromRfc1766(&lcid, (BSTR)wstrValue.GetString()))) {
return  lcid;
}
 

return LOCALE_USER_DEFAULT;
}
 


4 comments:

  1. Hello,

    thanks for posting this Code, it is great but it has a bug in it, the line

    static bool bDone;

    results in an undefined variable, the correct version of the line is:

    static bool bDone = false;

    Regards
    Jakob

    ReplyDelete
  2. Hello,

    thanks for posting this Code, it is great but it has a bug in it, the line

    static bool bDone;

    results in an undefined variable, the correct version of the line is:

    static bool bDone = false;

    Regards
    Jakob

    ReplyDelete
  3. Hi Jakob,

    Thanks for taking the time to comment, but the line is correct as I wrote it. Static variables are always initialized to zero by the compiler. This goes all the way back to the K&R C standard - see K&R1 (Appendix A, §6.8).

    More details:
    http://stackoverflow.com/questions/3373108/why-are-static-variables-auto-initialized-to-zero

    ReplyDelete
  4. CString lang;
    CoInitialize(NULL);
    HRESULT hr = CoCreateInstance(CLSID_CMultiLanguage, 0, CLSCTX_INPROC_SERVER, IID_IMultiLanguage, (void**)&mlang);
    if (SUCCEEDED(hr))
    {
    BSTR st;// = str.AllocSysString();
    mlang->GetRfc1766FromLcid(l/*lcid*/,&st );
    lang = st;
    SysFreeString(st);

    }
    CoUninitialize();

    ReplyDelete