The number of possible ways to implement an MUI solution in applications is endless, but each possibility is a variation of one of three basic methods:
This section discusses each one of these techniques, along with their pros and their cons. You can offer an MUI solution for your application regardless of the version of Windows. In fact, these techniques allow you to offer your MUI solution even on Windows 95. The following comparison assumes that your application is only targeting three pilot languages: English, French, and Japanese.
This traditional approach involves compiling one binary, containing both source code and resources, for each target language. Figure 6-2 illustrates the file distribution of such an approach.
Figure 6-2 - File distribution for the language-dependent binary approach.
In this approach, once the English binary has been created, its code is then modified to include other language-specific requirements and features. The customized binary is then localized into each target language.
The advantage of using a language-dependent binary lies in its familiarity. This traditional approach is used in older versions of Windows, and is still widely used throughout the computer industry.
One of the disadvantages is that a separate source tree, or at least a separate configuration of the build process, is required for each language. This goes against one of the fundamental rules of globalization: to create a single, worldwide binary.
Another problem is that changes to resources will require a full binary compilation. Imagine if you have a typo in your Japanese translation. Your functional binary would have to be recompiled, which would expose your application to the risk of potential defects.
Moreover, with this method valuable disk space is wasted when there are multiple copies of the core binary. For instance, imagine if a user is interested in having all language versions of your application. In this case, most of the core functions of your binaries, which are common to all languages, would be copied multiple times, needlessly consuming the user's disk space.
Furthermore, there is no real possibility of switching the UI language unless the existing process is ended and a new instance of your application is created in the desired language. The disadvantages of having language-dependent binaries are such that this approach is now considered obsolete.
Another method is to use one multilingual resource file.
The biggest disadvantage of the previous approach was the fact that multiple functional binaries were needed. The main idea behind this second approach is to separate out the resources from the source code, thus creating a resource-only DLL that contains all the localized resources for each targeted language. The functional part of the binary is language-neutral and thus common to all languages. Figure 6-3 illustrates the file distribution of such an approach.
Figure 6-3 - File distribution for the multilingual resource file approach.
You can easily create multilingual resource files by using Microsoft Visual Studio's resource editor, in which multiple copies of the same resource identifier are created with different language IDs. The following example shows the string resource IDS_ENUMSTRTEST in both English and French within the same resource script (.rc) file:
// French (France) resources #ifdef _WIN32 LANGUAGE LANG_FRENCH, SUBLANG_FRENCH #pragma code_page(1252) #endif //_WIN32 // String Table STRINGTABLE DISCARDABLE BEGIN IDS_ENUMSTRTEST "Cette phrase est en français..." END #endif // French (France) resources // English (United States) resources #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif _//WIN32 // String Table STRINGTABLE DISCARDABLE BEGIN IDS_ENUMSTRTEST "This is an English string..." END #endif // English (United States) resources
To access the resources at run time, the resource DLL is loaded through the LoadLibraryEx application programming interface (API). You can then use EnumResourceLanguages to find the list of available languages for a given control or 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. You can implement language switching by reloading the newly selected resources and refreshing the client area.
The Windows resource loader always defaults to the current thread locale for applications, which is inherited from the currently selected user locale. GetThreadLocale allows you to query the thread locale. In the multilingual resource file method, predefined resource-loading APIs (such as LoadIcon, LoadString, or LoadCursor) always return resources associated with the thread locale. For example, in the previous resource sample, the French resources cannot be loaded by using LoadString if the user locale is set to English (0x0409). However, once a thread locale is inherited-during thread creation-it is independent of the user locale. This means that the thread locale could, in this case, be changed to French (0x040C) by using SetThreadLocale and by calling LoadString to retrieve the French string. The following code sample demonstrates this principle:
// If the thread locale is English to start with g_hInst = LoadLibraryEx(TEXT("intl_res.dll"), NULL, LOAD_LIBRARY_AS_DATAFILE); LoadString(g_hInst, IDS_ENUMSTRTEST, g_szTemp, MAX_STR); // g_szTemp would then point to the English resources, // changing the thread locale to French. // 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 reset 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, the system's resource loader will, by default, use the language ID 0 (neutral). If the desired resource is defined as a neutral language, then the data or resources 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 English (United States) and French (France) resources, in which the user locale is set to Japanese. Thus the original thread locale is Japanese (0x0411). The first call to LoadString will return English resources since the 0x0411 version of the string ID cannot be found, and since English (0x0409) is enumerated before French (0x040C). Now, if the user locale to start with is French, the original thread locale will be French (0x040C). Since the user locale and thread locale match, the system resource loader will use language ID 0, will start enumerating resources, and will once again return the English version!
The best way around this problem is to avoid 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 example given earlier for .rc, the English resource section was defined as:
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
Defining the resources in this manner prevents the thread locale from being switched back to English (United States). 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 since the predefined resource loader APIs can still be used.
One of the advantages of this method is that you can switch UI languages on the fly by simply reloading a new set of resources from the language resource file and refreshing the UI area. Also, you can update resources without having to compile each of them. As a result, fixing translation defects or typos does not require that you compile the binary. The final advantage is that this method follows the underlying rule of globalization: that of creating a single, worldwide binary.
Despite the advantages, with this method it is difficult to update the resource binary to accommodate new languages. Suppose besides English, French, and Japanese, you also want to support German. A whole new resource binary would need to be provided to your existing users who might not necessarily need German.
Another disadvantage is that it is hard to install a subset of UI languages. For example, suppose 10 languages are available in the same file, but the user is only interested in a subset of four of these languages. The user is forced to waste memory and the disk space that the other six languages require because of the "one-size-fits-all" property of a multilingual resource. Another problem with a multilingual resource file is that changes for one language resource file may affect all languages and users.
Also, there is no straightforward way to implement direct resource-loading interfaces (such as LoadMenu, LoadString, and so on). On the whole, the big drawback of this approach resides in its lack of flexibility when it comes to adding, removing, or modifying the existing set of languages. To overcome this lack of flexibility, use satellite DLLs (Method 3), as discussed next.
This method is an expansion of the previous technique. Instead of a single DLL that contains all languages, a separate DLL is created for each language, compensating for the lack of flexibility in the previous method. This concept is somewhat like the Indian television network mentioned earlier that uses a single feed for a movie, but allows for subtitles in various languages. The satellite DLL approach is the one that Microsoft Office 2000, Microsoft Office XP, Microsoft Internet Explorer 5 and 6, Windows 2000, Windows XP, and the .NET Framework use. The technique is named differently depending on each product's implementation, but "satellite DLLs" is a commonly used, general name. Figure 6-4 illustrates the file distribution of such an approach.
Figure 6-4 - File distribution for the satellite DLL approach.
Just like in the multilingual resource file method, this technique requires that the resource DLL be loaded through the LoadLibraryEx API. This time, however, the appropriate language DLL must be selected. Typically it is safe to assume that the Windows UI language is the same as the user's preferred language and that the application can be started by loading this language resource. When the user selects another language, the current language can be freed and a new one loaded. Of course, this approach requires re-creating windows and initializing dialog boxes with the newly loaded resources.
Different variations of this technique exist. In the Windows XP MUI Pack, for example, the English resource files are embedded in the core binary, and satellite DLLs are offered for other languages.
Another advantage of satellite DLLs is that new language DLLs of an application can be released and shipped as they are made available, without having to ship the whole product. Also unique to this method is the ability to add a language DLL without affecting other languages.
A further benefit is that there is no need to worry about user locales, system locales, and thread locales. Finally, the satellite DLL technique can be used for any 32-bit version of Windows.
A disadvantage to this method is that you need to keep all language satellite DLLs synchronized. Doing this requires that setup and update procedures be adjusted in the development cycle to ensure that any update related to a change in resources will refresh resources for all installed languages.
Taking on the development model that employs satellite DLLs might also add some complexity to the developer's work. The following are some of the issues developers must address in this case: