diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index b9e01b6..10eb0ab 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -220,6 +220,7 @@ pg_perm_setlocale(int category, const char *locale) result = IsoLocaleName(locale); if (result == NULL) result = (char *) locale; + elog(DEBUG3, "IsoLocaleName() executed; locale: \"%s\"", result); #endif /* WIN32 */ break; #endif /* LC_MESSAGES */ @@ -953,25 +954,106 @@ cache_locale_time(void) * string. Furthermore, msvcr110.dll changed the undocumented _locale_t * content to carry locale names instead of locale identifiers. * - * MinGW headers declare _create_locale(), but msvcrt.dll lacks that symbol. - * IsoLocaleName() always fails in a MinGW-built postgres.exe, so only - * Unix-style values of the lc_messages GUC can elicit localized messages. In - * particular, every lc_messages setting that initdb can select automatically - * will yield only C-locale messages. XXX This could be fixed by running the - * fully-qualified locale name through a lookup table. + * Visual Studio 2015 should still be able to do the same as Visual Studio + * 2012, but the declaration of locale_name is missing in _locale_t, causing + * this code compilation to fail, hence this falls back instead on to + * enumerating all system locales by using EnumSystemLocalesEx to find the + * required loacle name. If the input argument is in Unix-style then we can + * get ISO Locale name directly by using GetLocaleInfoEx() with LCType as + * LOCALE_SNAME. + * + * MinGW headers declare _create_locale(), but msvcrt.dll lacks that symbol in + * releases before Windows 8. IsoLocaleName() always fails in a MinGW-built + * postgres.exe, so only Unix-style values of the lc_messages GUC can elicit + * localized messages. In particular, every lc_messages setting that initdb + * can select automatically will yield only C-locale messages. XXX This could + * be fixed by running the fully-qualified locale name through a lookup table. * * This function returns a pointer to a static buffer bearing the converted * name or NULL if conversion fails. * - * [1] http://msdn.microsoft.com/en-us/library/windows/desktop/dd373763.aspx - * [2] http://msdn.microsoft.com/en-us/library/windows/desktop/dd373814.aspx + * [1] https://docs.microsoft.com/en-us/windows/win32/intl/locale-identifiers + * [2] https://docs.microsoft.com/en-us/windows/win32/intl/locale-names */ + +#if _MSC_VER >= 1900 +/* + * Callback function for EnumSystemLocalesEx() in IsoLocaleName(). + * + * This search is done by enumerating all system locales, trying to match a + * locale with the format: [_], e.g. English[_United States] + * + * The input is a three wchar_t array as an LPARAM. The first element is the + * locale_name we want to match, the second element is an allocated buffer + * where the Unix-style locale is copied if a match is found, and the third + * element is the search status, 1 if a match was found, 0 otherwise. + */ +static BOOL CALLBACK +search_locale_enum(LPWSTR pStr, DWORD dwFlags, LPARAM lparam) +{ + wchar_t test_locale[LOCALE_NAME_MAX_LENGTH]; + wchar_t **argv; + + (void) (dwFlags); + + argv = (wchar_t **) lparam; + *argv[2] = (wchar_t) 0; + + memset(test_locale, 0, sizeof(test_locale)); + + /* Get the name of the in English */ + if (GetLocaleInfoEx(pStr, LOCALE_SENGLISHLANGUAGENAME, + test_locale, LOCALE_NAME_MAX_LENGTH)) + { + /* + * If the enumerated locale does not have a hyphen ("en") OR the + * lc_message input does not have an underscore ("English"), we only + * need to compare the tags. + */ + if (wcsrchr(pStr, '-') == NULL || wcsrchr(argv[0], '_') == NULL) + { + if (_wcsicmp(argv[0], test_locale) == 0) + { + wcscpy(argv[1], pStr); + *argv[2] = (wchar_t) 1; + return FALSE; + } + } + + /* + * We have to compare a full _ tag, so we append + * the underscore and name of the country/region in English, e.g. + * "English_United States". + */ + else + { + size_t len; + + wcscat(test_locale, L"_"); + len = wcslen(test_locale); + if (GetLocaleInfoEx(pStr, LOCALE_SENGLISHCOUNTRYNAME, + test_locale + len, + LOCALE_NAME_MAX_LENGTH - len)) + { + if (_wcsicmp(argv[0], test_locale) == 0) + { + wcscpy(argv[1], pStr); + *argv[2] = (wchar_t) 1; + return FALSE; + } + } + } + } + + return TRUE; +} +#endif /* _MSC_VER >= 1900 */ + static char * IsoLocaleName(const char *winlocname) { #if (_MSC_VER >= 1400) /* VC8.0 or later */ - static char iso_lc_messages[32]; - _locale_t loct = NULL; + static char iso_lc_messages[LOCALE_NAME_MAX_LENGTH]; if (pg_strcasecmp("c", winlocname) == 0 || pg_strcasecmp("posix", winlocname) == 0) @@ -979,18 +1061,74 @@ IsoLocaleName(const char *winlocname) strcpy(iso_lc_messages, "C"); return iso_lc_messages; } - - loct = _create_locale(LC_CTYPE, winlocname); - if (loct != NULL) + else { #if (_MSC_VER >= 1700) /* Visual Studio 2012 or later */ - size_t rc; + size_t rc = -1; char *hyphen; +#if (_MSC_VER >= 1900) /* Visual Studio 2015 or later */ + wchar_t wc_locale_name[LOCALE_NAME_MAX_LENGTH]; + wchar_t buffer[LOCALE_NAME_MAX_LENGTH]; + char *period; + int len; + int ret_val; + + /* + * Valid locales have the following syntax: + * [_[.]] + * + * GetLocaleInfoEx can only take locale name without code-page and for + * the purpose of this API the code-page doesn't matter. + */ + period = strchr(winlocname, '.'); + if (period != NULL) + len = period - winlocname; + else + len = pg_mbstrlen(winlocname); + memset(wc_locale_name, 0, sizeof(wc_locale_name)); + memset(buffer, 0, sizeof(buffer)); + MultiByteToWideChar(CP_ACP, 0, winlocname, len, wc_locale_name, + LOCALE_NAME_MAX_LENGTH); + + /* + * If the lc_messages is already an Unix-style string, we have a + * direct match with LOCALE_SNAME, e.g. en-US, en_US. + */ + ret_val = GetLocaleInfoEx(wc_locale_name, LOCALE_SNAME, (LPWSTR) &buffer, + LOCALE_NAME_MAX_LENGTH); + if (!ret_val) + { + /* + * Search for a locale in the system that matches language and + * county names. + */ + wchar_t *argv[3]; + + argv[0] = wc_locale_name; + argv[1] = buffer; + argv[2] = (wchar_t *) &ret_val; + EnumSystemLocalesEx(search_locale_enum, LOCALE_WINDOWS, (LPARAM) argv, + NULL); + } + + if (ret_val) + { + /* Locale names use only ASCII, any conversion locale suffices. */ + rc = wchar2char(iso_lc_messages, buffer, sizeof(iso_lc_messages), + NULL); + } +#else /* Visual Studio 2015 or later */ + _locale_t loct; + + loct = _create_locale(LC_CTYPE, winlocname); + if (loct != NULL) + { + rc = wchar2char(iso_lc_messages, loct->locinfo->locale_name[LC_CTYPE], + sizeof(iso_lc_messages), NULL); + _free_locale(loct); + } +#endif /* Visual Studio 2015 or later */ - /* Locale names use only ASCII, any conversion locale suffices. */ - rc = wchar2char(iso_lc_messages, loct->locinfo->locale_name[LC_CTYPE], - sizeof(iso_lc_messages), NULL); - _free_locale(loct); if (rc == -1 || rc == sizeof(iso_lc_messages)) return NULL; @@ -1010,22 +1148,27 @@ IsoLocaleName(const char *winlocname) hyphen = strchr(iso_lc_messages, '-'); if (hyphen) *hyphen = '_'; -#else +#else /* Visual Studio 2012 or later */ char isolang[32], isocrty[32]; LCID lcid; + _locale_t loct; - lcid = loct->locinfo->lc_handle[LC_CTYPE]; - if (lcid == 0) - lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); - _free_locale(loct); + loct = _create_locale(LC_CTYPE, winlocname); + if (loct != NULL) + { + lcid = loct->locinfo->lc_handle[LC_CTYPE]; + if (lcid == 0) + lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); + _free_locale(loct); + } if (!GetLocaleInfoA(lcid, LOCALE_SISO639LANGNAME, isolang, sizeof(isolang))) return NULL; if (!GetLocaleInfoA(lcid, LOCALE_SISO3166CTRYNAME, isocrty, sizeof(isocrty))) return NULL; snprintf(iso_lc_messages, sizeof(iso_lc_messages) - 1, "%s_%s", isolang, isocrty); -#endif +#endif /* Visual Studio 2012 or later */ return iso_lc_messages; } return NULL;