Multiple-Language Resources

Glossary

Most existing Windows-based programs—even localized ones—contain resources for only one language. Win32 supports the ability to store multiple-language versions of a given resource in a single executable or DLL. This makes it possible to draw a program's user interface at startup time in whatever language corresponds to the user's default locale if resources for that language are available. On Windows NT, you can also access multilingual resources to change the language of the user interface at any time during program execution.

Win32 searches for resources based on type, name, and language, in that order, as illustrated in the following diagram:

You can declare the language of a particular resource using the LANGUAGE statement. The two parameters to this statement are a language identifier and a sublanguage identifier; you must either select these parameters from the list of legal values contained in WINNT.H or create customized values within a certain range.

#include <windows.h>
#include "PROJECT.H"

#define CLASS_STRING "STE"

STRINGTABLE PRELOAD DISCARDABLE
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
BEGIN
IDS_CLASS, CLASS_STRING
IDS_TITLE, "Simple Text Editor"
END

#include "MESSAGES.RC" // created by MC.EXE
#include "DIALOGS.DLG" // from DLGEDIT.EXE
#include "COMMON.RC" // other resource

The range for customized primary language IDs is 0x200 through 0x3FF, and the range for customized sublanguage IDs is 0x20 through 0x3F. (These ranges correspond to setting the high bit on each predefined ID.) You can use custom language IDs to tag resources, but the national language support functions (described in Chapter 5) will not accept them as parameters—you cannot add custom locale information to the system registry. Appendix K lists the languages and sublanguages that Windows NT and Windows 95 have predefined.

When concatenated, the primary and secondary language identifiers become a language ID. WINNT.H defines several macros for working with primary and secondary language IDs. Examples of how to use these macros are shown in Figure 4-9.

WORD lang_id = MAKELANGID( primary, sublang )
BYTE primary = PRIMARYLANGID( lang_id )
BYTE sublang = SUBLANGID( lang_id )

Figure 4-9. Predefined macros for handling language IDs.

Another basic element of Win32's national language support is the locale ID, which is composed of a sort ID and a language ID. You can extract a language ID from a locale ID using the macro LANGIDFROMLCID. The definitions for this and other macros useful in manipulating locale IDs are shown in Figure 4-10 below. Chapters 5 and 6 discuss locale IDs in more detail.

#define LANGIDFROMLCID(lcid) ((WORD)(lcid))
#define MAKELCID(lgid, sort) ((ULONG)(((USHORT)(lgid)) | \
(((ULONG)((USHORT)(sort))) << 16)))
#define SORTIDFROMLCID(lcid) ((USHORT)(((lcid) >> 16) & 0xF))

Figure 4-10. Predefined macros for handling locale IDs.

A LANGUAGE statement placed before the BEGIN keyword of an accelerator, dialog, menu, RCDATA, or string table definition applies only to that resource element. A LANGUAGE statement anywhere else defines the language for all the resources that follow it, up to the next LANGUAGE statement. An .RC file might contain one LANGUAGE statement that defines the entire file (invoking the resource compiler with the /L flag has the same effect), several LANGUAGE statements, or none at all. It's possible for different LANGUAGE statements to encompass only a subset of all the resources. The API calls that search for resources based on language—FindResource and FindResourceEx—will always return something, even if they cannot find a resource that is tagged with the requested language.

HRSRC hrsrc = FindResourceEx(hMod, RT_ICON, id, langID );
HGLOBAL hglb = LoadResource(hMod, hrsrc);
LPVOID lpsz = LockResource(hglb);

On Windows NT, FindResource searches for resources tagged with the language ID of the calling thread. On Windows 95, it searches for resources tagged with the default system language ID. On Windows NT, you can search for a resource in a specific language by calling FindResourceEx, which takes a language ID as a parameter. Both FindResource and FindResourceEx first attempt to find a resource tagged with a language ID, as described above. If they don't find anything, they then search for a resource tagged with the same primary language as that of the specified language ID. (If several resources with the same primary language but different sublanguages exist, the functions will return whatever they encounter first.) If, for example, the program requests resources in Standard German that aren't available, the program can retrieve Austrian German or Swiss German resources and still provide a user interface that the user can understand.

If the FindResource and FindResourceEx functions do not find any resources that match the language ID's primary language, they search for resources tagged as "language-neutral." This language ID is useful for resource elements such as icons or cursors that are identical for all languages. If a bitmap or an icon will differ for some languages, you can define one language-neutral bitmap as the default and specify language IDs for as many other customized bitmaps as required. For example, bidirectional applications might require bitmaps with right-to-left directionality. Because the FindResource and FindResourceEx functions always search for specific language IDs first, they will always find a bitmap tagged with that language ID before they find one tagged as language-neutral. The search algorithm they follow is summarized in the following list:

  1. Primary language/sublanguage
  2. Primary language
  3. Language-neutral
  4. English (skipped if primary language is English)
  5. Any

Another approach to loading resources by language doesn't use FindResourceEx. You set the thread locale to match that of the desired language, and then you load the resource directly. For example, to load a language-specific menu from the program's .EXE file, you can use the following sequence of calls (shown here for Japanese):

// initialization code
static DWORD dwJapanese =
MAKELCID(MAKELANGID(LANG_JAPANESE, SUBLANG_DEFAULT));
...

// load Japanese resource
SetThreadLocale(dwJapanese, SORT_DEFAULT);

hMenu = LoadMenu(hCurrentInstance, TEXT("MainMenu"));

SetMenu(hMainWindow, hMenu);

If you have more than one menu resource with the same identifier ("MainMenu," in this case), LoadMenu will automatically load the one whose LANGUAGE identifier matches the current locale setting. Note that if you are running on Windows 95, SetThreadLocale will fail—on Windows 95, you can use LoadMenu to load only resources tagged with the default system locale. If you want to change only the language used for menu resources but not for other resources, you should call GetThreadLocale and save the current locale in a variable of type DWORD before calling SetThreadLocale. You can then restore the original locale later—for example, after calling LoadMenu in the example above.

The same method works with all of the Load<resource type> functions, such as LoadAccelerators, LoadBitmap, and LoadCursor.