by Michael S. Kaplan, published on 2005/02/01 09:35 -08:00, original URI: http://blogs.msdn.com/michkap/archive/2005/02/01/364707.aspx
A few years back (some time before Windows XP shipped) when we located in were in Building 9 and much smaller than we are now, someone else in the building was having a problem. Our kind of problem. An international problem. I don't remember what it was -- something to do with code pages, maybe?
Anyway, Wei Wu, one of our cool development leads, asked a few configuration questions, and at the end of the message, asked him "what is your default system locale?".
His response, which I cannot find a copy of now, was priceless. Assuming that Wei was going to stop by to look at the machine. this guy started to describe the location of his computer.... :-)
One of those jokes that most people won't quite get, and jokes are never funny if they have to be explained. Ah, the life of an international geek.
But it was pretty funny, I think. We all had a good laugh at the time.
There is a not-so-hidden truth in there -- our terminiology story is weak. It really ought to be better. So, once again, here is a quick glossary of the four most common types of locales:
DEFAULT USER LOCALE (Windows XP term: "Standards and Formats"):
This setting controls the way information is presented -- the sort order in list boxes, the format of date, time, number, and currency values, the calendar you prefer to use. The list of locales can be thought of as a big group of defaults that are grouped by many language/region pairings, which you can see in the first tab Regional and Language Options Control Panel applet. Several of the settings are customizable, particularly the various formats.
The setting is per-user and when you change the setting, it is effective immediately, and all top level windows in the user's windowstation will get a WM_SETTINGCHANGE message indicating that the change has happened so that they too can reflect the change immediately.
Developers will commonly use LOCALE_USER_DEFAULT as their LCID of choice, whether by doing so directly or by calling functions in SHELL, USER, or elsewhere that do so for them. Using this setting and behaving appropriately with the results thereby is a sure sign that the developer respects the user's settings.
DEFAULT SYSTEM LOCALE (Windows XP term: "Language for non-Unicode Programs"):
This setting has three major purposes:
- Specifies the default ANSI, OEM, MAC, and EBCDIC code pages to use for non-Unicode programs.
- Specifies some of the font linking preferences for CJK fonts and for legacy bitmap fonts.
- Specifies application behavior when developers incorrectly use this setting rather than the DEFAULT USER LOCALE.
This setting is found on the third tab of the Windows XP/Server 2003 Regional and Language Options dialog and in the "Default" button on the first tab of the Windows 2000 Regional Options dialog.
Changing the setting changes it for the entire machine and it requires a reboot to take effect. No notification mechanism is done, nor is one needed since no change happens until the reboot does. A small number of misbehaving applications which check the registry rather than using the APIs will get wrong results after the setting change but before the reboot.
Unfortunately, developers will sometimes use LOCALE_SYSTEM_DEFAULT for purposes other than #1 and #2, and by doing so they manage to simultaneously show their users disrespect and cause yet another compatibility weirdness with functionality tied to the default system locale. Given the fact that a reboot is required, you think people would avoid this, but dven developers are not always perfect.
The XP name should be a big hint, though sometimes it adds confusion.
DEFAULT USER INTERFACE LANGUAGE (Windows XP Term: "Language used in menus and dialogs"):
This setting controls the language in which the UI is presented. It is only present if you have the MUI version of Windows (which is to say that you have Windows with the multilanguage files installed).
This setting is found on the second tab of the Windows XP/Server 2003 Regional and Language Options dialog and in the middle of the first tab of the Windows 2000 Regional Options dialog.
The setting is per-user and changing it requires a logoff to take effect.
There is no constant for it but the GetUserDefaultUILanguage API will retrieve the setting quickly enough. Given the changes to the resource model that the changes to support MUI inspired, it is easy for applications to plug into the very same setting automatically. I'll talk more about this another time....
DEFAULT INPUT LOCALE (Windows XP Term: "Default Input Language"):
This setting controls the initial input language used for all newly created threads.
This setting is found on the second tab of the Windows XP/Server 2003 Regional and Language Options dialog (hit the "Details..." button) and on the last tab of the Windows 2000 Regional Options dialog.
The setting is per-user and it takes place immediately. But obviously will not change the input language on any existing threads; only new threads get the new default.
Developers can find out what the current setting is by calling the SystemParametersInfo API with the SPI_GETDEFAULTINPUTLANG parameter. You can even set it with the SPI_SETDEFAULTINPUTLANG parameter but this almost always something that a developer should not be doing -- it is a user preference. Since proper application behavior is mostly about respect, this really is a constant that you should avoid. :-)
For more on this topic, Dr. International has bigger lists here and here, and there is more information here.
But when you are a developer, respect is the key here -- respect of the user's preferences and settings.
When you are a user, consider which applications respect your settings and which do not. Because while doing so may not be convenient for an application, it is certainly possible....
This post sponsored by "ð" (U+00f0, a.k.a. LATIN SMALL LETTER ETH)
# Ben Bryant on Tuesday, February 01, 2005 3:59 PM:
# Ben Bryant on Tuesday, February 01, 2005 4:24 PM:
# Michael Kaplan on Tuesday, February 01, 2005 4:46 PM:
# Michael Kaplan on Tuesday, February 01, 2005 8:55 PM:
# Jonathan Payne on Wednesday, February 02, 2005 3:42 AM:
# Michael Kaplan on Wednesday, February 02, 2005 6:15 AM:
# Mike Dimmick on Wednesday, February 02, 2005 6:31 AM:
# Jonathan Payne on Wednesday, February 02, 2005 7:41 AM:
# Michael Kaplan on Wednesday, February 02, 2005 7:47 AM:
# Ivo on Wednesday, February 02, 2005 12:42 PM:
# Michael Kaplan on Wednesday, February 02, 2005 12:46 PM:
# Ivo on Wednesday, February 02, 2005 4:05 PM:
# Michael Kaplan on Wednesday, February 02, 2005 4:26 PM:
# Richard on Thursday, February 03, 2005 1:09 AM:
# Michael Kaplan on Thursday, February 03, 2005 1:45 AM:
# Mike Williams on Thursday, February 03, 2005 3:18 AM:
# Richard on Monday, February 07, 2005 2:14 AM:
# Ovate on Friday, April 15, 2005 10:21 PM:
# Michael S. Kaplan on Saturday, April 16, 2005 11:57 AM:
# Michael Holtstrom on Friday, January 09, 2009 11:46 AM:
I have a db that contains latin1 data and is accessed by several apps. One app is on the dos console. It used to be built with vs6, now vs9. We call setlocale(LC_CTYPE,""); near startup.
Default winxp is oemcp 437. That means that not all latin1 chars can be displayed in dos. Fine, we detect these chars display simple-ascii escape sequences instead.
Now the suffering begins. Default winxp is LC_CTYPE English_United States.1252. In vs6 that didn't matter, but in vs9, printfs are overriden and a magic last-minute best-fit mapping occurs. Very painful for me.
Here is a little app that your readers might find useful.
#include <iostream>
#include <windows.h>
// Note: In order to ditch the UNICODE define you have to do the following:
// Configuration Properties >> C/C++ >> Preprocessor >> Preprocessor Definitions >> [uncheck] Inherit from parent or project defaults
// If your product is 14 years old, switching to UNICODE just isn't an option.
void info()
{
// GetCPInfoEx vars
BOOL res;
CPINFOEX CPInfoEx;
// Registry vars
#define CHAR_CMD_REG_DATA_SIZE 1024
DWORD ret;
HKEY hKey = 0;
BYTE data[CHAR_CMD_REG_DATA_SIZE];
DWORD dataSize = CHAR_CMD_REG_DATA_SIZE;
bool regIsOpen = false;
// GetLocaleInfo vars
int status;
#define LOCALE_DATA_SIZE 1024
char localeData[LOCALE_DATA_SIZE];
int localeDataSize = LOCALE_DATA_SIZE;
// Open the registry
ret = RegOpenKeyEx( HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage", 0, KEY_EXECUTE, &hKey );
if (ret) { printf("\n RegOpenKeyEx Failed"); }
else { regIsOpen = true; }
// -----------------------------------------------------
// http://blogs.msdn.com/michkap/archive/2005/02/01/364707.aspx
//
// LOCALE_USER_DEFAULT : Control Panel >> Regional and Language Options >> Regional Options [tab] >> Standards and Formats
// - is per user and does not require reboot
//
// LOCALE_SYSTEM_DEFAULT : Control Panel >> Regional and Language Options >> Advanced [tab] >> Language for non-Unicode Programs
// - is system wide and requires reboot
// - specifies the default ANSI and OEM code pages, and some of the font linking preferences
// -----------------------------------------------------
printf("\n GetConsoleCP() %d -- Note: effected by the chcp command.\n",GetConsoleCP());
printf("\n GetOEMCP() %d",GetOEMCP());
res = GetCPInfoEx( CP_OEMCP, 0, &CPInfoEx );
if (res==0) { printf("\n GetCPInfoEx Failed"); }
else { printf("\n GetCPInfoEx(CP_OEMCP) %d",CPInfoEx.CodePage); }
status = GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_IDEFAULTCODEPAGE, localeData, localeDataSize );
if (status==0) { printf("\n GetLocaleInfo Failed"); }
else { printf("\n GetLocaleInfo(LOCALE_USER_DEFAULT,OEM) %s",localeData); }
status = GetLocaleInfo( LOCALE_SYSTEM_DEFAULT, LOCALE_IDEFAULTCODEPAGE, localeData, localeDataSize );
if (status==0) { printf("\n GetLocaleInfo Failed"); }
else { printf("\n GetLocaleInfo(LOCALE_SYSTEM_DEFAULT,OEM) %s",localeData); }
if (regIsOpen)
{
dataSize = CHAR_CMD_REG_DATA_SIZE;
ret = RegQueryValueEx( hKey, "OEMCP", NULL, NULL, data, &dataSize );
if (ret) { printf("\n RegQueryValueEx Failed"); }
else { printf("\n Registry...\\CodePage\\OEMCP %s\n",(char*)data); }
}
// -----------------------------------------------------
printf("\n GetACP() %d",GetACP());
res = GetCPInfoEx( CP_ACP, 0, &CPInfoEx );
if (res==0) { printf("\n GetCPInfoEx Failed"); }
else { printf("\n GetCPInfoEx(CP_ACP) %d",CPInfoEx.CodePage); }
status = GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_IDEFAULTANSICODEPAGE, localeData, localeDataSize );
if (status==0) { printf("\n GetLocaleInfo Failed"); }
else { printf("\n GetLocaleInfo(LOCALE_USER_DEFAULT,ANSI) %s",localeData); }
status = GetLocaleInfo( LOCALE_SYSTEM_DEFAULT, LOCALE_IDEFAULTANSICODEPAGE, localeData, localeDataSize );
if (status==0) { printf("\n GetLocaleInfo Failed"); }
else { printf("\n GetLocaleInfo(LOCALE_SYSTEM_DEFAULT,ANSI) %s",localeData); }
if (regIsOpen)
{
dataSize = CHAR_CMD_REG_DATA_SIZE;
ret = RegQueryValueEx( hKey, "ACP", NULL, NULL, data, &dataSize );
if (ret) { printf("\n RegQueryValueEx Failed"); }
else { printf("\n Registry...\\CodePage\\ACP %s\n",(char*)data); }
}
// -----------------------------------------------------
char * str;
str = setlocale(LC_ALL , NULL); if (str == NULL) { printf("\n LC_ALL = unknown"); } else { printf("\n LC_ALL = %s",str); }
str = setlocale(LC_COLLATE , NULL); if (str == NULL) { printf("\n LC_COLLATE = unknown"); } else { printf("\n LC_COLLATE = %s",str); }
str = setlocale(LC_CTYPE , NULL); if (str == NULL) { printf("\n LC_CTYPE = unknown"); } else { printf("\n LC_CTYPE = %s",str); }
str = setlocale(LC_MONETARY, NULL); if (str == NULL) { printf("\n LC_MONETARY = unknown"); } else { printf("\n LC_MONETARY = %s",str); }
str = setlocale(LC_NUMERIC , NULL); if (str == NULL) { printf("\n LC_NUMERIC = unknown"); } else { printf("\n LC_NUMERIC = %s",str); }
str = setlocale(LC_TIME , NULL); if (str == NULL) { printf("\n LC_TIME = unknown"); } else { printf("\n LC_TIME = %s",str); }
printf("\n\n"
" 0 1 2 3 4 5 6 7 8 9 A B C D E F\n\n"
" 2 \x20 \x21 \x22 \x23 \x24 \x25 \x26 \x27 \x28 \x29 \x2a \x2b \x2c \x2d \x2e \x2f\n"
" 3 \x30 \x31 \x32 \x33 \x34 \x35 \x36 \x37 \x38 \x39 \x3a \x3b \x3c \x3d \x3e \x3f\n"
" 4 \x40 \x41 \x42 \x43 \x44 \x45 \x46 \x47 \x48 \x49 \x4a \x4b \x4c \x4d \x4e \x4f\n"
" 5 \x50 \x51 \x52 \x53 \x54 \x55 \x56 \x57 \x58 \x59 \x5a \x5b \x5c \x5d \x5e \x5f\n"
" 6 \x60 \x61 \x62 \x63 \x64 \x65 \x66 \x67 \x68 \x69 \x6a \x6b \x6c \x6d \x6e \x6f\n"
" 7 \x70 \x71 \x72 \x73 \x74 \x75 \x76 \x77 \x78 \x79 \x7a \x7b \x7c \x7d \x7e \x7f\n"
" 8 \x80 \x81 \x82 \x83 \x84 \x85 \x86 \x87 \x88 \x89 \x8a \x8b \x8c \x8d \x8e \x8f\n"
" 9 \x90 \x91 \x92 \x93 \x94 \x95 \x96 \x97 \x98 \x99 \x9a \x9b \x9c \x9d \x9e \x9f\n"
" a \xa0 \xa1 \xa2 \xa3 \xa4 \xa5 \xa6 \xa7 \xa8 \xa9 \xaa \xab \xac \xad \xae \xaf\n"
" b \xb0 \xb1 \xb2 \xb3 \xb4 \xb5 \xb6 \xb7 \xb8 \xb9 \xba \xbb \xbc \xbd \xbe \xbf\n"
" c \xc0 \xc1 \xc2 \xc3 \xc4 \xc5 \xc6 \xc7 \xc8 \xc9 \xca \xcb \xcc \xcd \xce \xcf\n"
" d \xd0 \xd1 \xd2 \xd3 \xd4 \xd5 \xd6 \xd7 \xd8 \xd9 \xda \xdb \xdc \xdd \xde \xdf\n"
" e \xe0 \xe1 \xe2 \xe3 \xe4 \xe5 \xe6 \xe7 \xe8 \xe9 \xea \xeb \xec \xed \xee \xef\n"
" f \xf0 \xf1 \xf2 \xf3 \xf4 \xf5 \xf6 \xf7 \xf8 \xf9 \xfa \xfb \xfc \xfd \xfe \xff\n"
"\n");
}
int main(int argc, char** argv)
{
info();
setlocale(LC_CTYPE,"");
info();
return 0;
}
referenced by
2012/01/17 The evolving Story of Locale Support, part 15: Fixing our listings up in Windows 8!
2010/09/15 How to format? What locale?
2009/07/13 Anything still wrong is probably wrong for good....
2008/06/01 Seeing through the non-English "Mesh"
2006/11/19 Another satisfied customer!
2006/09/22 Inaccurate localization can make you bust out laughing
2005/04/16 Not all keyboards are included in MSKLC's lists
2005/04/14 On approaching international programming....
2005/02/21 Give me a [word-]break!
2005/02/04 GEOID -- The LCIDs maligned little brother....
2005/02/03 English only! (or how to misuse NLS APIs)