From e6c6d65b1a500362a97fc9b72189bf72a724f686 Mon Sep 17 00:00:00 2001
From: overpathz <overpathz@gmail.com>
Date: Thu, 1 Dec 2022 22:45:15 +0200
Subject: [PATCH 1/2] Provide multilingual support of
 Kernel32Util.formatMessage()

- provided multilingual support of Kernel32Util methods using Kernel32.INSTANCE.FormatMessage()
- overloaded methods
- test is written
---
 .../sun/jna/platform/win32/Kernel32Util.java  | 72 +++++++++++++++++--
 .../jna/platform/win32/Kernel32UtilTest.java  | 12 ++++
 2 files changed, 78 insertions(+), 6 deletions(-)

diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java
index 9654fc52b1..db9373a361 100644
--- a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java
+++ b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java
@@ -178,14 +178,35 @@ public static void closeHandle(HANDLE h) {
         }
     }
 
+    /**
+     * Format a message from a code.
+     *
+     * @param code The error code
+     * @return Formatted message in the default locale.
+     */
+    public static String formatMessage(int code) {
+        return formatMessage(code, 0, 0);
+    }
+
     /**
      * Format a message from the value obtained from
      * {@link Kernel32#GetLastError()} or {@link Native#getLastError()}.
      *
+     * <p>If you pass in zero, FormatMessage looks for a message for LANGIDs in the following order:</p>
+     * <ol>
+     *   <li>Language neutral</li>
+     *   <li>Thread LANGID, based on the thread's locale value</li>
+     *   <li>User default LANGID, based on the user's default locale value</li>
+     *   <li>System default LANGID, based on the system default locale value</li>
+     *   <li>US English</li>
+     * </ol>
+     *
      * @param code The error code
-     * @return Formatted message.
+     * @param primaryLangId The primary language identifier
+     * @param sublangId The sublanguage identifier
+     * @return Formatted message in the specified locale.
      */
-    public static String formatMessage(int code) {
+    public static String formatMessage(int code, int primaryLangId, int sublangId) {
         PointerByReference buffer = new PointerByReference();
         int nLen = Kernel32.INSTANCE.FormatMessage(
                 WinBase.FORMAT_MESSAGE_ALLOCATE_BUFFER
@@ -193,7 +214,7 @@ public static String formatMessage(int code) {
                 | WinBase.FORMAT_MESSAGE_IGNORE_INSERTS,
                 null,
                 code,
-                0, // TODO: // MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT)
+                WinNT.LocaleMacros.MAKELANGID(primaryLangId, sublangId),
                 buffer, 0, null);
         if (nLen == 0) {
             throw new LastErrorException(Native.getLastError());
@@ -213,32 +234,71 @@ public static String formatMessage(int code) {
      *
      * @param code
      *            HRESULT
-     * @return Formatted message.
+     * @return Formatted message in the default locale.
      */
     public static String formatMessage(HRESULT code) {
         return formatMessage(code.intValue());
     }
 
+    /**
+     * Format a message from an HRESULT.
+     *
+     * @param code
+     *            HRESULT
+     * @param primaryLangId
+     *            The primary language identifier
+     * @param sublangId
+     *            The primary language identifier
+     * @return Formatted message in the specified locale.
+     */
+    public static String formatMessage(HRESULT code, int primaryLangId, int sublangId) {
+        return formatMessage(code.intValue(), primaryLangId, sublangId);
+    }
+
     /**
      * Format a system message from an error code.
      *
      * @param code
      *            Error code, typically a result of GetLastError.
-     * @return Formatted message.
+     * @return Formatted message in the default locale.
      */
     public static String formatMessageFromLastErrorCode(int code) {
         return formatMessage(W32Errors.HRESULT_FROM_WIN32(code));
     }
 
+    /**
+     * Format a system message from an error code.
+     *
+     * @param code
+     *            Error code, typically a result of GetLastError.
+     * @param primaryLangId
+     *            The primary language identifier
+     * @param sublangId
+     *            The primary language identifier
+     * @return Formatted message in the specified locale.
+     */
+    public static String formatMessageFromLastErrorCode(int code, int primaryLangId, int sublangId) {
+        return formatMessage(W32Errors.HRESULT_FROM_WIN32(code), primaryLangId, sublangId);
+    }
+
     /**
      * @return Obtains the human-readable error message text from the last error
-     *         that occurred by invocating {@code Kernel32.GetLastError()}.
+     *         that occurred by invocating {@code Kernel32.GetLastError()} in the default locale.
      */
     public static String getLastErrorMessage() {
         return Kernel32Util.formatMessageFromLastErrorCode(Kernel32.INSTANCE
                 .GetLastError());
     }
 
+    /**
+     * @return Obtains the human-readable error message text from the last error
+     *         that occurred by invocating {@code Kernel32.GetLastError()} in the specified locale.
+     */
+    public static String getLastErrorMessage(int primaryLangId, int sublangId) {
+        return Kernel32Util.formatMessageFromLastErrorCode(Kernel32.INSTANCE
+                .GetLastError(), primaryLangId, sublangId);
+    }
+
     /**
      * Return the path designated for temporary files.
      *
diff --git a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java
index cd96bfbee1..cdb5c373f4 100644
--- a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java
+++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java
@@ -159,6 +159,18 @@ public void testFormatMessageFromHR() {
         }
     }
 
+    public void testFormatMessageFromErrorCodeWithNonEnglishLocale() {
+        int errorCode = W32Errors.S_OK.intValue();
+        String formattedMsgInDefaultLocale = Kernel32Util.formatMessage(errorCode);
+        // primary and sub languages id's of the english locale, because it is present on most machines
+        String formattedMsgInEnglishLocale = Kernel32Util.formatMessage(errorCode, 9, 1);
+        if(AbstractWin32TestSupport.isEnglishLocale) {
+            assertEquals(formattedMsgInDefaultLocale, formattedMsgInEnglishLocale);
+        } else {
+            assertNotSame(formattedMsgInDefaultLocale, formattedMsgInEnglishLocale);
+        }
+    }
+
     public void testGetTempPath() {
         assertTrue(Kernel32Util.getTempPath().length() > 0);
     }

From daf3ed2748ec3b29dc74eea3938aefd6a8f64b11 Mon Sep 17 00:00:00 2001
From: Oleksandr Klymenko <overpathz@gmail.com>
Date: Thu, 1 Dec 2022 22:45:15 +0200
Subject: [PATCH 2/2] Provide multilingual support of
 Kernel32Util.formatMessage()

- provided multilingual support of Kernel32Util methods using Kernel32.INSTANCE.FormatMessage()
- overloaded methods
- test is written
- updated CHANGES.md
---
 CHANGES.md                                    |  1 +
 .../sun/jna/platform/win32/Kernel32Util.java  | 72 +++++++++++++++++--
 .../jna/platform/win32/Kernel32UtilTest.java  | 12 ++++
 3 files changed, 79 insertions(+), 6 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 7c10f121f6..58a5e05999 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -11,6 +11,7 @@ Features
 * [#1459](https://github.com/java-native-access/jna/pull/1459): Add `VirtualLock` and `VirtualUnlock` in `c.s.j.p.win32.Kernel32` - [@matthiasblaesing](https://github.com/matthiasblaesing).
 * [#1471](https://github.com/java-native-access/jna/pull/1471): Add `c.s.j.p.win32.Advapi32Util#isCurrentProcessElevated` and associated Types - [@dbwiddis](https://github.com/dbwiddis).
 * [#1474](https://github.com/java-native-access/jna/pull/1474): Add `c.s.j.p.win32.WbemCli#IWbemClassObject.IWbemQualifierSet`, `IWbemServices.GetObject`, `IWbemContext.SetValue` and associated methods - [@rchateauneu](https://github.com/rchateauneu).
+* [#1482](https://github.com/java-native-access/jna/pull/1482): Add multilingual support of `Kernel32Util.formatMessage` - [@overpathz](https://github.com/overpathz).
 
 Bug Fixes
 ---------
diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java
index 9654fc52b1..db9373a361 100644
--- a/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java
+++ b/contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java
@@ -178,14 +178,35 @@ public static void closeHandle(HANDLE h) {
         }
     }
 
+    /**
+     * Format a message from a code.
+     *
+     * @param code The error code
+     * @return Formatted message in the default locale.
+     */
+    public static String formatMessage(int code) {
+        return formatMessage(code, 0, 0);
+    }
+
     /**
      * Format a message from the value obtained from
      * {@link Kernel32#GetLastError()} or {@link Native#getLastError()}.
      *
+     * <p>If you pass in zero, FormatMessage looks for a message for LANGIDs in the following order:</p>
+     * <ol>
+     *   <li>Language neutral</li>
+     *   <li>Thread LANGID, based on the thread's locale value</li>
+     *   <li>User default LANGID, based on the user's default locale value</li>
+     *   <li>System default LANGID, based on the system default locale value</li>
+     *   <li>US English</li>
+     * </ol>
+     *
      * @param code The error code
-     * @return Formatted message.
+     * @param primaryLangId The primary language identifier
+     * @param sublangId The sublanguage identifier
+     * @return Formatted message in the specified locale.
      */
-    public static String formatMessage(int code) {
+    public static String formatMessage(int code, int primaryLangId, int sublangId) {
         PointerByReference buffer = new PointerByReference();
         int nLen = Kernel32.INSTANCE.FormatMessage(
                 WinBase.FORMAT_MESSAGE_ALLOCATE_BUFFER
@@ -193,7 +214,7 @@ public static String formatMessage(int code) {
                 | WinBase.FORMAT_MESSAGE_IGNORE_INSERTS,
                 null,
                 code,
-                0, // TODO: // MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT)
+                WinNT.LocaleMacros.MAKELANGID(primaryLangId, sublangId),
                 buffer, 0, null);
         if (nLen == 0) {
             throw new LastErrorException(Native.getLastError());
@@ -213,32 +234,71 @@ public static String formatMessage(int code) {
      *
      * @param code
      *            HRESULT
-     * @return Formatted message.
+     * @return Formatted message in the default locale.
      */
     public static String formatMessage(HRESULT code) {
         return formatMessage(code.intValue());
     }
 
+    /**
+     * Format a message from an HRESULT.
+     *
+     * @param code
+     *            HRESULT
+     * @param primaryLangId
+     *            The primary language identifier
+     * @param sublangId
+     *            The primary language identifier
+     * @return Formatted message in the specified locale.
+     */
+    public static String formatMessage(HRESULT code, int primaryLangId, int sublangId) {
+        return formatMessage(code.intValue(), primaryLangId, sublangId);
+    }
+
     /**
      * Format a system message from an error code.
      *
      * @param code
      *            Error code, typically a result of GetLastError.
-     * @return Formatted message.
+     * @return Formatted message in the default locale.
      */
     public static String formatMessageFromLastErrorCode(int code) {
         return formatMessage(W32Errors.HRESULT_FROM_WIN32(code));
     }
 
+    /**
+     * Format a system message from an error code.
+     *
+     * @param code
+     *            Error code, typically a result of GetLastError.
+     * @param primaryLangId
+     *            The primary language identifier
+     * @param sublangId
+     *            The primary language identifier
+     * @return Formatted message in the specified locale.
+     */
+    public static String formatMessageFromLastErrorCode(int code, int primaryLangId, int sublangId) {
+        return formatMessage(W32Errors.HRESULT_FROM_WIN32(code), primaryLangId, sublangId);
+    }
+
     /**
      * @return Obtains the human-readable error message text from the last error
-     *         that occurred by invocating {@code Kernel32.GetLastError()}.
+     *         that occurred by invocating {@code Kernel32.GetLastError()} in the default locale.
      */
     public static String getLastErrorMessage() {
         return Kernel32Util.formatMessageFromLastErrorCode(Kernel32.INSTANCE
                 .GetLastError());
     }
 
+    /**
+     * @return Obtains the human-readable error message text from the last error
+     *         that occurred by invocating {@code Kernel32.GetLastError()} in the specified locale.
+     */
+    public static String getLastErrorMessage(int primaryLangId, int sublangId) {
+        return Kernel32Util.formatMessageFromLastErrorCode(Kernel32.INSTANCE
+                .GetLastError(), primaryLangId, sublangId);
+    }
+
     /**
      * Return the path designated for temporary files.
      *
diff --git a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java
index cd96bfbee1..cdb5c373f4 100644
--- a/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java
+++ b/contrib/platform/test/com/sun/jna/platform/win32/Kernel32UtilTest.java
@@ -159,6 +159,18 @@ public void testFormatMessageFromHR() {
         }
     }
 
+    public void testFormatMessageFromErrorCodeWithNonEnglishLocale() {
+        int errorCode = W32Errors.S_OK.intValue();
+        String formattedMsgInDefaultLocale = Kernel32Util.formatMessage(errorCode);
+        // primary and sub languages id's of the english locale, because it is present on most machines
+        String formattedMsgInEnglishLocale = Kernel32Util.formatMessage(errorCode, 9, 1);
+        if(AbstractWin32TestSupport.isEnglishLocale) {
+            assertEquals(formattedMsgInDefaultLocale, formattedMsgInEnglishLocale);
+        } else {
+            assertNotSame(formattedMsgInDefaultLocale, formattedMsgInEnglishLocale);
+        }
+    }
+
     public void testGetTempPath() {
         assertTrue(Kernel32Util.getTempPath().length() > 0);
     }