On Windows Vista and later you can call SetThreadPreferredUILanguages
to set a list of up to 5 languages for the current thread, ordered by preference. Windows 7+ also has SetProcessPreferredUILanguages
to change this setting for the entire process.
Windows language packs provide the required Multilingual User Interface (MUI) resource libraries. For the system libraries, you'll find the MUI resources in subdirectories of System32 that are named by language. For example, if a Spanish language pack is installed, then System32\es-ES\kernel32.dll.mui
should exist. For more info on MUI, read Understanding MUI and MUI Fundamental Concepts Explained.
Here's an example ctypes definition for calling SetThreadPreferredUILanguages
:
import ctypes
from ctypes import wintypes
kernel32 = ctypes.WinDLL('kernel32')
MUI_LANGUAGE_ID = 0x004
MUI_LANGUAGE_NAME = 0x008
MUI_RESET_FILTERS = 0x001
MUI_CONSOLE_FILTER = 0x100
MUI_COMPLEX_SCRIPT_FILTER = 0x200
def errcheck_bool(result, func, args):
if not result:
raise ctypes.WinError(ctypes.get_last_error())
return args
kernel32.SetThreadPreferredUILanguages = ctypes.WINFUNCTYPE(
wintypes.BOOL,
wintypes.DWORD,
wintypes.LPCWSTR,
wintypes.PULONG,
use_last_error=True
)(('SetThreadPreferredUILanguages', kernel32),
((1, 'flags', MUI_LANGUAGE_NAME),
(1, 'languages', None),
(2, 'pulNumLanguages')))
kernel32.SetThreadPreferredUILanguages.errcheck = errcheck_bool
I defined the pulNumLanguages
parameter as an out parameter (i.e. type 2), so ctypes takes care of passing a reference to a temporary c_ulong
, and then returns the value as the high-level Python result.
The low-level BOOL
result of the C call is handled by errcheck_bool
, which potentially raises an exception based on the Windows LastError
code. To ensure the accuracy of this value, the native call is configured with use_last_error=True
, which captures the LastError
value in a thread-local storage variable. This captured error value is retrieved by calling ctypes.get_last_error
.
The list of languages is passed as a single wide-character string, with each element terminated by a nul
character (i.e. '\0'
). Using language names instead of the old language IDs is recommeneded, so I've set MUI_LANGUAGE_NAME
as the default flag value.
Example:
>>> ERROR_FILE_NOT_FOUND = 2
>>> kernel32.SetThreadPreferredUILanguages(languages='es-es\0en-us\0')
2
>>> ctypes.FormatError(ERROR_FILE_NOT_FOUND)
'El sistema no puede encontrar el archivo especificado.'
Reset:
>>> kernel32.SetThreadPreferredUILanguages(flags=0)
0
>>> ctypes.FormatError(ERROR_FILE_NOT_FOUND)
'The system cannot find the file specified.'
Unfortunately this doesn't set the language used by C runtime error messages. Generally CRT error messages are used on Windows whenever Python relies on the CRT's low-level POSIX API, e.g. _open
/ _wopen
:
>>> kernel32.SetThreadPreferredUILanguages(languages='es-es\0en-us\0')
2
>>> open('spam')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'spam'
The above ENOENT
message is hard-coded in the CRT's _sys_errlist
array. For example here's the first 10 entries of this array in Python 3.5 (using the new universal CRT):
ucrtbase = ctypes.CDLL('ucrtbase')
ucrtbase.__sys_nerr.restype = ctypes.POINTER(ctypes.c_int)
nerr = ucrtbase.__sys_nerr()[0]
ucrtbase.__sys_errlist.restype = ctypes.POINTER(ctypes.c_char_p * nerr)
errlist = ucrtbase.__sys_errlist()[0]
>>> print(*(m.decode() for m in errlist[:10]), sep='\n')
No error
Operation not permitted
No such file or directory
No such process
Interrupted function call
Input/output error
No such device or address
Arg list too long
Exec format error
Bad file descriptor
(Don't actually use ctypes for this. The above was just to show the implementation. In Python use os.strerror
to get these CRT error messages.)
In this case you'd have to handle the exception by re-raising it with a custom or machine translated error message.