#11594: Fix wxLocale::GetSystemLanguage() and enhance it for Vista and later
---------------------+-------------------------------
Reporter: nielsm | Owner:
Type: defect | Status: confirmed
Priority: normal | Milestone:
Component: wxMSW | Version: 2.8.10
Resolution: | Keywords: wxLocale i18n MUI
Blocked By: | Blocking:
Patch: 0 |
---------------------+-------------------------------
Changes (by kgschlosser):
* cc: drschlosser@… (added)
Comment:
Since this problem is already reported I figured I would put some code in
to be able to replicate the issue at hand. and give explicit instruction
on how to replicate.
the code below is in Python and will require wxPython to be used. the
issue actually lies in wxWidgets \src\common\intl.cpp line 789
What I have personally tested and does not work properly
Windows Versions: 7, 10
Python Versions: 2.7, 3.5
wxPython Versions: 3.0.2, 4.0.3
{{{
from __future__ import print_function
import wx
import ctypes
import locale
from ctypes.wintypes import (
DWORD,
WORD,
INT,
WCHAR,
HANDLE
)
LOCALE_NAME_MAX_LENGTH = 85
LOCALE_INVARIANT = 0x007F
LOCALE_USER_DEFAULT =- 0x0400
LOCALE_SYSTEM_DEFAULT = 0x0800
KL_NAMELENGTH = 9
kernel32 = ctypes.windll.Kernel32
user32 = ctypes.windll.User32
if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p):
ULONG_PTR = ctypes.c_ulong
elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p):
ULONG_PTR = ctypes.c_ulonglong
else:
ULONG_PTR =ctypes. c_ulong
LCID = DWORD
LANGID = WORD
GEOTYPE = DWORD
HKL = HANDLE
DWORD_PTR = ULONG_PTR
PWSTR = ctypes.POINTER(WCHAR)
# LCID GetUserDefaultLCID();
GetUserDefaultLCID = kernel32.GetUserDefaultLCID
GetUserDefaultLCID.restype = LCID
user_default_lcid = GetUserDefaultLCID()
print('kernel32.GetUserDefaultLCID:', user_default_lcid)
if user_default_lcid in locale.windows_locale:
print(
'kernel32.GetUserDefaultLCID canonical name:',
locale.windows_locale[user_default_lcid]
)
else:
print('kernel32.GetUserDefaultLCID canonical name: None')
# LCID GetSystemDefaultLCID();
GetSystemDefaultLCID = kernel32.GetSystemDefaultLCID
GetSystemDefaultLCID.restype = LCID
system_default_lcid = GetSystemDefaultLCID()
print()
print('kernel32.GetSystemDefaultLCID:', system_default_lcid)
if system_default_lcid in locale.windows_locale:
print(
'kernel32.GetSystemDefaultLCID canonical name:',
locale.windows_locale[system_default_lcid]
)
else:
print('kernel32.GetSystemDefaultLCID canonical name: None')
# int GetUserDefaultLocaleName(
# LPWSTR lpLocaleName,
# int cchLocaleName
# );
GetUserDefaultLocaleName = kernel32.GetUserDefaultLocaleName
GetUserDefaultLocaleName.restype = INT
user_locale_name = (WCHAR * LOCALE_NAME_MAX_LENGTH)()
GetUserDefaultLocaleName(
ctypes.byref(user_locale_name),
LOCALE_NAME_MAX_LENGTH
)
print()
print('kernel32.GetUserDefaultLocaleName:', user_locale_name.value)
# int GetSystemDefaultLocaleName(
# LPWSTR lpLocaleName,
# int cchLocaleName
# );
GetSystemDefaultLocaleName = kernel32.GetSystemDefaultLocaleName
GetSystemDefaultLocaleName.restype = INT
system_locale_name = (WCHAR * LOCALE_NAME_MAX_LENGTH)()
GetSystemDefaultLocaleName(
ctypes.byref(system_locale_name),
LOCALE_NAME_MAX_LENGTH
)
print('kernel32.GetSystemDefaultLocaleName:', system_locale_name.value)
# LANGID GetUserDefaultUILanguage();
GetUserDefaultUILanguage = kernel32.GetUserDefaultUILanguage
GetUserDefaultUILanguage.restype = LANGID
user_default_ui_language = GetUserDefaultUILanguage()
print()
print('kernel32.GetUserDefaultUILanguage:', user_default_ui_language)
if user_default_ui_language in locale.windows_locale:
print(
'kernel32.GetUserDefaultUILanguage canonical name:',
locale.windows_locale[user_default_ui_language]
)
else:
print('kernel32.GetUserDefaultUILanguage canonical name: None')
print()
# LANGID GetSystemDefaultUILanguage();
GetSystemDefaultUILanguage = kernel32.GetSystemDefaultUILanguage
GetSystemDefaultUILanguage.restype = LANGID
system_default_ui_language = GetSystemDefaultUILanguage()
print('kernel32.GetSystemDefaultUILanguage:', system_default_ui_language)
if system_default_ui_language in locale.windows_locale:
print(
'kernel32.GetSystemDefaultUILanguage canonical name:',
locale.windows_locale[system_default_ui_language]
)
else:
print('kernel32.GetSystemDefaultUILanguage canonical name: None')
# LANGID GetUserDefaultLangID();
GetUserDefaultLangID = kernel32.GetUserDefaultLangID
GetUserDefaultLangID.restype = LANGID
user_default_langid = GetUserDefaultLangID()
print()
print('kernel32.GetUserDefaultLangID:', user_default_langid)
if user_default_langid in locale.windows_locale:
print(
'kernel32.GetUserDefaultLangID canonical name:',
locale.windows_locale[user_default_langid]
)
else:
print('kernel32.GetUserDefaultLangID canonical name: None')
# LANGID GetSystemDefaultLangID();
GetSystemDefaultLangID = kernel32.GetSystemDefaultLangID
GetSystemDefaultLangID.restype = LANGID
system_default_langid = GetSystemDefaultLangID()
print()
print('kernel32.GetSystemDefaultLangID:', system_default_langid)
if system_default_langid in locale.windows_locale:
print(
'kernel32.GetSystemDefaultLangID canonical name:',
locale.windows_locale[system_default_langid]
)
else:
print('kernel32.GetSystemDefaultLangID canonical name: None')
# HKL GetKeyboardLayout(
# DWORD idThread
# );
GetKeyboardLayout = user32.GetKeyboardLayout
GetKeyboardLayout.restype = HKL
def LOWORD(l):
return WORD(DWORD_PTR(l).value & 0xffff)
keyboard_layout = GetKeyboardLayout(DWORD(0))
keyboard_langid = LANGID(LOWORD(keyboard_layout).value)
print()
print('user32.GetKeyboardLayout:', keyboard_langid.value)
# BOOL GetKeyboardLayoutNameW(
# LPWSTR pwszKLID
# );
if keyboard_langid.value in locale.windows_locale:
print(
'user32.GetKeyboardLayout canonical name:',
locale.windows_locale[keyboard_langid.value]
)
else:
print('user32.GetKeyboardLayout canonical name: None')
wx_default = wx.Locale.GetSystemLanguage()
print('\n')
print('wx.Locale.GetSystemLanguage: ' + str(wx_default))
lang_info = wx.Locale.GetLanguageInfo(wx_default)
print('wx.LanguageInfo.LocaleName:', lang_info.GetLocaleName())
print('wx.LanguageInfo.CanonicalName:', lang_info.CanonicalName)
print('wx.LanguageInfo.Description:', lang_info.Description)
print('wx.LanguageInfo.Language:', lang_info.Language)
}}}
The code above leverages ctypes to access the Windows specific API calls
to grab the language. Windows has a very complex language/locale setup. I
believe this is due to decades of adding and patching.
In order to replicate the issue.
Start -> Control Panel -> Region and Language
Keyboards and Languages tab. Display langiage section. Install and enable
English (United States).
Formats tab. Format drop down. Select English (United States)
Administrative tab. (Now here is something very misleading) Change System
Locale button. Set to English (United States). This may require a reboot.
Click the apply button. Run the script above the output will be
{{{
user default: 1033
system default: 1033
wx default: 60
user default canonical name: en_US
system default canonical name: en_US
wx default canonical name: en_US
}}}
Now repeat the steps above making only one change. Change the format
to Hebrew (Israel). Take note this is not changing the the displayed
language
It is changing the language format which would be display of date/time,
currency things of that nature. Which as best as i figure would be the
locale.
rerun the script and the output will be
{{{
user default: 1033
system default: 1033
wx default: 100
user default canonical name: en_US
system default canonical name: en_US
wx default canonical name: he_IL
}}}
now if you notice the returned languages from the Windows API has not
changed at all. But the language code from wxWidgets has. This is
incorrect behavior
In Windows You have the ability to set the locale, displayed language,
keyboard layout and finally the system language all separately. this adds
quite a bit of complexity in deciding what to use in order to display the
GUI components.
wxLocale.GetSystemLanguage is incorrect in 2 ways. first is it does not
return the system language it actually returns the user locale. second is
it should not return anything user based it should return the system
language as the method name implies. But with respect to returning the
default system language this is not something that should be needed. it is
for use in non unicode applications as well as being the System "install"
language, the defaulted language that windows uses when it is getting
installed. It can be change via the Administrative tab and clicking on the
Change System Locale button. and the button being labeled as such is very
misleading because not only does setting that change the system locale.
but it also changes the system language.
Now I specifically used the languages above for a reason. if you set
wxLocale
using wx.Locale(wx.Locale.GetSystemLanguage()) and you have the format
field set to Hebrew (Israel) the GUI gets completely flipped. including
the written text gets displayed right to left. English (United States) is
not a language that gets read from right to left and should not be
displayed in this manner. But the whole GUI is actually flipped
horizontally including the close and min/max buttons in the caption bar.
Also if you open a dialog setting the current frame as a parent the
displayed text in the dialog is in Hebrew and not the default user
displayed language.
I can submit a patch for repairing the single method. But it appears that
possibly other parts of wxWidgets might possibly use that method. I am
unfamiliar with the code and it would take me a long while to locate any
potential issues. As a quick and dirty band-aide I have build a cross
reference from Windows LANDID's to wx.LANGUAGE_* codes. Use the ctypes
Windows API call to GetUserDefaultUILanguage to get the LANGID feed it
into the cross reference and use that output to set the language properly.
I believe the whole of wxLocale as well as wxLanguageInfo needs a code
rewrite. I feel this should be done because when dealing with Windows the
locale, displayed language, keyboard layout are separate things and using
the locale to set the language is incorrect. and using the language to set
the locale is also incorrect. I do not know how other operating systems
function in respect to this.
The script above has the methods that can be used in determining the
keyboard/language/locale.
Thank You for taking the time to go over this problem.
--
Ticket URL: <
https://trac.wxwidgets.org/ticket/11594#comment:3>