patch 9.2.0867: MS-Windows: messages are not in the display language
Commit:
https://github.com/vim/vim/commit/930dccfed0617979b836dfefbbe57d5c26a6679d
Author: Hirohito Higashi <
h.eas...@gmail.com>
Date: Mon Jul 27 22:04:06 2026 +0000
patch 9.2.0867: MS-Windows: messages are not in the display language
Problem: On MS-Windows the messages use the language of the regional
format, not the language that Windows is displayed in
(Markus Nißl)
Solution: Use the display language for the messages, with the other
preferred languages as fall back. Make ":language messages"
overrule it (Hirohito Higashi).
fixes: #20855
closes: #20860
Co-Authored-By: Claude Opus 5 (1M context) <
nor...@anthropic.com>
Signed-off-by: Hirohito Higashi <
h.eas...@gmail.com>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/runtime/doc/mlang.txt b/runtime/doc/mlang.txt
index 6f0bb3fe6..0c1747eed 100644
--- a/runtime/doc/mlang.txt
+++ b/runtime/doc/mlang.txt
@@ -1,4 +1,4 @@
-*mlang.txt* For Vim version 9.2. Last change: 2026 Feb 14
+*mlang.txt* For Vim version 9.2. Last change: 2026 Jul 28
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -122,6 +122,10 @@ If you write your own translations you need to generate the .po file and
convert it to a .mo file. You need to get the source distribution and read
the file "src/po/README.txt".
+The language of the messages comes from the display language of Windows, not
+from the regional format. The other locale categories, such as the one used
+for |strftime()|, do follow the regional format.
+
To overrule the automatic choice of the language, set the $LANG variable to
the language of your choice. use "en" to disable translations. >
diff --git a/src/locale.c b/src/locale.c
index eb1845fce..0d217fd43 100644
--- a/src/locale.c
+++ b/src/locale.c
@@ -116,10 +116,12 @@ get_mess_lang(void)
p = get_locale_val(LC_MESSAGES);
# else
// This is necessary for Win32, where LC_MESSAGES is not defined and $LANG
- // may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME
- // and LC_MONETARY may be set differently for a Japanese working in the
- // US.
- p = get_locale_val(LC_COLLATE);
+ // may be set to the LCID number. $LANG holds the display language, see
+ // set_init_lang_env(). Otherwise LC_COLLATE is the best guess, LC_TIME
+ // and LC_MONETARY may be set differently for a Japanese working in the US.
+ p = mch_getenv((char_u *)"LANG");
+ if (!is_valid_mess_lang(p))
+ p = get_locale_val(LC_COLLATE);
# endif
# else
p = mch_getenv((char_u *)"LC_ALL");
@@ -365,9 +367,6 @@ ex_language(exarg_T *eap)
if (what == LC_ALL)
{
vim_setenv((char_u *)"LANG", name);
-
- // Clear $LANGUAGE because GNU gettext uses it.
- vim_setenv((char_u *)"LANGUAGE", (char_u *)"");
# ifdef MSWIN
// Apparently MS-Windows printf() may cause a crash when
// we give it 8-bit text while it's expecting text in the
@@ -383,6 +382,8 @@ ex_language(exarg_T *eap)
# else
mname = name;
# endif
+ // Clear $LANGUAGE because GNU gettext uses it.
+ vim_setenv((char_u *)"LANGUAGE", (char_u *)"");
vim_setenv((char_u *)"LC_MESSAGES", mname);
# ifdef FEAT_MULTI_LANG
set_helplang_default(mname);
diff --git a/src/option.c b/src/option.c
index d4f63203d..f511b8ea5 100644
--- a/src/option.c
+++ b/src/option.c
@@ -494,6 +494,64 @@ set_init_expand_env(void)
}
}
+#if defined(MSWIN) && defined(FEAT_GETTEXT)
+/*
+ * Get the display language of Windows and the languages to fall back on, as a
+ * colon separated list for gettext, e.g. "ja_JP:en_US". The list stops after
+ * English, untranslated messages are English already.
+ * Returns NULL when it cannot be obtained. The result must be freed.
+ */
+ static char_u *
+get_ui_langs(void)
+{
+ ULONG num_languages = 0;
+ ULONG bufsize = 0;
+ WCHAR *buffer;
+ char_u *langs = NULL;
+
+ if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &num_languages, NULL,
+ &bufsize)
+ || bufsize == 0)
+ return NULL;
+
+ buffer = ALLOC_MULT(WCHAR, bufsize);
+ if (buffer == NULL)
+ return NULL;
+
+ if (GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &num_languages, buffer,
+ &bufsize))
+ {
+ // The list is NUL separated, the result needs the same room.
+ langs = alloc(bufsize);
+ if (langs != NULL)
+ {
+ char_u *d = langs;
+ WCHAR *s = buffer;
+
+ while (*s != L'
+ {
+ bool english = s[0] == L'e' && s[1] == L'n'
+ && (s[2] == L'
+
+ if (d > langs)
+ *d++ = ':';
+ // Locale names are ASCII, "en-US" becomes "en_US".
+ for ( ; *s != L'
+ *d++ = *s == L'-' ? '_' : (char_u)*s;
+ ++s;
+
+ if (english)
+ break;
+ }
+ *d = NUL;
+ }
+ }
+ vim_free(buffer);
+
+ return langs;
+}
+#endif
+
/*
* Initialize the 'LANG' environment variable to a default value.
*/
@@ -501,32 +559,27 @@ set_init_expand_env(void)
set_init_lang_env(void)
{
#if defined(MSWIN) && defined(FEAT_GETTEXT)
- // If $LANG isn't set, try to get a good value for it. This makes the
- // right language be used automatically. Don't do this for English.
- if (mch_getenv((char_u *)"LANG") == NULL)
- {
- char buf[20];
- long_u n;
-
- // Could use LOCALE_SISO639LANGNAME, but it's not in Win95.
- // LOCALE_SABBREVLANGNAME gives us three letters, like "enu", we use
- // only the first two.
- n = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVLANGNAME,
- (LPTSTR)buf, 20);
- if (n >= 2 && STRNICMP(buf, "en", 2) != 0)
- {
- // There are a few exceptions (probably more)
- if (STRNICMP(buf, "cht", 3) == 0 || STRNICMP(buf, "zht", 3) == 0)
- STRCPY(buf, "zh_TW");
- else if (STRNICMP(buf, "chs", 3) == 0
- || STRNICMP(buf, "zhc", 3) == 0)
- STRCPY(buf, "zh_CN");
- else if (STRNICMP(buf, "jp", 2) == 0)
- STRCPY(buf, "ja");
- else
- buf[2] = NUL; // truncate to two-letter code
- vim_setenv((char_u *)"LANG", (char_u *)buf);
+ // If the language isn't set in the environment, use the display language
+ // of Windows. Not the regional format, which is what the CRT would use
+ // for setlocale(LC_ALL, "").
+ if (mch_getenv((char_u *)"LANG") == NULL
+ && mch_getenv((char_u *)"LANGUAGE") == NULL
+ && mch_getenv((char_u *)"LC_ALL") == NULL
+ && mch_getenv((char_u *)"LC_MESSAGES") == NULL)
+ {
+ char_u *langs = get_ui_langs();
+
+ if (langs != NULL && *langs != NUL)
+ {
+ char_u *colon = vim_strchr(langs, ':');
+
+ // $LANGUAGE is the list gettext picks from, $LANG the language.
+ vim_setenv((char_u *)"LANGUAGE", langs);
+ if (colon != NULL)
+ *colon = NUL;
+ vim_setenv((char_u *)"LANG", langs);
}
+ vim_free(langs);
}
#elif defined(MACOS_CONVERT)
// Moved to os_mac_conv.c to avoid dependency problems.
diff --git a/src/version.c b/src/version.c
index 48389e931..af42c7cbd 100644
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 867,
/**/
866,
/**/