Neutral? I do not think that word means what you think it means!

by Michael S. Kaplan, published on 2007/07/15 22:36 -04:00, original URI: http://blogs.msdn.com/b/michkap/archive/2007/07/15/3888681.aspx


It can be hard to act in a neutral manner.

As an example, you may recall how I talked about neutral locales and how they are automatically converted into full locales by NLS functions in Windows in this post. Using the same logic, if you pass LOCALE_NEUTRAL (which is to say, 0), it will handily be converted to 0x0400, which is to say, LOCALE_USER_DEFAULT.

In other words, no chance of 100% neutrality in NLS.

But it does not end there.

In the world of the resources, you can tag resources with a 0 to indicate that they are LANG_NEUTRAL or LOCALE_NEUTRAL. In fact, someone was just asking me the other day about how these neutral resources are used:

I am trying to build an application which has a .rc which will contain both English[US] dialogs/menus etc and also a similar set of copies of neutral resources. In my English[US] OS and locale, I expect loading of English resources, whereas, i find neutral resources being loaded.

I found from the link below that, when user locale and thread locale are same, the system resource loader will use language ID 0

http://www.microsoft.com/globaldev/handson/dev/muiapp.mspx

Though such a requirement of using English as well as Neutral resources is weird, I am looking for some solutions/suggestions/learnings to the problem without removal of either resource (not even to dlls). Also I am using Visual Studio 2003 - VC++

The text of that topic is indeed quite confusing. Here is the part that may make your head explode if you read it too many times:

To access the resources at run time, the resource DLL is loaded through the LoadLibrary API. EnumResourceLanguages can then be used to find the list of available languages for a given control/resource, and FindResourceEx to determine the location of the resource with the specified type, name, and language. Displaying a given language is then just a matter of selecting the right resources within the DLL. Language switching can be implemented by re-loading the newly selected resources and refreshing the client area.

It’s important to note that the Windows resource loader always defaults to the current user locale (the thread locale gets inherited from the currently logged in user's user locale - see Figure 1). GetThreadLocale allows querying of thread locale. In this method, predefined resource loading APIs (LoadIcon, LoadString, LoadCursor…) always return resources associated with this locale. For example, in the resource sample above, the French resources cannot be loaded by using LoadString if the system locale is set to English (0x0409). Actually this is not entirely true: a thread locale, once inherited – upon thread creation – is independent from the user locale, which means in this case the thread locale can be changed to French (0x040C) by using SetThreadLocale and calling LoadString to get the French string back.

// if our thread locale is English to start with…
g_hInst = LoadLibrary(_TEXT(“intl_res.dll”));
LoadString(g_hInst, IDS_ENUMSTRTEST,g_szTemp, MAX_STR);
// g_szTemp would then point to the English resources
// changing our thread locale to French. X
// Always make sure that French is in fact one of the
// valid languages returned by EnumResourceLanguages()
// Save a copy of the current thread-locale to set back later
Lcid = GetThreadLocale();
SetThreadLocale(MAKELCID(0x040c, SORT_DEFAULT));
LoadString(g_hInst, IDS_ENUMSTRTEST, g_szTemp, MAX_STR);
// g_szTemp would then point to the French resources


The catch here is that if the thread locale is the same as the currently selected user locale, system’s resource loader will by default use the language ID 0 (neutral). If the desired resource is defined as a neutral language, then this value will be returned. Otherwise, all of the language resources will be enumerated (in language ID order) and the first matching resource ID – regardless of its language – will be returned.

To clarify, consider an example (using our English – US and French – France resources) in which the user locale is set to Japanese, then: our original thread locale is Japanese (0x0411). The first call to LoadString will return English resources since 0x0411 version if our string ID cannot be found and English (0x0409) is enumerated before French (0x40C). Now, if the user locale to start with is French: our original thread locale is French (0x040C). Since the user locale and thread locale match, the system resource loader will use language ID 0 and will start enumerating resources and will once again return the English version!

The best way around this is to give up changing the thread locale in the first place. Resources can be manually loaded from within multilingual resource files by making calls to FindResourceEx . Another workaround is to define the resources within an owner-defined language tag. In the RC example above, the English section was defined as:

LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
    
This prevents the thread locale from being switched back to English US. However, defining the resources as:

LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL
    
will guarantee the success of the SetThreadLocale call since there is no matching user locale for this language ID and the predefined resource loader APIs can still be used.

Again, your head may explode trying to parse what this text is saying.

Thankfully, however, the text is slightly incorrect in its assumption, or at least misleading in its language.

You see, the actual behavior boils down to the difference between FindResource and FindResourceEx.

Ignoring my Ex claims here, the only difference between the two is that the former calls the latter with a wLanguage value of 0.

But the meaning of a FindResource call (or the essentially identical FindResourceEx call with a wLanguage of 0) is, when the docs refer to the thread language, is not in any way at all guaranteed to be either the return of the dreaded GetThreadLocale or what that quoted resource page calls "language ID 0 (neutral)". That 0 to the world of MUI and the UI language means the default user UI language!

The behavioral interaction between thread locale and user locale can be made much clearer than above, with two simple statements:

And hopefully no head explosions. :-)

This behavior almost kind of makes sense in terms of keeping the balance between the old and new behaviors, and really only breaks the case where you wanted to use SetThreadLocale to change the UI language to be one that matches the user locale....

And how else could MUI have been bolted on to the existing FindResource and FindResourceEx with minimal code change way back in Windows 2000?

(Please pretend that is rhetorical; given the benefits of hindsight I can actually think of several designs that would probably have been better here; but this is the model we have!)

So where do the resources that are actually marked with LANG_NEUTRAL fit in?

Well, if the resource loader has its way, then they will show up somewhere on the list prior to simply giving up. But they cannot ever be directly loaded and will never be the first choice for what the resource loader tries to use, even when you ask for it (since the resource loader gives 0 this special meaning)....

So once again, we find that neutral has a different meaning that is not actually neutral!

 

This post brought to you by (U+26b2, a.k.a. NEUTER)


no comments

referenced by

2011/03/16 Reporting one casualty in the operation; luckily it was the stupidest member of the unit

2008/11/12 You can either be intuitive and completely inconsistent, or consistent and completely unintuitive!

go to newer or older post, or back to index or month or day