From 8b696319e73736453735806197a1b23d4d168a36 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Thu, 5 Jan 2023 23:22:26 +0100 Subject: [PATCH 01/15] Make comment in wxLocale::GetLanguageInfo() even more detailed Explain that we need to call our (i.e. wxLocale) GetSystemLanguage() here instead of letting wxUILocale::GetLanguageInfo() call its own (i.e. wxUILocale) GetSystemLanguage() which does _not_ do the same thing. No real changes, just try to explain this confusing code better. --- src/common/intl.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/common/intl.cpp b/src/common/intl.cpp index 9e6c43f5c5..abcff7b1a0 100644 --- a/src/common/intl.cpp +++ b/src/common/intl.cpp @@ -653,8 +653,14 @@ const wxLanguageInfo* wxLocale::GetLanguageInfo(int lang) { // We need to explicitly handle the case "lang == wxLANGUAGE_DEFAULT" here, // because wxUILocale::GetLanguageInfo() determines the system language - // based on the the preferred UI language while wxLocale uses the default + // based on the preferred UI language while wxLocale uses the default // user locale for that purpose. + // + // Note that even though wxUILocale::GetLanguageInfo() seems to do the same + // thing as we do here, it actually does _not_ because we're calling our + // GetSystemLanguage() which maps to wxUILocale::GetSystemLocale() and not + // the function with the same name in that class. This is incredibly + // confusing but necessary for backwards compatibility. if (lang == wxLANGUAGE_DEFAULT) lang = GetSystemLanguage(); return wxUILocale::GetLanguageInfo(lang); From 4c237217438076074024744e80e56fe0cb4817f8 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 6 Jan 2023 03:20:57 +0100 Subject: [PATCH 02/15] Add a pseudo test to show system locale and language This can be useful to compare the results of calling wxUILocale::GetSystemLanguage() and the function with the same name in wxLocale (which actually corresponds to wxUILocale::GetSystemLocale()). --- tests/intl/intltest.cpp | 69 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/intl/intltest.cpp b/tests/intl/intltest.cpp index 9810199cfc..8826644021 100644 --- a/tests/intl/intltest.cpp +++ b/tests/intl/intltest.cpp @@ -430,4 +430,73 @@ TEST_CASE("wxUILocale::FromTag", "[.]") WARN("Locale \"" << tag << "\" supported: " << loc.IsSupported() ); } +namespace +{ + +const wxString GetLangName(int lang) +{ + switch ( lang ) + { + case wxLANGUAGE_DEFAULT: + return "DEFAULT"; + + case wxLANGUAGE_UNKNOWN: + return "UNKNOWN"; + + default: + return wxUILocale::GetLanguageName(lang); + } +} + +wxString GetLocaleDesc(const char* when) +{ + const wxUILocale& curloc = wxUILocale::GetCurrent(); + const wxLocaleIdent locid = curloc.GetLocaleId(); + + // Make the output slightly more readable. + wxString decsep = curloc.GetInfo(wxLOCALE_DECIMAL_POINT); + if ( decsep == "." ) + decsep = "point"; + else if ( decsep == "," ) + decsep = "comma"; + else + decsep = wxString::Format("UNKNOWN (%s)", decsep); + + return wxString::Format("%s\ncurrent locale:\t%s (decimal separator: %s)", + when, + locid.IsEmpty() ? wxString("NONE") : locid.GetTag(), + decsep); +} + +} // anonymous namespace + +// Test to show information about the system locale and the effects of various +// ways to change the current locale. +TEST_CASE("wxUILocale::ShowSystem", "[.]") +{ + WARN("System locale:\t" + << GetLangName(wxUILocale::GetSystemLocale()) << "\n" + "System language:\t" + << GetLangName(wxUILocale::GetSystemLanguage())); + + WARN(GetLocaleDesc("Before calling any locale functions")); + + wxLocale locDef; + CHECK( locDef.Init(wxLANGUAGE_DEFAULT, wxLOCALE_DONT_LOAD_DEFAULT) ); + WARN(GetLocaleDesc("After wxLocale::Init(wxLANGUAGE_DEFAULT)")); + + REQUIRE( wxUILocale::UseDefault() ); + WARN(GetLocaleDesc("After wxUILocale::UseDefault()")); + + wxString preferredLangsStr; + const auto preferredLangs = wxUILocale::GetPreferredUILanguages(); + for (const auto& lang: preferredLangs) + { + if ( !preferredLangsStr.empty() ) + preferredLangsStr += ", "; + preferredLangsStr += lang; + } + WARN("Preferred UI languages:\n" << preferredLangsStr); +} + #endif // wxUSE_INTL From ce04dbc0247aa548680f753568f9c9c39a4236b9 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 6 Jan 2023 03:45:47 +0100 Subject: [PATCH 03/15] Recommend using wxUILocale::GetSystemLanguage() in wxLocale docs The wxUILocale function really returns the default language unlike the one in wxLocale which must kepe using the default locale for compatibility, making its name confusing. --- interface/wx/intl.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/interface/wx/intl.h b/interface/wx/intl.h index 53bc3284ab..5114d00516 100644 --- a/interface/wx/intl.h +++ b/interface/wx/intl.h @@ -526,13 +526,15 @@ public: /** Tries to detect the user's default locale setting. - Returns the ::wxLanguage value or @c wxLANGUAGE_UNKNOWN if the language-guessing - algorithm failed. + @note This function is somewhat misleading, as it uses the default + system locale to determine its return value, and not just the system + language. It is preserved for backwards compatibility, but to actually + get the language, and not locale, used by the system by default, call + wxUILocale::GetSystemLanguage() instead. - @note This function works with @em locales and returns the user's default - locale. This may be, and usually is, the same as their preferred UI - language, but it's not the same thing. Use wxTranslation to obtain - @em language information. + Returns the ::wxLanguage value or @c wxLANGUAGE_UNKNOWN if the locale + is not recognized, as can notably happen when combining any language + with a region where this language is not typically spoken. @see wxTranslations::GetBestTranslation(). */ From f0b54bef2f5aef5487625ece22fa34845b9ad8e5 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 6 Jan 2023 03:48:39 +0100 Subject: [PATCH 04/15] Handle wxLANGUAGE_DEFAULT specially in wxLocale::IsAvailable() Check if using Init(wxLANGUAGE_DEFAULT) is going to succeed by actually calling wxSetLocale(LC_ALL, "") in this special case, as we can't if it's going to work otherwise if we don't recognize the language. --- src/common/intl.cpp | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/common/intl.cpp b/src/common/intl.cpp index abcff7b1a0..c32d708173 100644 --- a/src/common/intl.cpp +++ b/src/common/intl.cpp @@ -732,11 +732,36 @@ bool wxLocale::IsAvailable(int lang) const wxLanguageInfo *info = wxLocale::GetLanguageInfo(lang); if ( !info ) { - // The language is unknown (this normally only happens when we're - // passed wxLANGUAGE_DEFAULT), so we can't support it. - wxASSERT_MSG( lang == wxLANGUAGE_DEFAULT, - wxS("No info for a valid language?") ); - return false; + // This must be wxLANGUAGE_DEFAULT as otherwise we should have found + // the matching entry. + wxCHECK_MSG( lang == wxLANGUAGE_DEFAULT, false, + wxS("No info for a valid language?") ); + + // For this one, we need to check whether using it later is going to + // actually work, i.e. if the CRT supports it. + const char* const origLocale = wxSetlocale(LC_ALL, nullptr); + if ( !origLocale ) + { + // This is not supposed to happen, we should always be able to + // query the current locale, but don't crash if it does. + return false; + } + + // Make a copy of the string because wxSetlocale() call below may + // change the buffer to which it points. + const wxString origLocaleStr = wxString::FromUTF8(origLocale); + + if ( !wxSetlocale(LC_ALL, "") ) + { + // Locale wasn't changed, so nothing else to do. + return false; + } + + // We support this locale, but restore the original one before + // returning. + wxSetlocale(LC_ALL, origLocaleStr.utf8_str()); + + return true; } wxString localeTag = info->GetCanonicalWithRegion(); From d6c041e69b3c17abc3cb19980974d499484cf43a Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 6 Jan 2023 03:49:39 +0100 Subject: [PATCH 05/15] Don't assume that wxLocale::Init(wxLANGUAGE_DEFAULT) succeeds At least under macOS it fails when the system locale is something like en_DE, for example, as setlocale() doesn't support such locales. Use wxUILocale::GetSystemLanguage() instead of wxLANGUAGE_DEFAULT. --- tests/intl/intltest.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/intl/intltest.cpp b/tests/intl/intltest.cpp index 8826644021..7b9c1157f8 100644 --- a/tests/intl/intltest.cpp +++ b/tests/intl/intltest.cpp @@ -237,12 +237,13 @@ void IntlTestCase::IsAvailable() TEST_CASE("wxLocale::Default", "[locale]") { - INFO("System language: " << wxLocale::GetSystemLanguage()); - CHECK( wxLocale::IsAvailable(wxLANGUAGE_DEFAULT) ); + const int langDef = wxUILocale::GetSystemLanguage(); + INFO("System language: " << wxUILocale::GetLanguageName(langDef)); + CHECK( wxLocale::IsAvailable(langDef) ); wxLocale loc; - REQUIRE( loc.Init(wxLANGUAGE_DEFAULT, wxLOCALE_DONT_LOAD_DEFAULT) ); + REQUIRE( loc.Init(langDef, wxLOCALE_DONT_LOAD_DEFAULT) ); } // Under MSW and macOS all the locales used below should be supported, but From 98a9cd06880e10627868df5d11b97578ba18abca Mon Sep 17 00:00:00 2001 From: Ulrich Telle Date: Sun, 15 Jan 2023 15:17:44 +0100 Subject: [PATCH 06/15] Don't ignore LC_XXX in wxUILocaleImpl::GetPreferredUILanguages() Previously, if LANGUAGE was set, the other locale-related environment variables (LC_ALL, LC_MESSAGES, LANG) were simply ignored, which seems wrong as the user running the program after explicitly setting LC_ALL expects it to use this locale and not en_US from the default value of LANGUAGE on a typical Linux system using English. So handle LANGUAGE as a source of supplementary information, but still honour all the other environment variables. This goes against an explicit GNU gettext manual recommendation, but this recommendation seems to only make sense in the usual case, when the first element of LANGUAGE is the same as LC_ALL/LANG value, but not when they differ. See #23146. --- src/unix/uilocale.cpp | 45 ++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/unix/uilocale.cpp b/src/unix/uilocale.cpp index 11d084c474..c2a351c674 100644 --- a/src/unix/uilocale.cpp +++ b/src/unix/uilocale.cpp @@ -760,7 +760,29 @@ wxVector wxUILocaleImpl::GetPreferredUILanguages() // When the preferred UI language is determined, the LANGUAGE environment // variable is the primary source of preference. // http://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html - // + + // Since the first item in LANGUAGE is supposed to be equal to LANG resp LC_ALL, + // determine the default language based on the locale related environment variables + // as the first entry in the list of preferred languages. + wxString langFull; + wxString modifier; + if (GetLocaleFromEnvironment(langFull, modifier)) + { + if (!modifier.empty()) + { + // Locale name with modifier + if (const wxLanguageInfo* li = wxUILocale::FindLanguageInfo(langFull + modifier)) + { + preferred.push_back(li->CanonicalName); + } + } + // Locale name without modifier + if (const wxLanguageInfo* li = wxUILocale::FindLanguageInfo(langFull)) + { + preferred.push_back(li->CanonicalName); + } + } + // The LANGUAGE variable may contain a colon separated list of language // codes in the order of preference. // http://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html @@ -780,26 +802,19 @@ wxVector wxUILocaleImpl::GetPreferredUILanguages() return preferred; wxLogTrace(TRACE_I18N, " - LANGUAGE was set, but it didn't contain any languages recognized by the system"); } + else + { + wxLogTrace(TRACE_I18N, " - LANGUAGE was not set or empty, check LC_ALL, LC_MESSAGES, and LANG"); + } - wxLogTrace(TRACE_I18N, " - LANGUAGE was not set or empty, check LC_ALL, LC_MESSAGES, and LANG"); - - // first get the string identifying the language from the environment - wxString langFull, - modifier; - if (!GetLocaleFromEnvironment(langFull, modifier)) + if (preferred.empty()) { // no language specified, treat it as English langFull = "en_US"; + preferred.push_back(langFull); + wxLogTrace(TRACE_I18N, " - LC_ALL, LC_MESSAGES, and LANG were not set or empty, use English"); } - if (!modifier.empty()) - { - // Locale name with modifier - preferred.push_back(langFull + modifier); - } - // Locale name without modifier - preferred.push_back(langFull); - return preferred; } From c13b3645a8825d43b48d60ad297141827b71dad6 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 4 Feb 2023 16:06:19 +0100 Subject: [PATCH 07/15] Fix wxUILocale::FindLanguageInfo() to work for mixed locales When looking for the language information, we must recognize the language independently of the region it is followed by, so en_FR is still English and fr_DE is still French, even if the full locale is unknown, but this wasn't the case before. Fix this by comparing the language part of wxLanguageInfo with just the language of wxLocaleIdent we're trying to match, instead of comparing it with its full BCP47 tag, which is never going to match. --- src/common/uilocale.cpp | 7 +++++-- tests/intl/intltest.cpp | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/common/uilocale.cpp b/src/common/uilocale.cpp index 69846c2274..dbde0a5820 100644 --- a/src/common/uilocale.cpp +++ b/src/common/uilocale.cpp @@ -813,9 +813,12 @@ const wxLanguageInfo* wxUILocale::FindLanguageInfo(const wxLocaleIdent& locId) CreateLanguagesDB(); const wxLanguageInfo* infoRet = nullptr; + + wxString lang = locId.GetLanguage(); wxString localeTag = locId.GetTag(wxLOCALE_TAGTYPE_BCP47); - if (IsDefaultCLocale(locId.GetLanguage())) + if (IsDefaultCLocale(lang)) { + lang = wxS("en"); localeTag = "en-US"; } @@ -832,7 +835,7 @@ const wxLanguageInfo* wxUILocale::FindLanguageInfo(const wxLocaleIdent& locId) break; } - if (wxStricmp(localeTag, info->LocaleTag.BeforeFirst(wxS('-'))) == 0) + if (wxStricmp(lang, info->LocaleTag.BeforeFirst(wxS('-'))) == 0) { // a match -- but maybe we'll find an exact one later, so continue // looking diff --git a/tests/intl/intltest.cpp b/tests/intl/intltest.cpp index 7b9c1157f8..d34c0d90f2 100644 --- a/tests/intl/intltest.cpp +++ b/tests/intl/intltest.cpp @@ -413,6 +413,11 @@ TEST_CASE("wxUILocale::FindLanguageInfo", "[uilocale]") CheckFindLanguage("English_United States.utf8", "en_US"); // Test tag that includes an explicit script CheckFindLanguage("sr-Latn-RS", "sr_RS@latin"); + + // Test mixed locales: we should still detect the language correctly, even + // if we don't recognize the full locale. + CheckFindLanguage("en_FR", "en"); + CheckFindLanguage("fr_DE", "fr"); } // Test which can be used to check if the given locale tag is supported. From 9f2a416f81f76ae23b1f1bc5644c5918c2593b43 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 5 Feb 2023 15:49:01 +0100 Subject: [PATCH 08/15] Return wxLANGUAGE_UNKNOWN from GetSystemLocale() in mixed locales After the changes of the previous commit, GetSystemLocale() started returning the language, found as due to the fallback logic implemented in FindLanguageInfo(), for mixed locales, e.g. it would return English for en_FR which is wrong as the actual locale is rather the French one, using decimal comma and not period in this case. Make it always return wxLANGUAGE_UNKNOWN for the mixed locales for now. --- src/common/uilocale.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/common/uilocale.cpp b/src/common/uilocale.cpp index dbde0a5820..bf24482e7e 100644 --- a/src/common/uilocale.cpp +++ b/src/common/uilocale.cpp @@ -679,10 +679,22 @@ int wxUILocale::GetSystemLocale() { // Create default wxUILocale wxUILocale defaultLocale(wxUILocaleImpl::CreateUserDefault()); + const wxLocaleIdent locId = defaultLocale.GetLocaleId(); // Find corresponding wxLanguageInfo - const wxLanguageInfo* defaultLanguage = wxUILocale::FindLanguageInfo(defaultLocale.GetLocaleId()); - return defaultLanguage ? defaultLanguage->Language : wxLANGUAGE_UNKNOWN; + const wxLanguageInfo* defaultLanguage = wxUILocale::FindLanguageInfo(locId); + + // Check if it really corresponds to this locale: we could find it via the + // fallback on the language, which is something that it generally makes + // sense for FindLanguageInfo() to do, but in this case we really need the + // locale. + if ( defaultLanguage && + locId.GetTag(wxLOCALE_TAGTYPE_BCP47) == defaultLanguage->LocaleTag ) + { + return defaultLanguage->Language; + } + + return wxLANGUAGE_UNKNOWN; } /* static */ From 14714856b3956d52ede8bcf27345fdc6efd438e9 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 5 Feb 2023 16:40:27 +0100 Subject: [PATCH 09/15] Add wxUILocale::GetSystemLocaleId() This function replaces the existing GetSystemLocale() as it can represent the locales that don't have any corresponding wxLanguage values and for which GetSystemLocale() has no choice but to return wxLANGUAGE_UNKNOWN. --- include/wx/uilocale.h | 6 ++++++ interface/wx/uilocale.h | 19 ++++++++++++++++--- src/common/uilocale.cpp | 13 +++++++++---- tests/intl/intltest.cpp | 6 ++++-- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/include/wx/uilocale.h b/include/wx/uilocale.h index 039c6c2411..6176803008 100644 --- a/include/wx/uilocale.h +++ b/include/wx/uilocale.h @@ -158,12 +158,18 @@ public: // its dtor is not virtual. ~wxUILocale(); + // Return the locale ID representing the default system locale, which would + // be set is UseDefault() is called. + static wxLocaleIdent GetSystemLocaleId(); + // Try to get user's (or OS's) preferred language setting. // Return wxLANGUAGE_UNKNOWN if the language-guessing algorithm failed + // Prefer using GetSystemLocaleId() above. static int GetSystemLanguage(); // Try to get user's (or OS's) default locale setting. // Return wxLANGUAGE_UNKNOWN if the locale-guessing algorithm failed + // Prefer using GetSystemLocaleId() above. static int GetSystemLocale(); // Try to retrieve a list of user's (or OS's) preferred UI languages. diff --git a/interface/wx/uilocale.h b/interface/wx/uilocale.h index d61e11d928..67dc2364e7 100644 --- a/interface/wx/uilocale.h +++ b/interface/wx/uilocale.h @@ -289,7 +289,7 @@ public: by the operating system (for example, Windows 7 and below), the user's default @em locale will be used. - @see wxTranslations::GetBestTranslation(). + @see wxTranslations::GetBestTranslation(), GetSystemLocaleId(). */ static int GetSystemLanguage(); @@ -297,7 +297,8 @@ public: Tries to detect the user's default locale setting. Returns the ::wxLanguage value or @c wxLANGUAGE_UNKNOWN if the locale-guessing - algorithm failed. + algorithm failed or if the locale can't be described using solely a + language constant. Consider using GetSystemLocaleId() in this case. @note This function works with @em locales and returns the user's default locale. This may be, and usually is, the same as their preferred UI @@ -308,7 +309,19 @@ public: @see wxTranslations::GetBestTranslation(). */ - static int GetSystemLocale();}; + static int GetSystemLocale(); + + /** + Return the description of the default system locale. + + This function can always represent the system locale, even when using + a language and region pair that doesn't correspond to any of the + predefined ::wxLanguage constants, such as e.g. "fr-DE", which means + French language used with German locale settings. + + @since 3.3.0 + */ +}; /** Return the format to use for formatting user-visible dates. diff --git a/src/common/uilocale.cpp b/src/common/uilocale.cpp index bf24482e7e..49de63fcdc 100644 --- a/src/common/uilocale.cpp +++ b/src/common/uilocale.cpp @@ -632,6 +632,13 @@ wxUILocale::~wxUILocale() } +/* static */ +wxLocaleIdent wxUILocale::GetSystemLocaleId() +{ + wxUILocale defaultLocale(wxUILocaleImpl::CreateUserDefault()); + return defaultLocale.GetLocaleId(); +} + /*static*/ int wxUILocale::GetSystemLanguage() { @@ -677,11 +684,9 @@ int wxUILocale::GetSystemLanguage() /*static*/ int wxUILocale::GetSystemLocale() { - // Create default wxUILocale - wxUILocale defaultLocale(wxUILocaleImpl::CreateUserDefault()); - const wxLocaleIdent locId = defaultLocale.GetLocaleId(); + const wxLocaleIdent locId = GetSystemLocaleId(); - // Find corresponding wxLanguageInfo + // Find wxLanguageInfo corresponding to the default locale. const wxLanguageInfo* defaultLanguage = wxUILocale::FindLanguageInfo(locId); // Check if it really corresponds to this locale: we could find it via the diff --git a/tests/intl/intltest.cpp b/tests/intl/intltest.cpp index d34c0d90f2..a82330d35d 100644 --- a/tests/intl/intltest.cpp +++ b/tests/intl/intltest.cpp @@ -480,9 +480,11 @@ wxString GetLocaleDesc(const char* when) // ways to change the current locale. TEST_CASE("wxUILocale::ShowSystem", "[.]") { - WARN("System locale:\t" + WARN("System locale identifier:\t" + << wxUILocale::GetSystemLocaleId().GetTag() << "\n" + "System locale as language:\t" << GetLangName(wxUILocale::GetSystemLocale()) << "\n" - "System language:\t" + "System language identifier:\t" << GetLangName(wxUILocale::GetSystemLanguage())); WARN(GetLocaleDesc("Before calling any locale functions")); From e3146f9ac078f6095e6c93651583fcba5fd3feb2 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 5 Feb 2023 16:58:39 +0100 Subject: [PATCH 10/15] Create valid (if empty) "C" locale under macOS Use [NSLocale systemLocale] instead of trying to create a locale with the name "C" which just fails and doesn't create any NSLocale at all. If nothing else, this makes the behaviour of wxUILocale::GetCurrent() consistent on all platforms as its GetLocaleId() now returns "C" everywhere. --- src/osx/core/uilocale.mm | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/osx/core/uilocale.mm b/src/osx/core/uilocale.mm index f739fa32d2..978cdf7625 100644 --- a/src/osx/core/uilocale.mm +++ b/src/osx/core/uilocale.mm @@ -131,7 +131,13 @@ wxUILocaleImplCF::Use() wxString wxUILocaleImplCF::GetName() const { - return wxCFStringRef::AsString([m_nsloc localeIdentifier]); + wxString name = wxCFStringRef::AsString([m_nsloc localeIdentifier]); + + // Check for the special case of the "empty" system locale, see CreateStdC() + if ( name.empty() ) + name = "C"; + + return name; } wxLocaleIdent @@ -209,7 +215,12 @@ wxUILocaleImplCF::GetLayoutDirection() const /* static */ wxUILocaleImpl* wxUILocaleImpl::CreateStdC() { - return wxUILocaleImplCF::Create(wxLocaleIdent().Language("C")); + // This is an "empty" locale, but it seems to correspond rather well to the + // "C" locale under POSIX systems and using localeWithLocaleIdentifier:@"C" + // wouldn't be much better as we'd still need a hack for it in GetName() + // because the locale names are always converted to lower case, while we + // really want to return "C" rather than "c" as the name of this one. + return new wxUILocaleImplCF([NSLocale systemLocale]); } /* static */ From 856c0371faaf17c735efabc7708e79b68e00aaf2 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 5 Feb 2023 17:07:06 +0100 Subject: [PATCH 11/15] Remove outdated special case for Mac from wxSetlocale() This function must be used for setting the default CRT locale only and we can't request arbitrary locales support from it under Mac as it doesn't support any kind of mixed locales, such as "en-FR". We do support them in wxUILocale, and calling wxUILocale::UseDefault() from wxLocale::Init(wxLANGUAGE_DEFAULT) actually worked, but then this function failed because wxSetlocale(LC_ALL, "") did not work for such locales. Fix this simply by removing Mac-specific code from this function. This makes wxLocale::Init(wxLANGUAGE_DEFAULT) work as well as it ever can under Mac. --- src/common/wxcrt.cpp | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/common/wxcrt.cpp b/src/common/wxcrt.cpp index ab8ae524a7..bf00eecc86 100644 --- a/src/common/wxcrt.cpp +++ b/src/common/wxcrt.cpp @@ -52,13 +52,6 @@ #include -#if defined(__DARWIN__) - #include "wx/osx/core/cfref.h" - #include - #include "wx/osx/core/cfstring.h" - #include -#endif - wxDECL_FOR_STRICT_MINGW32(int, vswprintf, (wchar_t*, const wchar_t*, __VALIST)) wxDECL_FOR_STRICT_MINGW32(int, _putws, (const wchar_t*)) wxDECL_FOR_STRICT_MINGW32(void, _wperror, (const wchar_t*)) @@ -125,27 +118,7 @@ WXDLLIMPEXP_BASE size_t wxWC2MB(char *buf, const wchar_t *pwz, size_t n) char* wxSetlocale(int category, const char *locale) { -#ifdef __WXMAC__ - char *rv = nullptr ; - if ( locale != nullptr && locale[0] == 0 ) - { - // the attempt to use newlocale(LC_ALL_MASK, "", nullptr); - // here in order to deduce the language along the environment vars rules - // lead to strange crashes later... - - // we have to emulate the behaviour under OS X - wxCFRef userLocaleRef(CFLocaleCopyCurrent()); - wxCFStringRef str(wxCFRetain((CFStringRef)CFLocaleGetValue(userLocaleRef, kCFLocaleLanguageCode))); - wxString langFull = str.AsString()+"_"; - str.reset(wxCFRetain((CFStringRef)CFLocaleGetValue(userLocaleRef, kCFLocaleCountryCode))); - langFull += str.AsString(); - rv = setlocale(category, langFull.c_str()); - } - else - rv = setlocale(category, locale); -#else char *rv = setlocale(category, locale); -#endif if ( locale != nullptr /* setting locale, not querying */ && rv /* call was successful */ ) { From 2ca76449bf636b58b72fccbe5dea485b241d692d Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 6 Feb 2023 00:07:51 +0100 Subject: [PATCH 12/15] Fail if environment variables define unknown locale under Unix Don't always "succeed" in wxUILocale::UseDefault() and, consequently, in wxLocale::Init(wxLANGUAGE_DEFAULT), under Unix systems, even if the locale couldn't actually be set, as it can happen if the environment variables contain a locale which is not supported on the current system, e.g. a "mixed" locale such as "en_FR", or even a completely invalid string such as "bloordyblop", which still used to succeed. For now only fix it for reasonably modern systems with locale_t support, it could be done even for the ancient ones without it later too if anybody still cares about them. Closes #23218. --- src/unix/uilocale.cpp | 14 ++++++++++++++ tests/intl/intltest.cpp | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/unix/uilocale.cpp b/src/unix/uilocale.cpp index c2a351c674..b9ef63865f 100644 --- a/src/unix/uilocale.cpp +++ b/src/unix/uilocale.cpp @@ -730,7 +730,21 @@ wxUILocaleImpl* wxUILocaleImpl::CreateStdC() /* static */ wxUILocaleImpl* wxUILocaleImpl::CreateUserDefault() { +#ifdef HAVE_LOCALE_T + // Setting default locale can fail under Unix if LANG or LC_ALL are set to + // an unsupported value, so check for this here to let the caller know if + // we can't do it. + wxLocaleIdent locDef; + locale_t loc = TryCreateLocaleWithUTF8(locDef); + if ( !loc ) + return nullptr; + + return new wxUILocaleImplUnix(wxLocaleIdent(), loc); +#else // !HAVE_LOCALE_T + // We could temporarily change the locale here to check if it's supported, + // but for now don't bother and assume it is. return new wxUILocaleImplUnix(wxLocaleIdent()); +#endif // HAVE_LOCALE_T/!HAVE_LOCALE_T } /* static */ diff --git a/tests/intl/intltest.cpp b/tests/intl/intltest.cpp index a82330d35d..0391a90b1b 100644 --- a/tests/intl/intltest.cpp +++ b/tests/intl/intltest.cpp @@ -493,7 +493,7 @@ TEST_CASE("wxUILocale::ShowSystem", "[.]") CHECK( locDef.Init(wxLANGUAGE_DEFAULT, wxLOCALE_DONT_LOAD_DEFAULT) ); WARN(GetLocaleDesc("After wxLocale::Init(wxLANGUAGE_DEFAULT)")); - REQUIRE( wxUILocale::UseDefault() ); + CHECK( wxUILocale::UseDefault() ); WARN(GetLocaleDesc("After wxUILocale::UseDefault()")); wxString preferredLangsStr; From 3dfe253d3cf7f8e1bfba6b2cdb3f53628aa224df Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 6 Feb 2023 21:31:16 +0100 Subject: [PATCH 13/15] Fix GetSystemLocale() return value for "C" locale This was broken by the recent changes, so fix it again: we do still want to return wxLANGUAGE_ENGLISH_US for this particular locale. --- src/common/uilocale.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/common/uilocale.cpp b/src/common/uilocale.cpp index 49de63fcdc..cc50184bc5 100644 --- a/src/common/uilocale.cpp +++ b/src/common/uilocale.cpp @@ -693,10 +693,14 @@ int wxUILocale::GetSystemLocale() // fallback on the language, which is something that it generally makes // sense for FindLanguageInfo() to do, but in this case we really need the // locale. - if ( defaultLanguage && - locId.GetTag(wxLOCALE_TAGTYPE_BCP47) == defaultLanguage->LocaleTag ) + if ( defaultLanguage ) { - return defaultLanguage->Language; + // We have to handle the "C" locale specially as its name is different + // from the "en-US" tag found for it, but we do still want to return + // English for it. + const wxString tag = locId.GetTag(wxLOCALE_TAGTYPE_BCP47); + if ( tag == defaultLanguage->LocaleTag || IsDefaultCLocale(tag) ) + return defaultLanguage->Language; } return wxLANGUAGE_UNKNOWN; From 1cf59d345ba79719f0c143f5ce1c7047e2b3dea7 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 7 Feb 2023 00:23:31 +0100 Subject: [PATCH 14/15] Remove fallback on locale using different region under Unix Historically, we used to fall back on any locale using the same language, if the exactly requested locale was unavailable. This doesn't seem such a great idea, and results in unexpected behaviour, such as returning true from wxUILocale::IsSupported() for locales such as "en-FR" that are not really supported at all. Remove this fallback and just return false if the locale with the specified region is not supported. Still choose any supported region if the region is omitted entirely, however. --- docs/changes.txt | 6 ++++++ src/unix/uilocale.cpp | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index fab00ba096..fdbc5cb67f 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -63,6 +63,12 @@ Changes in behaviour not resulting in compilation errors mode under MSW, use the new AreAppsDark() or IsSystemDark() to check if the other applications or the system are using dark mode. +- wxUILocale::IsSupported() now returns false for unavailable locales under + Unix systems without trying to fall back on another locale using the same + language in a different region, e.g. it doesn't use fr_FR if fr_BE is not + available. If any locale using the given language is acceptable, the region + must be left empty, e.g. just "fr" would use any available "fr_XX". + Changes in behaviour which may result in build errors ----------------------------------------------------- diff --git a/src/unix/uilocale.cpp b/src/unix/uilocale.cpp index b9ef63865f..d5583d902d 100644 --- a/src/unix/uilocale.cpp +++ b/src/unix/uilocale.cpp @@ -272,11 +272,11 @@ locale_t TryCreateLocaleWithUTF8(wxLocaleIdent& locId) locale_t TryCreateMatchingLocale(wxLocaleIdent& locId) { locale_t loc = TryCreateLocaleWithUTF8(locId); - if ( !loc ) + if ( !loc && locId.GetRegion().empty() ) { - // Try to find a variant of this locale available on this system: first - // of all, using just the language, without the territory, typically - // does _not_ work under Linux, so try adding one if we don't have it. + // Try to find a variant of this locale available on this system: as + // using just the language, without the territory, typically does _not_ + // work under Linux, we try adding one if we don't have it. const wxString lang = locId.GetLanguage(); const wxLanguageInfos& infos = wxGetLanguageInfos(); From db14662116fbf433c928b0caedc1bcde54e72436 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 7 Feb 2023 13:12:21 +0100 Subject: [PATCH 15/15] Don't use LC_MESSAGES for determining locale on non-glibc systems LC_MESSAGES should only be used for getting the translations language, so using GetLocaleFromEnvironment(), which uses its value if LC_ALL was not set, in wxUILocaleImplUnix::InitLocaleNameAndCodeset() was wrong for the cases when we did call it from there, i.e. when not using glibc and so _NL_LOCALE_NAME is not defined. Correct this by replacing GetLocaleFromEnvironment() querying all environment variables at once with GetLocaleFromEnvVar() getting the value from just the one environment variable specified by caller and not calling it for LC_MESSAGES from InitLocaleNameAndCodeset(). See #23217. --- src/unix/uilocale.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/unix/uilocale.cpp b/src/unix/uilocale.cpp index d5583d902d..c51c4f2f19 100644 --- a/src/unix/uilocale.cpp +++ b/src/unix/uilocale.cpp @@ -47,17 +47,15 @@ inline bool wxGetNonEmptyEnvVar(const wxString& name, wxString* value) return wxGetEnv(name, value) && !value->empty(); } -// Get locale information from the appropriate environment variable: the output +// Get locale information from the specified environment variable: the output // variables are filled with the locale part (xx_XX) and the modifier is filled // with the optional part following "@". // // Return false if there is no locale information in the environment variables // or if it is just "C" or "POSIX". -bool GetLocaleFromEnvironment(wxString& langFull, wxString& modifier) +bool GetLocaleFromEnvVar(const char* var, wxString& langFull, wxString& modifier) { - if (!wxGetNonEmptyEnvVar(wxS("LC_ALL"), &langFull) && - !wxGetNonEmptyEnvVar(wxS("LC_MESSAGES"), &langFull) && - !wxGetNonEmptyEnvVar(wxS("LANG"), &langFull)) + if ( !wxGetNonEmptyEnvVar(var, &langFull) ) { return false; } @@ -475,7 +473,8 @@ wxUILocaleImplUnix::InitLocaleNameAndCodeset() const // This must be the default locale. wxString locName, modifier; - if ( !GetLocaleFromEnvironment(locName, modifier) ) + if ( !GetLocaleFromEnvVar("LC_ALL", locName, modifier) && + !GetLocaleFromEnvVar("LANG", locName, modifier) ) { // This is the default locale if nothing is specified. locName = "en_US"; @@ -780,7 +779,15 @@ wxVector wxUILocaleImpl::GetPreferredUILanguages() // as the first entry in the list of preferred languages. wxString langFull; wxString modifier; - if (GetLocaleFromEnvironment(langFull, modifier)) + + // Check LC_ALL first, as it's supposed to override everything else, then + // for LC_MESSAGES because this is the variable defining the translations + // language and so must correspond to the language the user wants to use + // and, otherwise, fall back on LANG which is the normal way to specify + // both the locale and the language. + if ( GetLocaleFromEnvVar("LC_ALL", langFull, modifier) || + GetLocaleFromEnvVar("LC_MESSAGES", langFull, modifier) || + GetLocaleFromEnvVar("LANG", langFull, modifier) ) { if (!modifier.empty()) {