Automatic Language Selection up to XP
Good Evening,
I hope this topic hasn’t already been covered in another thread; if it was, I'm sorry.
While testing a Win32 program that runs on old Windows versions, I noticed that, despite the Windows UI language being the same, the program used different language strings (loaded with the LoadString() function) on different Windows versions. As it was my own program, I decided to investigate, because the behaviour I observed is not consistent with what the resources I could find suggest.
In Win32, a language identifier consists of two fields – (primary) language ID and sublanguage ID. The (primary) language ID is used for the language in general, e.g., English, German, French… The secondary language is a regional variation, e.g. English (U.K.), German (Switzerland), French (Canada). These fields are composed such that the primary ID is the low byte and the sublang ID is the high byte. Therefore, Language English (0x09) and Sublang English (U.S.) (0x04) make 16-bit language ID 0x0409.
There are three neutral languages before Vista. Visual Studio 2003 calls them
On Windows 9x and Windows NT up to and including NT 3.51, the system language is immutable and always the same as the UI language. NT 4.0 introduced the ability to change the system language, disconnecting it from the UI language. XP renamed the system language to the “Language for non-Unicode programs”, which is what the setting is called to this day.
If no resources can be found in the appropriate language, Windows select “language neutral” languages, and, if those cannot be found, fall back to English. If this still fails, it will just pick any language. The matching order should therefore be:
I used German (UI language) versions of Windows 98, NT 3.51, NT 4.0, 2000, XP and 7 with their user language set to Portuguese (Portugal) and their system language (if mutable) set to French (France).
I assume 98 to be representative of all 9x, NT 3.51 to be representative of all versions below it and 7 (which behaved like 2000) to be representative of all versions above it (although I wouldn’t be surprised if Windows 10 changed something).
First, I made a Win32 program that sets the window title using LoadString(). It contained string tables in the following languages:
During testing, I noticed that, unlike the old Knowledge Base article specified, Windows never seemed to substitute another specific language if it couldn’t find an exact match, i.e., if Windows wanted German (Germany) but couldn’t get it, it would never select German (Switzerland) or vice-versa. According to the MSKB, one should combine a primary language with SUBLANG_NEUTRAL in the resource files (e.g. LANG_GERMAN, SUBLANG_NEUTRAL) if it should be used for all sublanguages, so I adopted this in the test program, setting the sublanguage of all the bold languages in the list to SUBLANG_NEUTRAL.
Indeed, Windows would now fall back to the appropriate Primary-Neutral combination.
I compiled several versions of the program, always removing the first language that Windows selected and then testing again, until the program selected Catalan (indicating that, at this point, Windows was just picking any language).
This is the language selection order I observed in the NT line, in a table to illustrate its evolution.
I hope this information is of use to anyone finding this thread in 20 years during a Google search for tangentially related information. I am reasonably confident now that my findings are correct, though, if you have any corrections, I am of course open to them. Also, feel free to ask any questions about this—I’ve got the stuff all set up right now, so I should be able to do further investigations relatively conveniently.
Good Night!
I hope this topic hasn’t already been covered in another thread; if it was, I'm sorry.
While testing a Win32 program that runs on old Windows versions, I noticed that, despite the Windows UI language being the same, the program used different language strings (loaded with the LoadString() function) on different Windows versions. As it was my own program, I decided to investigate, because the behaviour I observed is not consistent with what the resources I could find suggest.
How languages work in (old) Windows
Just to get us on the same page, let me quickly explain how Windows conceptualises languages.In Win32, a language identifier consists of two fields – (primary) language ID and sublanguage ID. The (primary) language ID is used for the language in general, e.g., English, German, French… The secondary language is a regional variation, e.g. English (U.K.), German (Switzerland), French (Canada). These fields are composed such that the primary ID is the low byte and the sublang ID is the high byte. Therefore, Language English (0x09) and Sublang English (U.S.) (0x04) make 16-bit language ID 0x0409.
There are three neutral languages before Vista. Visual Studio 2003 calls them
- “Neutral” (LANG_NEUTRAL, SUBLANG_NEUTRAL),
- “Neutral (Default)” aka “Process Default Language” (LANG_NEUTRAL, SUBLANG_DEFAULT), and
- “Neutral (Sys. Default)” (LANG_NEUTRAL, SUBLANG_SYS_DEFAULT).
On Windows 9x and Windows NT up to and including NT 3.51, the system language is immutable and always the same as the UI language. NT 4.0 introduced the ability to change the system language, disconnecting it from the UI language. XP renamed the system language to the “Language for non-Unicode programs”, which is what the setting is called to this day.
How language selection should work
In theory, at least according to this old MS Knowledge Base article, Windows 95 tries to match the „system language“, while NT first tries to match „the language of the calling thread“. First, they both try to find an exact match (i.e., same primary language and sublanguage), and, failing that, they try to match just the primary language. Importantly, the article explicitly states that this means that e.g., if the desired language is German (Germany), but that is unavailable, the system will match, e.g. German (Switzerland) instead.If no resources can be found in the appropriate language, Windows select “language neutral” languages, and, if those cannot be found, fall back to English. If this still fails, it will just pick any language. The matching order should therefore be:
- Calling thread (NT) / System (95) exact
- Calling thread (NT) / System (95) primary
- Language Neutral
- English
- Any
Methodology
I had to redo the experiment several times, because I initially, made some ambiguous language choices. This account should present all the relevant information while sparing you the chaos, though.I used German (UI language) versions of Windows 98, NT 3.51, NT 4.0, 2000, XP and 7 with their user language set to Portuguese (Portugal) and their system language (if mutable) set to French (France).
I assume 98 to be representative of all 9x, NT 3.51 to be representative of all versions below it and 7 (which behaved like 2000) to be representative of all versions above it (although I wouldn’t be surprised if Windows 10 changed something).
First, I made a Win32 program that sets the window title using LoadString(). It contained string tables in the following languages:
- Neutral
- Process Default Language
- Neutral (Sys. Default)
- English (U.S.)
- English (U.K.) [as a potential English-language fallback]
- German (Germany)
- German (Switzerland)
- French (France)
- French (Canada)
- Portuguese (Portugal)
- Portuguese (Brazil)
- Danish [as a completely unrelated language]
- Catalan [as a completely unrelated language]
During testing, I noticed that, unlike the old Knowledge Base article specified, Windows never seemed to substitute another specific language if it couldn’t find an exact match, i.e., if Windows wanted German (Germany) but couldn’t get it, it would never select German (Switzerland) or vice-versa. According to the MSKB, one should combine a primary language with SUBLANG_NEUTRAL in the resource files (e.g. LANG_GERMAN, SUBLANG_NEUTRAL) if it should be used for all sublanguages, so I adopted this in the test program, setting the sublanguage of all the bold languages in the list to SUBLANG_NEUTRAL.
Indeed, Windows would now fall back to the appropriate Primary-Neutral combination.
I compiled several versions of the program, always removing the first language that Windows selected and then testing again, until the program selected Catalan (indicating that, at this point, Windows was just picking any language).
How language selection actually works
This is the language selection order I observed in Windows 98:- Neutral
- Process Default Language
- Neutral (Sys. Default)
- German (Germany) [UI = system exact]
- Portuguese (Portugal) [User exact]
- Portuguese (Neutral) [User fallback]
- English (U.S.)
- Catalan [any]
This is the language selection order I observed in the NT line, in a table to illustrate its evolution.
| No. | NT 3.51 and below | NT 4.0 | 2000 and up |
|---|---|---|---|
| 1 | Neutral | Neutral | Neutral |
| 2 | German (Germany) [UI exact] | ||
| 3 | German (Neutral) [UI fallback] | ||
| 4 | Portuguese (Portugal) [User exact] | Portuguese (Portugal) [User exact] | Portuguese (Portugal) [User exact] |
| 5 | Portuguese (Neutral) [User fallback] | Portuguese (Neutral) [User fallback] | Portuguese (Neutral) [User fallback] |
| 6 | French (France) [System exact] | French (France) [System exact] | |
| 7 | French (Neutral) [System fallback] | French (Neutral) [System fallback] | |
| 8 | English (U.S.) | English (U.S.) | English (U.S.) |
| 9 | Catalan [any] | Catalan [any] | Catalan [any] |
Okay, but why are you telling me this?
A few things to note when making a Win32 application that runs on these old Windows versions are:- String tables should not use Neutral (on NT) or any of the neutral primary languages (on 9x), as they will otherwise override any other string table.
- The English string table should be English (U.S.) so that it is always selected if all other matches fail on non-English systems.
- If you want Windows 9x to prefer its UI = system language for your application, you’ll need to provide it not just with SUBLANG_NEUTRAL, but also with the appropriate sublang for the default regional variation (e.g. not just German (Neutral), but also German (Germany)).
I hope this information is of use to anyone finding this thread in 20 years during a Google search for tangentially related information. I am reasonably confident now that my findings are correct, though, if you have any corrections, I am of course open to them. Also, feel free to ask any questions about this—I’ve got the stuff all set up right now, so I should be able to do further investigations relatively conveniently.
Good Night!