1 Developing Middle East Applications Using Microsoft Visual C++ Introduction [This Is an Initial Version Updates Could Be Available in later Stages] In the following paper we will highlight some of the most important issues concerning developing a Localized or Globalized version of the applications developed using the different tools supplied with Microsoft Visual Studio Package. This paper will be stressing on these topics included in Microsoft Visual C++ ver. 6.0 (included in MS Visual Studio 6.0), and some general terms and definitions that are commonly used in the areas of Localization and Globalization of application. More emphasis on Arabic language will be applied when considering language specific programming tips. If you have any inquiries or a bug list you wish to submit please contact us through our e-mail: vsarabic@microsoft.com Microsoft Visual Studio Team 2 Best Practices for Globalization Globalization is the practice of designing and implementing software that is not locale dependent, i.e., can accommodate any locale. In software design, a locale is defined as a set of user preferences associated with a user’s language. A locale in Windows 2000 includes formats for date, time, currency and numbers; rules and tables for sorting and comparison; and tables of character classifications. Other user preferences that a globalized application should accommodate include userinterface language, default font selection, language rules for use in spell checking and grammar, and input methods such as keyboard layouts and input method editors. Guidelines for developing a globalized application include the following: Technical Considerations 1. Use Unicode as your character encoding to represent text. If your application must also run on Windows 9x, consider a method for using Unicode on those platforms, as described in recent Microsoft Systems Journal articles. If you cannot use Unicode for whatever reason, you will need to implement DBCS enabling, BiDi enabling, codepage switching, text tagging, etc. Guidelines related to this functionality are available elsewhere (see for example, http://www.microsoft.com/globaldev). 2. Consider multilingual user interface: launch the application in the default user interface language, and offer the option to change to other languages. 3. Use the Win32 API NLS functions to handle locale sensitive data. 4. Watch for windows messages that indicate changes in the input language, and use that information for spell checking, font selection, etc. 5. Use the Script APIs (Uniscribe) to layout formatted text on a page. This will allow your application to display multilingual text and complex scripts such as Arabic, Hebrew, Hindi, Tamil, and Thai. 6. Test your application on all language variants of Windows 2000, using all possible locales. Microsoft Visual Studio Team 3 Cultural and Political Considerations 1. Avoid slang expressions, colloquialisms, and obscure phrasing in all text. At best they are difficult to translate; at worst they are offensive. 2. Avoid images in bitmaps and icons that are ethno-centric or offensive in other cultures. For example, the US style mailbox is sometimes used to indicate an electronic mailbox, but many Europeans have no idea what it is (they think it looks like a tunnel). 3. Avoid maps that include controversial national boundaries – they are a notorious source of political offense. Best Practices for Localizability In contrast to globalization, localization is the process of modifying an application so that its user interface is in the language of the user. Well designed software can be localized to any of the languages supported by Windows 2000 without changes to the source code, i.e., without compilation. In addition to the guidelines for globalization mentioned above, those for localizability include the following: 1. Isolate all user interface elements from the program source code. Put them in resource files, message files or a private database. 2. Use the same resource identifiers throughout the life of the project. Changing identifiers makes it difficult to update localized resources from one build to another. 3. Make multiple copies of the same string if it is used in multiple contexts. The same string may have different translations in different contexts. 4. Do not place strings that should not be localized in resources. Leave them as string constants in the source code. 5. Allocate text buffers dynamically, since text size may expand when translated. If you must use static buffers, make them extra large to accommodate localized strings (e.g., double the English string length). 6. Keep in mind that dialog boxes may expand due to localization. Thus, a large dialog box that occupies the entire screen in low-resolution mode may have to be resized to an unusable size when localized. 7. Avoid text in bitmaps and icons, as these are difficult to localize. 8. Do not create a text message dynamically at runtime, either by concatenating multiple strings or by removing characters from static text. Word order varies by language, so dynamic composition of text in this manner requires code changes to localize to some languages. Microsoft Visual Studio Team 4 9. Similarly, avoid composing text using multiple insertion parameters in a format string (e.g., in sprintf or wsprintf), because the order of insertion of the arguments changes when translated to some languages. 10. If localizing to a Middle Eastern language such as Arabic or Hebrew, use the right-toleft layout APIs to layout your application right to left. See the Microsoft Systems Journal article for details. 11. Test localized applications on all language variants of Windows 2000. If your application uses Unicode, as recommended, it should run fine with no modifications. If it uses a Windows codepage you will need to set the system locale to the appropriate value for your localized application, and reboot, before testing. Microsoft Visual Studio Team 5 Locales and Code Pages A “locale” reflects the local conventions and language for a particular geographical region. A given language may be spoken in more than one country; for example, Portuguese is spoken in Brazil as well as in Portugal. Conversely, a country may have more than one official language. For example, Canada has two: English and French. Thus, Canada has two distinct locales: Canadian-English and Canadian French. Some locale-dependent categories include the formatting of dates and the display format for monetary values. The language determines the text and data formatting conventions, while the country determines the national conventions. Every language has a unique mapping, represented by “code pages,” which includes characters other than those in the alphabet (such as punctuation marks and numbers). A code page is a character set and is related to the current locale and language. As such, a locale is a unique combination of language, country, and code page. The code page setting can determine the locale setting and can be changed at run time by calling the setlocale function. Different languages may use different code pages. For example, the ANSI code page 1252 is used for American English and most European languages, and the ANSI code page 932 is used for Japanese Kanji. Virtually all code pages share the ASCII character set for the lowest 128 characters (0x00 to 0x7F). Any single-byte code page can be represented in a table (with 256 entries) as a mapping of byte values to characters (including numbers and punctuation marks), or glyphs. Any multibyte code page can also be represented as a very large table (with 64K entries) of double-byte values to characters. In practice, however, it are usually represented as a table for the first 256 (single-byte) characters and as ranges for the double-byte values. The C run-time library has two types of internal code pages: locale and multibyte. You can change the current code page during program execution (see the documentation for the setlocale and _setmbcp functions). Also, the run-time library may obtain and use the value of the operating system code page. In Windows NT, the operating system code page is the “system default ANSI” code page. This code page is constant for the duration of the program’s execution. When the locale code page changes, the behavior of the locale-dependent set of functions changes to that dictated by the chosen code page. By default, all locale-dependent functions begin execution with a locale code page unique to the “C” locale. You can change the internal locale code page (as well as other locale-specific properties) by calling the setlocale function. A call to setlocale(LC_ALL, "") will set the locale to that indicated by the operating system’s default code page. Similarly, when the multibyte code page changes, the behavior of the multibyte functions changes to that dictated by the chosen code page. By default, all multibyte functions begin Microsoft Visual Studio Team 6 execution with a multibyte code page corresponding to the operating system’s default code page. You can change the internal multibyte code page by calling the _setmbcp function. The C run-time function setlocale sets, changes, or queries some or all of the current program’s locale information. The _wsetlocale routine is a wide-character version of setlocale; the arguments and return values of _wsetlocale are wide-character strings. Microsoft Visual Studio Team 7 SETLOCALE, _WSETLOCALE Define the locale char *setlocale( int category, const char *locale ); wchar_t *_wsetlocale( int category, const wchar_t *locale ); Routine Required Header Compatibility setlocale <locale.h> ANSI, Win 95, Win NT _wsetlocale <locale.h> or <wchar.h> Win 95, Win NT For additional compatibility information, see Compatibility in the Introduction. Libraries LIBC.LIB Single thread static library, retail version LIBCMT.LIB Multithread static library, retail version MSVCRT.LIB Import library for MSVCRT.DLL, retail version Return Value If a valid locale and category are given, the function returns a pointer to the string associated with the specified locale and category. If the locale or category is invalid, the function returns a null pointer and the current locale settings of the program are not changed. For example, the call setlocale( LC_ALL, "English" ); sets all categories, returning only the string English_USA.1252. If all categories are not explicitly set by a call to setlocale, the function returns a string indicating the current setting of each of the categories, separated by semicolons. If the locale argument is a null pointer, setlocale returns a pointer to the string associated with the category of the program’s locale; the program’s current locale setting is not changed. The null pointer is a special directive that tells setlocale to query rather than set the international environment. For example, the sequence of calls Microsoft Visual Studio Team 8 // Set all categories and return "English_USA.1252" setlocale( LC_ALL, "English" ); // Set only the LC_MONETARY category and return "French_France.1252" setlocale( LC_MONETARY, "French" ); setlocale( LC_ALL, NULL ); returns LC_COLLATE=English_USA.1252; LC_CTYPE=English_USA.1252; LC_MONETARY=French_France.1252; LC_NUMERIC=English_USA.1252; LC_TIME=English_USA.1252 which is the string associated with the LC_ALL category. You can use the string pointer returned by setlocale in subsequent calls to restore that part of the program’s locale information, assuming that your program does not alter the pointer or the string. Later calls to setlocale overwrite the string; you can use _strdup to save a specific locale string. Parameters category Category affected by locale locale Locale name Remarks Use the setlocale function to set, change, or query some or all of the current program locale information specified by locale and category. “Locale” refers to the locality (country and language) for which you can customize certain aspects of your program. Some localedependent categories include the formatting of dates and the display format for monetary values. _wsetlocale is a wide-character version of setlocale; the locale argument and return value of _wsetlocale are wide-character strings. _wsetlocale and setlocale behave identically otherwise. Microsoft Visual Studio Team 9 Generic-Text Routine Mappings TCHAR.H _UNICODE & _MBCS Routine Not Defined _tsetlocale setlocale _MBCS Defined _UNICODE Defined setlocale _wsetlocale The category argument specifies the parts of a program’s locale information that are affected. The macros used for category and the parts of the program they affect are as follows: LC_ALL All categories, as listed below LC_COLLATE The strcoll, _stricoll, wcscoll, _wcsicoll, and strxfrm functions LC_CTYPE The character-handling functions (except isdigit, isxdigit, mbstowcs, and mbtowc, which are unaffected) LC_MONETARY Monetary-formatting information returned by the localeconv function LC_NUMERIC Decimal-point character for the formatted output routines (such as printf), for the dataconversion routines, and for the nonmonetary-formatting information returned by localeconv LC_TIME The strftime and wcsftime functions The locale argument is a pointer to a string that specifies the name of the locale. If locale points to an empty string, the locale is the implementation-defined native environment. A value of “C” specifies the minimal ANSI conforming environment for C translation. The “C” locale assumes that all char data types are 1 byte and that their value is always less than 256. The “C” locale is the only locale supported in Microsoft Visual C++ version 1.0 and earlier versions of Microsoft C/C++. Microsoft Visual C++ supports all the locales listed in Language and Country Strings. At program startup, the equivalent of the following statement is executed: setlocale( LC_ALL, "C" ); The locale argument takes the following form: locale :: "lang[_country[.code_page]]" | ".code_page" Microsoft Visual Studio Team 10 | "" | NULL The set of available languages, countries, and code pages includes all those supported by the Win32 NLS API. The set of language and country codes supported by setlocale is listed in Appendix A, Language and Country Strings. If locale is a null pointer, setlocale queries, rather than sets, the international environment, and returns a pointer to the string associated with the specified category. The program’s current locale setting is not changed. For example, setlocale( LC_ALL, NULL ); returns the string associated with category. The following examples pertain to the LC_ALL category. Either of the strings ".OCP" and ".ACP" can be used in place of a code page number to specify use of the system default OEM code page and system-default ANSI code page, respectively. setlocale( LC_ALL, "" ); Sets the locale to the default, which is the system-default ANSI code page obtained from the operating system. setlocale( LC_ALL, ".OCP" ); Explicitly sets the locale to the current OEM code page obtained from the operating system. setlocale( LC_ALL, ".ACP" ); Sets the locale to the ANSI code page obtained from the operating system. setlocale( LC_ALL, "[lang_ctry]" ); Sets the locale to the language and country indicated, using the default code page obtained from the host operating system. setlocale( LC_ALL, "[lang_ctry.cp]" ); Sets the locale to the language, country, and code page indicated in the [ lang_ctry.cp] string. You can use various combinations of language, country, and code page. For example: setlocale( LC_ALL, "French_Canada.1252" ); // Set code page to French Canada ANSI default setlocale( LC_ALL, "French_Canada.ACP" ); // Set code page to French Canada OEM default setlocale( LC_ALL, "French_Canada.OCP" ); setlocale( LC_ALL, "[lang]" ); Microsoft Visual Studio Team 11 Sets the locale to the country indicated, using the default country for the language specified, and the system-default ANSI code page for that country as obtained from the host operating system. For example, the following two calls to setlocale are functionally equivalent: setlocale( LC_ALL, "English" ); setlocale( LC_ALL, "English_United States.1252" ); setlocale( LC_ALL, "[.code_page]" ); Sets the code page to the value indicated, using the default country and language (as defined by the host operating system) for the specified code page. The category must be either LC_ALL or LC_CTYPE to effect a change of code page. For example, if the default country and language of the host operating system are “United States” and “English,” the following two calls to setlocale are functionally equivalent: setlocale( LC_ALL, ".1252" ); setlocale( LC_ALL, "English_United States.1252"); For more information see the setlocale pragma in Preprocessor Reference. Microsoft Visual Studio Team 12 Example /* LOCALE.C: Sets the current locale to "Germany" using the * setlocale function and demonstrates its effect on the strftime * function. */ #include <stdio.h> #include <locale.h> #include <time.h> void main(void) { time_t ltime; struct tm *thetime; unsigned char str[100]; setlocale(LC_ALL, "German"); time (&ltime); thetime = gmtime(&ltime); /* %#x is the long date representation, appropriate to * the current locale */ if (!strftime((char *)str, 100, "%#x", (const struct tm *)thetime)) printf("strftime failed!\n"); else printf("In German locale, strftime returns '%s'\n", str); Microsoft Visual Studio Team 13 /* Set the locale back to the default environment */ setlocale(LC_ALL, "C"); time (&ltime); thetime = gmtime(&ltime); if (!strftime((char *)str, 100, "%#x", (const struct tm *)thetime)) printf("strftime failed!\n"); else printf("In 'C' locale, strftime returns '%s'\n", str); } Output In German locale, strftime returns 'Donnerstag, 22. April 1993' In 'C' locale, strftime returns 'Thursday, April 22, 1993' Microsoft Visual Studio Team 14 Locale Constants Use the setlocale function to change or query some or all of the current program locale information. “Locale” refers to the locality (the country and language) for which you can customize certain aspects of your program. Some locale-dependent categories include the formatting of dates and the display format for monetary values. For more information, see Locale Categories. Locale-Dependent Routines setlocale Category Routine atof, atoi, atol Use Setting Dependence Convert character to floating-point, integer, or long integer LC_NUMERIC value, respectively is Routines Test given integer for particular LC_CTYPE condition. isleadbyte Test for lead byte () LC_CTYPE localeconv Read appropriate values for formatting LC_MONETARY, numeric quantities LC_NUMERIC Maximum length in bytes of any LC_CTYPE MB_CUR_MAX multibyte character in current locale (macro defined in STDLIB.H) _mbccpy Copy one multibyte character LC_CTYPE _mbclen Return length, in bytes, of given LC_CTYPE multibyte character Mblen Validate and return number of bytes in LC_CTYPE multibyte character _mbstrlen For multibyte-character validate each character strings: LC_CTYPE in string; return string length mbstowcs Convert sequence of multibyte LC_CTYPE Microsoft Visual Studio Team 15 characters to corresponding sequence of wide characters mbtowc Convert multibyte character to LC_CTYPE corresponding wide character printf functions Write formatted output LC_NUMERIC (determines radix character output) scanf functions Read formatted input LC_NUMERIC (determines radix character recognition) setlocale, Select locale for program Not applicable strcoll, wcscoll Compare characters of two strings LC_COLLATE _stricoll, _wcsicoll Compare characters of two strings LC_COLLATE _wsetlocale (case insensitive) _strncoll, Compare first n characters of two _wcsncoll strings _strnicoll, Compare first n characters of two _wcsnicoll strings (case insensitive) strftime, wcsftime Format date and time value according LC_COLLATE LC_COLLATE LC_TIME to supplied format argument _strlwr Convert, in place, each uppercase LC_CTYPE letter in given string to lowercase strtod, wcstod, strtol, wcstol, Convert character string to double, LC_NUMERIC (determines long, or unsigned long value radix character recognition) Convert, in place, each lowercase LC_CTYPE strtoul, wcstoul _strupr letter in string to uppercase strxfrm, wcsxfrm Transform string into collated form LC_COLLATE according to locale tolower, towlower Convert given character to LC_CTYPE Microsoft Visual Studio Team 16 corresponding lowercase character toupper, towupper Convert given character to LC_CTYPE Convert sequence of wide characters LC_CTYPE corresponding uppercase letter wcstombs to corresponding sequence of multibyte characters wctomb Convert wide character to LC_CTYPE corresponding multibyte character _wtoi, _wtol Convert wide-character string to int or LC_NUMERIC long Microsoft Visual Studio Team 17 Locale Categories #include <locale.h> Remarks Locale categories are manifest constants used by the localization routines to specify which portion of a program's locale information will be used. The locale refers to the locality (or country) for which certain aspects of your program can be customized. Locale-dependent areas include, for example, the formatting of dates or the display format for monetary values. Locale Category Parts of Program Affected LC_ALL All locale-specific behavior (all categories) LC_COLLATE Behavior of strcoll and strxfrm functions LC_CTYPE Behavior of character-handling functions (except isdigit, isxdigit, mbstowcs, and mbtowc, which are unaffected) LC_MAX Same as LC_TIME LC_MIN Same as LC_ALL LC_MONETARY Monetary formatting information returned by the localeconv function LC_NUMERIC Decimal-point character for formatted output routines (for example, printf), data conversion routines, and nonmonetary formatting information returned by localeconv function LC_TIME Behavior of strftime function Microsoft Visual Studio Team 18 Localization of MFC Components This part describes some of the designs and procedures you can use to localize your component, be it an application or an OLE control, or a DLL, which uses MFC. Overview There are really two issues to resolve when localizing a component, which uses MFC. First of all, you must localize your own resources – strings, dialogs, and other resources that are specific to your component. Most components built using MFC also include and use a number of resources that are defined by MFC. You must provide localized MFC resources as well. Fortunately, several languages are already provided by MFC itself. In addition, your component should be prepared to run in its target environment (European, or DBCS enabled environment). For the most part, this depends on your application treating characters with the high bit set correctly and handling strings with double byte characters. MFC is enabled, by default, for both of these environments, such that it is possible to have a single “world wide” binary that is used on all platforms with just different resources plugged in at setup time. Localizing your Component’s Resources Localizing your application or DLL should involve simply replacing the resources with resources that match the target language. For your own resources, this is relatively simple: edit the resources in the resource editor and build your application. If your code is written properly there will be no strings or text that you wish to localize hard-coded into your C++ source code, all localization can be done by simply modifying resources. In fact, you can implement your component such that all providing a localized version does not even involve a build of the original code. This is more complex, but is well worth it and is the mechanism chosen for MFC itself. It is also possible to localize an application by loading the EXE or DLL file into the resource editor and editing the resources directly. While this is possible, it is requires re-application of those changes each time you build a new version of your application. One way to avoid that is to locate all resources in a separate DLL, sometimes called a satellite DLL. This DLL is then loaded dynamically at runtime and the resources are loaded from that DLL instead of from the main module with all your code. MFC directly supports this approach. Consider an application called MYAPP.EXE; it could have all of its resources located in a DLL called MYRES.DLL. In the application’s InitInstance it would perform the following to load that DLL and cause MFC to load resources from that location: Microsoft Visual Studio Team 19 CMyApp::InitInstance () { // One of the first things in the init code HINSTANCE hInst = LoadLibrary(“myres.dll”); if (hInst != NULL) AfxSetResourceHandle(hInst); // other initialization code would follow . . . } From then on, MFC will load resources from that DLL instead of from myapp.exe. All resources, however, must be present in that DLL – MFC will not search the application’s instance in search of a given resource. This technique applies equally well to regular DLLs as well as OLE Controls. Your setup program would copy the appropriate version of MYRES.DLL depending upon which resource locale the user would like. It is relatively easy to create a resource only DLL. You create a DLL project, add your .RC file to it, and add the necessary resources. If you have an existing project that does not use this technique, you can copy the resources from that project. After adding the resource file to the project you are almost ready to build the project. The only thing you must do is set the linker options to include /NOENTRY. This tells the linker that the DLL has no entry point – since it has no code, it has no entry point. Note The resource editor in Visual C++ 4.0 and later supports multiple languages per .RC file. This can make it very easy to manage your localization in a single project. Preprocessor directives generated by the resource editor control the resources for each language. Using the Provided MFC Localized Resources Any MFC application that you build re-uses two things from MFC: code and resources. That is, MFC has various error messages, built-in dialogs, and other resources that are used by the MFC classes. In order to completely localize your application, you need to localize not only your application’s resources, but also the resources, which come directly from MFC. MFC provides a number of different language resource files automatically, so that if the language you are targeting is one of the languages MFC already supports, you just need to make sure you use those localized resources. As of this writing MFC supports: Chinese, German, Spanish, French, Italian, Japanese, and Korean. The files which contain these localized versions are in the MFC\INCLUDE\L.* (the ‘L’ stands for localized) directories. The German files are in MFC\INCLUDE\L.DEU, for example. Microsoft Visual Studio Team 20 In order to cause your application to use these RC files instead of the files located in MFC\INCLUDE, simply add a /IC:\PROGRAM FILES\DEVSTUDIO\VC\MFC\INCLUDE\L.DEU to your RC command line (this is just an example – you would need to substitute your locale of choice as well as the directory into which you installed Visual C++). The above instructions will work if your application links statically with MFC. Most applications link dynamically (because that is the AppWizard default). In this scenario, not only the code is dynamically linked – so are the resources. As a result, you can localize your resources in your application, but the MFC implementation resources will still be loaded from the MFC4x.DLL (or a later version) or from MFC4xLOC.DLL if it exists. You can approach this from two different angles. Feature Only in Professional and Enterprise Editions Static linking to MFC is supported only in Visual C++ Professional and Enterprise Editions. For more information, see Visual C++ Editions. The more complex approach is to ship one of the localized MFC4xLOC.DLLs (such as MFC4xDEU, for German, MFC4xESP.DLL for Spanish, etc.), or a later version, and install the appropriate MFC4xLOC.DLL into the system directory when the user installs your application. This can be very complex for both the developer and the end user and as such is not recommended. See Technical Note 56 for more information on this technique and its caveats. The simplest and safest approach is to include the localized MFC resources in your application or DLL itself (or its satellite DLL if you are using one). This avoids the problems of installing MFC4xLOC.DLL properly. To do so, you follow the same instructions for the static case given above (setting the RC command line properly to point to the localized resources), except that you must also remove the /D_AFXDLL define that was added by AppWizard. When /D_AFXDLL is defined, AFXRES.H (and the other MFC RC files) don’t actually define any resources (because they will be pulled from the MFC DLLs instead). Microsoft Visual Studio Team 21 ActiveX Controls: Localizing an ActiveX Control This article discusses procedures for localizing ActiveX control interfaces. If you want to adapt an ActiveX control to an international market, you may want to localize the control. Windows supports several languages in addition to the default English, including German, French, and Swedish. This can present problems for the control if its interface is in English only. In general, ActiveX controls should always base their locale on the ambient LocaleID property. There are three ways to do this: Load resources, always on demand, based on the current value of the ambient LocaleID property. The MFC ActiveX controls sample LOCALIZE, listed under CONTROLS, uses this strategy. Load resources when the first control is instanced, based on the ambient LocaleID property, and use these resources for all other instances. This article demonstrates this strategy. Note This will not work correctly in some cases, if future instances have different locales. Use the OnAmbientChanged notification function to dynamically load the proper resources for the container’s locale. Note This will work for the control, but the run-time DLL will not dynamically update its own resources when the ambient LocaleID property changes. In addition, run-time DLLs for ActiveX controls use the thread locale to determine the locale for its resources. The rest of this article describes two localizing strategies. The first strategy localizes the control’s programmability interface (names of properties, methods, and events). The second strategy localizes the control’s user interface, using the container’s ambient LocaleID property. For a demonstration of control localization, see the MFC ActiveX controls sample LOCALIZE, listed under CONTROLS. Microsoft Visual Studio Team 22 Localizing the Control’s Programmability Interface When localizing the control’s programmability interface (the interface used by programmers writing applications that use your control), you must create a modified version of the control .ODL file (a script for building the control type library) for each language you intend to support. This is the only place you need to localize the control property names. When you develop a localized control, include the locale ID as an attribute at the type library level. For example, if you want to provide a type library with French localized property names, make a copy of your SAMPLE.ODL file, and call it SAMPLEFR.ODL. Add a locale ID attribute to the file (the locale ID for French is 0x040c), similar to the following: [ uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx), version(1.0), lcid(0x040c) ] Change the property names in SAMPLEFR.ODL to their French equivalents, and then use MKTYPLIB.EXE to produce the French type library, SAMPLEFR.TLB. To create multiple localized type libraries you can add any localized .ODL files to the project and they will be built automatically. To add an .ODL file to your ActiveX control project 1. With your control project open, on the Project menu, click Add to Project and then Files. 2. The Insert Files Into Project dialog box appears. 3. If necessary, select the drive and directory to view. 4. In the Files of Type box, select Interface Definition Files (*.odl,*.idl). 5. In the file list box, double-click the .ODL file you want to insert into the project. 6. Close the Insert Files Into Project dialog box when you have added all necessary .ODL files. Because the files have been added to the project, they will be built when the rest of the project is built. The localized type libraries are located in the current ActiveX control project directory. Within your code, the internal property names (usually in English) are always used and are never localized. This includes the control dispatch map, the property exchange functions, and your property page data exchange code. Only one type library (.TLB) file may be bound into the resources of the control implementation (.OCX) file. This is usually the version with the standardized (typically, English) names. To ship a localized version of your control you need to ship the .OCX (which has already been bound to the default .TLB version) and the .TLB for the appropriate locale. This means that only the .OCX is needed for English versions, since the correct .TLB has Microsoft Visual Studio Team 23 already been bound to it. For other locales, the localized type library also must be shipped with the .OCX. To ensure that clients of your control can find the localized type library, register your localespecific .TLB file(s) under the TypeLib section of the Windows system registry. The third parameter (normally optional) of the AfxOleRegisterTypeLib function is provided for this purpose. The following example registers a French type library for an ActiveX control: STDAPI DllRegisterServer(void) { AFX_MANAGE_STATE(_afxModuleAddrThis); if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid)) return ResultFromScode(SELFREG_E_TYPELIB); AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid, _T("samplefr.tlb")) if (!COleObjectFactoryEx::UpdateRegistryAll(TRUE)) return ResultFromScode(SELFREG_E_CLASS); return NOERROR; } When your control is registered, the AfxOleRegisterTypeLib function automatically looks for the specified .TLB file in the same directory as the control and registers it in the Windows registration database. If the .TLB file isn’t found, the function has no effect. Localizing the Control’s User Interface To localize a control’s user interface, place all of the control’s user-visible resources (such as property pages and error messages) into language-specific resource DLLs. You then can use the container’s ambient LocaleID property to select the appropriate DLL for the user’s locale. The following code example demonstrates one approach to locate and load the resource DLL for a specific locale. This member function, called GetLocalizedResourceHandle for this example, can be a member function of your ActiveX control class: HINSTANCE CSampleCtrl::GetLocalizedResourceHandle(LCID lcid) { LPCTSTR lpszResDll; HINSTANCE hResHandle = NULL; LANGID lang = LANGIDFROMLCID(lcid); switch (PRIMARYLANGID(lang)) { Microsoft Visual Studio Team 24 case LANG_ENGLISH: lpszResDll = “myctlen.dll”; break; case LANG_FRENCH: lpszResDll = “myctlfr.dll”; break; case LANG_GERMAN: lpszResDll = “myctlde.dll”; break; case 0: default: lpszResDll = NULL; } if (lpszResDll != NULL) hResHandle = LoadLibrary(lpszResDll); #ifndef _WIN32 if(hResHandle <= HINSTANCE_ERROR) hResHandle = NULL; #endif return hResHandle; } Microsoft Visual Studio Team 25 Note that the sub language ID could be checked in each case of the switch statement, to provide more specialized localization (for example, local dialects of German). For a demonstration of this function, see the GetResourceHandle function in the MFC ActiveX controls sample LOCALIZE, listed under CONTROLS. When the control first loads itself into a container, it can call COleControl::AmbientLocaleID to retrieve the locale ID. The control can then pass the returned locale ID value to the GetLocalizedResourceHandle function, which loads the proper resource library. The control should pass the resulting handle, if any, to AfxSetResourceHandle: m_hResDll = GetLocalizedResourceHandle( AmbientLocaleID() ); if (m_hResDll != NULL) AfxSetResourceHandle(m_hResDll); Microsoft Visual Studio Team 26 Place the code sample above into a member function of the control, such as an override of COleControl::OnSetClientSite. In addition, m_hResDLL should be a member variable of the control class. You can use similar logic for localizing a control’s property page. To localize the property page, add code similar to the following sample to your property page’s implementation file (in an override of COlePropertyPage::OnSetPageSite): LPPROPERTYPAGESITE pSite; LCID lcid = 0; if((pSite = GetPageSite()) != NULL) pSite->GetLocaleID(&lcid); HINSTANCE hResource = GetLocalizedResourceHandle(lcid); HINSTANCE hResourceSave = NULL; if (hResource != NULL) { hResourceSave = AfxGetResourceHandle(); AfxSetResourceHandle(hResource); } // Load dialog template and caption string. COlePropertyPage::OnSetPageSite( ); if (hResource != NULL) AfxSetResourceHandle(hResourceSave); Microsoft Visual Studio Team 27 locale class locale { public: class facet; class id; typedef int category; static const category none, collate, ctype, monetary, numeric, time, messages, all; locale(); explicit locale(const char *s); locale(const locale& x, const locale& y, category cat); locale(const locale& x, const char *s, category cat); bool operator()(const string& lhs, const string& rhs) const; string name() const; bool operator==(const locale& x) const; bool operator!=(const locale& x) const; static locale global(const locale& x); static const locale& classic(); }; The class describes a locale object that encapsulates a locale. It represents culture-specific information as a list of facets. A facet is a pointer to an object of a class derived from class facet that has a public object of the form: static locale::id id; You can define an open-ended set of these facets. You can also construct a locale object that designates an arbitrary number of facets. Microsoft Visual Studio Team 28 Predefined groups of these facets represent the locale categories traditionally managed in the Standard C library by the function setlocale. Category collate (LC_COLLATE) includes the facets: collate<char> collate<wchar_t> Category ctype (LC_CTYPE) includes the facets: ctype<char> ctype<wchar_t> codecvt<char, char, mbstate_t> codecvt<wchar_t, char, mbstate_t> Category monetary (LC_MONETARY) includes the facets: moneypunct<char, false> moneypunct<wchar_t, false> moneypunct<char, true> moneypunct<wchar_t, true> money_get<char, istreambuf_iterator<char> > money_get<wchar_t, istreambuf_iterator<wchar_t> > money_put<char, ostreambuf_iterator<char> > money_put<wchar_t, ostreambuf_iterator<wchar_t> > Category numeric (LC_NUMERIC) includes the facets: num_get<char, istreambuf_iterator<char> > num_get<wchar_t, istreambuf_iterator<wchar_t> > num_put<char, ostreambuf_iterator<char> > num_put<wchar_t, ostreambuf_iterator<wchar_t> > numpunct<char> numpunct<wchar_t> Category time (LC_TIME) includes the facets: Microsoft Visual Studio Team 29 time_get<char, istreambuf_iterator<char> > time_get<wchar_t, istreambuf_iterator<wchar_t> > time_put<char, ostreambuf_iterator<char> > time_put<wchar_t, ostreambuf_iterator<wchar_t> > Category messages (LC_MESSAGE) includes the facets: messages<char> messages<wchar_t> (The last category is required by Posix, but not the C Standard.) Some of these predefined facets are used by the iostreams classes to control the conversion of numeric values to and from text sequences An object of class locale also stores a locale name as an object of class string. Using an invalid locale name to construct a locale facet or a locale object throws an object of class runtime_error. If the stored locale name is "*", no C-style locale corresponds exactly to that represented by the object. Otherwise, you can establish a matching locale within the Standard C library by calling setlocale( LC_ALL, x.name. c_str()). In this implementation, you can also call the static member function: static locale empty(); to construct a locale object that has no facets. It is also a transparent locale -- the template function use_facet consults the global locale if it cannot find the requested facet in a transparent locale. Thus, you can write: cout.imbue(locale::empty()); Subsequent insertions to cout are mediated by the current state of the global locale. You can even write: locale loc(locale::empty(), locale("C"), locale::numeric); cout.imbue(loc); Numeric formatting rules remain the same as in the C locale even as the global locale supplies changing rules for inserting dates and monetary amounts. Microsoft Visual Studio Team