How to set python exception messages to use a special language?
Question: I'd like to change the way python is handling the language of the returned
error strings, for example for WinError/OSError exception. I'm working with
ctypes and WinError is defined as

    
    
    def WinError(code=None, descr=None):
        if code is None:
            code = GetLastError()
        if descr is None:
            descr = FormatError(code).strip()
        return OSError(None, descr, None, code)
    

The FormatError function is extracted from ..\Python34\DLLs_ctypes.pyd and is
the python version of C++ FormatMessage function.

    
    
    DWORD WINAPI FormatMessage(
      _In_     DWORD   dwFlags,
      _In_opt_ LPCVOID lpSource,
      _In_     DWORD   dwMessageId,
      _In_     DWORD   dwLanguageId,
      _Out_    LPTSTR  lpBuffer,
      _In_     DWORD   nSize,
      _In_opt_ va_list *Arguments
    );
    

Ideally the python equivalent should have the same parameters, but FormatError
can only have one parameter and this is FormatError([code]). I found the
source code of ctypes written in c++. There is a file called callproc.c and
there is the FormatError function defined as

    
    
    static TCHAR *FormatError(DWORD code)
    {
        TCHAR *lpMsgBuf;
        DWORD n;
        n = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                  NULL,
                  code,
                  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
                  (LPTSTR) &lpMsgBuf,
                  0,
                  NULL);
        if (n) {
            while (isspace(lpMsgBuf[n-1]))
                --n;
            lpMsgBuf[n] = '\0'; /* rstrip() */
        }
        return lpMsgBuf;
    }
    

LANG_NEUTRAL|SUBLANG_DEFAULT = fallback to user's default language.

Is there a way to control the language of the error strings, perhaps by
setting the locale, an environmental variable or something else?

Thanks in advance!

Edit: I think i found something interesting, but i will test it later, cause
i'm really sleepy. This should work or not?
<https://gist.github.com/EBNull/6135237>


Answer: On Windows Vista and later you can call
[`SetThreadPreferredUILanguages`](https://msdn.microsoft.com/en-
us/library/dd374052) 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](https://msdn.microsoft.com/en-us/goglobal/dd218459) and [MUI Fundamental
Concepts Explained](https://msdn.microsoft.com/en-us/goglobal/dd218460).

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](https://msdn.microsoft.com/en-us/library/dd318696) 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.

