Improve wxLocale backwards compatibility and enhance wxUILocale

Restore the old behaviour of wxLocale, which is supposed to use the
default locale and not the preferred language, which may not be the same
(see #22281).

Also apply the following fixes and improvements to wxUILocale:

- Add new GetSystemLocale() method.
- Change the MSW implementation to use the default locale instead of the
  preferred UI language for Windows versions below Windows 10.
- Change the Unix implementation to respect LANGUAGE environment
  variable and use it for determining the preferred UI languages.
- Use wxUILocale in wxTranslations to determine the preferred UI
  languages.
- Use wxUILocale during initialization of internat sample.

Closes #22281.

Closes #22318.
This commit is contained in:
utelle 2022-04-16 13:52:20 +02:00 committed by Vadim Zeitlin
parent 4a3098aebb
commit 8ab635b451
8 changed files with 171 additions and 187 deletions

View File

@ -159,9 +159,13 @@ public:
~wxUILocale();
// Try to get user's (or OS's) preferred language setting.
// Return wxLANGUAGE_UNKNOWN if language-guessing algorithm failed
// Return wxLANGUAGE_UNKNOWN if the language-guessing algorithm failed
static int GetSystemLanguage();
// Try to get user's (or OS's) default locale setting.
// Return wxLANGUAGE_UNKNOWN if the locale-guessing algorithm failed
static int GetSystemLocale();
// Try to retrieve a list of user's (or OS's) preferred UI languages.
// Return empty list if language-guessing algorithm failed
static wxVector<wxString> GetPreferredUILanguages();

View File

@ -278,20 +278,37 @@ public:
static wxString GetLanguageCanonicalName(int lang);
/**
Tries to detect the user's default locale setting.
Tries to detect the user's default user interface language setting.
Returns the ::wxLanguage value or @c wxLANGUAGE_UNKNOWN if the language-guessing
algorithm failed.
@note Where possible this function returns the user's preferred UI @em language.
This may be, and usually is, the same as the user's default locale, but it's
not the same thing. If retrieving the preferred UI language is not supported
by the operating system (for example, Windows 7 and below), the user's
default @em locale will be used.
@see wxTranslations::GetBestTranslation().
*/
static int GetSystemLanguage();
/**
Tries to detect the user's default locale setting.
Returns the ::wxLanguage value or @c wxLANGUAGE_UNKNOWN if the locale-guessing
algorithm failed.
@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.
@since 3.1.7
@see wxTranslations::GetBestTranslation().
*/
static int GetSystemLanguage();
};
static int GetSystemLocale();};
/**
Return the format to use for formatting user-visible dates.

View File

@ -207,10 +207,10 @@ bool MyApp::OnInit()
// it unconditionally for localized programs -- or never do it at all for
// the other ones.
const wxLanguageInfo* const
langInfo = wxLocale::GetLanguageInfo(wxLANGUAGE_DEFAULT);
langInfo = wxUILocale::GetLanguageInfo(wxLANGUAGE_DEFAULT);
const wxString
langDesc = langInfo ? langInfo->Description
: wxString("the default system locale");
: wxString("the default system language");
if ( m_setLocale == Locale_Ask )
{

View File

@ -513,7 +513,10 @@ bool wxLocale::Init(int lang, int flags)
/*static*/
int wxLocale::GetSystemLanguage()
{
return wxUILocale::GetSystemLanguage();
// Despite the method name wxLocale always determines the system language
// based on the default user locale (and not the preferred UI language).
// Therefore we need to call wxUILocale::GetSystemLocale() here.
return wxUILocale::GetSystemLocale();
}
// ----------------------------------------------------------------------------
@ -679,6 +682,12 @@ void wxLocale::AddLanguage(const wxLanguageInfo& info)
/* static */
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
// user locale for that purpose.
if (lang == wxLANGUAGE_DEFAULT)
lang = GetSystemLanguage();
return wxUILocale::GetLanguageInfo(lang);
}

View File

@ -47,6 +47,7 @@
#include "wx/stdpaths.h"
#include "wx/version.h"
#include "wx/private/threadinfo.h"
#include "wx/uilocale.h"
#ifdef __WINDOWS__
#include "wx/dynlib.h"
@ -54,11 +55,6 @@
#include "wx/msw/wrapwin.h"
#include "wx/msw/missing.h"
#endif
#ifdef __WXOSX__
#include "wx/osx/core/cfstring.h"
#include <CoreFoundation/CFBundle.h>
#include <CoreFoundation/CFLocale.h>
#endif
// ----------------------------------------------------------------------------
// simple types
@ -96,11 +92,23 @@ wxStringToStringHashMap gs_msgIdCharset;
#if wxUSE_LOG_TRACE
void LogTraceArray(const char *prefix, const wxArrayString& arr)
void LogTraceArray(const char* prefix, const wxArrayString& arr)
{
wxLogTrace(TRACE_I18N, "%s: [%s]", prefix, wxJoin(arr, ','));
}
void LogTraceArray(const char *prefix, const wxVector<wxString>& arr)
{
wxString s;
for (wxVector<wxString>::const_iterator j = arr.begin(); j != arr.end(); ++j)
{
if (j != arr.begin())
s += ",";
s += *j;
}
wxLogTrace(TRACE_I18N, "%s: [%s]", prefix, s);
}
void LogTraceLargeArray(const wxString& prefix, const wxArrayString& arr)
{
wxLogTrace(TRACE_I18N, "%s:", prefix);
@ -118,184 +126,58 @@ void LogTraceLargeArray(const wxString& prefix, const wxArrayString& arr)
// Use locale-based detection as a fallback
wxString GetPreferredUILanguageFallback(const wxArrayString& WXUNUSED(available))
{
const wxString lang = wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage());
const wxString lang = wxUILocale::GetLanguageCanonicalName(wxUILocale::GetSystemLocale());
wxLogTrace(TRACE_I18N, " - obtained best language from locale: %s", lang);
return lang;
}
#ifdef __WINDOWS__
wxString GetPreferredUILanguage(const wxArrayString& available)
{
typedef BOOL (WINAPI *GetUserPreferredUILanguages_t)(DWORD, PULONG, PWSTR, PULONG);
static GetUserPreferredUILanguages_t s_pfnGetUserPreferredUILanguages = NULL;
static bool s_initDone = false;
if ( !s_initDone )
{
wxLoadedDLL dllKernel32("kernel32.dll");
wxDL_INIT_FUNC(s_pfn, GetUserPreferredUILanguages, dllKernel32);
s_initDone = true;
}
wxVector<wxString> preferred = wxUILocale::GetPreferredUILanguages();
LogTraceArray(" - system preferred languages", preferred);
if ( s_pfnGetUserPreferredUILanguages )
{
ULONG numLangs;
ULONG bufferSize = 0;
if ( s_pfnGetUserPreferredUILanguages(MUI_LANGUAGE_NAME,
&numLangs,
NULL,
&bufferSize) )
{
wxScopedArray<WCHAR> langs(bufferSize);
if ( s_pfnGetUserPreferredUILanguages(MUI_LANGUAGE_NAME,
&numLangs,
langs.get(),
&bufferSize) )
{
wxArrayString preferred;
WCHAR *buf = langs.get();
for ( unsigned i = 0; i < numLangs; i++ )
{
const wxString lang(buf);
preferred.push_back(lang);
buf += lang.length() + 1;
}
LogTraceArray(" - system preferred languages", preferred);
for ( wxArrayString::const_iterator j = preferred.begin();
j != preferred.end();
++j )
{
wxString lang(*j);
lang.Replace("-", "_");
if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND )
return lang;
size_t pos = lang.find('_');
if ( pos != wxString::npos )
{
lang = lang.substr(0, pos);
if ( available.Index(lang, /*bCase=*/false) != wxNOT_FOUND )
return lang;
}
}
}
}
}
return GetPreferredUILanguageFallback(available);
}
#elif defined(__WXOSX__)
#if wxUSE_LOG_TRACE
void LogTraceArray(const char *prefix, CFArrayRef arr)
{
wxString s;
const unsigned count = CFArrayGetCount(arr);
if ( count )
{
s += wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(arr, 0));
for ( unsigned i = 1 ; i < count; i++ )
s += "," + wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(arr, i));
}
wxLogTrace(TRACE_I18N, "%s: [%s]", prefix, s);
}
#endif // wxUSE_LOG_TRACE
wxString GetPreferredUILanguage(const wxArrayString& available)
{
wxStringToStringHashMap availableNormalized;
wxCFRef<CFMutableArrayRef> availableArr(
CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
for ( wxArrayString::const_iterator i = available.begin();
i != available.end();
++i )
{
wxString lang(*i);
wxCFStringRef code_wx(*i);
wxCFStringRef code_norm(
CFLocaleCreateCanonicalLanguageIdentifierFromString(kCFAllocatorDefault, code_wx));
CFArrayAppendValue(availableArr, code_norm);
availableNormalized[code_norm.AsString()] = *i;
}
LogTraceArray(" - normalized available list", availableArr);
wxCFRef<CFArrayRef> prefArr(
CFBundleCopyLocalizationsForPreferences(availableArr, NULL));
LogTraceArray(" - system preferred languages", prefArr);
unsigned prefArrLength = CFArrayGetCount(prefArr);
if ( prefArrLength > 0 )
{
// Lookup the name in 'available' by index -- we need to get the
// original value corresponding to the normalized one chosen.
wxString lang(wxCFStringRef::AsString((CFStringRef)CFArrayGetValueAtIndex(prefArr, 0)));
wxStringToStringHashMap::const_iterator i = availableNormalized.find(lang);
if ( i == availableNormalized.end() )
return lang;
else
return i->second;
}
return GetPreferredUILanguageFallback(available);
}
#else
// 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
//
// 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
wxString GetPreferredUILanguage(const wxArrayString& available)
{
wxString languageFromEnv;
wxArrayString preferred;
if ( wxGetEnv("LANGUAGE", &languageFromEnv) )
{
wxStringTokenizer tknzr(languageFromEnv, ":");
while ( tknzr.HasMoreTokens() )
{
const wxString tok = tknzr.GetNextToken();
if ( const wxLanguageInfo *li = wxLocale::FindLanguageInfo(tok) )
{
preferred.push_back(li->CanonicalName);
}
}
if ( preferred.empty() )
{
wxLogTrace(TRACE_I18N, " - LANGUAGE was set, but it didn't contain any languages recognized by the system");
}
}
LogTraceArray(" - preferred languages from environment", preferred);
for ( wxArrayString::const_iterator j = preferred.begin();
wxString langNoMatchRegion;
for ( wxVector<wxString>::const_iterator j = preferred.begin();
j != preferred.end();
++j )
{
wxString lang(*j);
if ( available.Index(lang) != wxNOT_FOUND )
wxLocaleIdent localeId = wxLocaleIdent::FromTag(*j);
wxString lang = localeId.GetTag(wxLOCALE_TAGTYPE_POSIX);
if (available.Index(lang, /*bCase=*/false) != wxNOT_FOUND)
return lang;
size_t pos = lang.find('_');
if ( pos != wxString::npos )
if (pos != wxString::npos)
{
lang = lang.substr(0, pos);
if ( available.Index(lang) != wxNOT_FOUND )
if (available.Index(lang, /*bCase=*/false) != wxNOT_FOUND)
return lang;
}
if (langNoMatchRegion.empty())
{
// lang now holds only the language
// check for an available language with potentially non-matching region
for ( wxArrayString::const_iterator k = available.begin();
k != available.end();
++k )
{
if ((*k).Lower().StartsWith(lang.Lower()))
{
langNoMatchRegion = *k;
break;
}
}
}
}
if (!langNoMatchRegion.empty())
return langNoMatchRegion;
return GetPreferredUILanguageFallback(available);
}
#endif
} // anonymous namespace
// ----------------------------------------------------------------------------
@ -1501,7 +1383,7 @@ void wxTranslations::SetLanguage(wxLanguage lang)
if ( lang == wxLANGUAGE_DEFAULT )
SetLanguage(wxString());
else
SetLanguage(wxLocale::GetLanguageCanonicalName(lang));
SetLanguage(wxUILocale::GetLanguageCanonicalName(lang));
}
void wxTranslations::SetLanguage(const wxString& lang)
@ -1546,7 +1428,7 @@ bool wxTranslations::AddCatalog(const wxString& domain,
bool wxTranslations::AddCatalog(const wxString& domain,
wxLanguage msgIdLanguage)
{
const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
const wxString msgIdLang = wxUILocale::GetLanguageCanonicalName(msgIdLanguage);
const wxString domain_lang = GetBestTranslation(domain, msgIdLang);
if ( domain_lang.empty() )
@ -1641,7 +1523,7 @@ bool wxTranslations::IsLoaded(const wxString& domain) const
wxString wxTranslations::GetBestTranslation(const wxString& domain,
wxLanguage msgIdLanguage)
{
const wxString lang = wxLocale::GetLanguageCanonicalName(msgIdLanguage);
const wxString lang = wxUILocale::GetLanguageCanonicalName(msgIdLanguage);
return GetBestTranslation(domain, lang);
}

View File

@ -652,7 +652,7 @@ int wxUILocale::GetSystemLanguage()
}
if (pos != wxString::npos)
{
if (languagesDB[ixLanguage].LocaleTag == lang)
if (languagesDB[ixLanguage].LocaleTag == langShort)
{
ixShort = ixLanguage;
}
@ -664,8 +664,20 @@ int wxUILocale::GetSystemLanguage()
}
}
// no info about this language in the database
return wxLANGUAGE_UNKNOWN;
// no info about the preferred UI language in the database
// fall back to default locale
return GetSystemLocale();
}
/*static*/
int wxUILocale::GetSystemLocale()
{
// Create default wxUILocale
wxUILocale defaultLocale(wxUILocaleImpl::CreateUserDefault());
// Find corresponding wxLanguageInfo
const wxLanguageInfo* defaultLanguage = wxUILocale::FindLanguageInfo(defaultLocale.GetLocaleId());
return defaultLanguage ? defaultLanguage->Language : wxLANGUAGE_UNKNOWN;
}
/* static */

View File

@ -32,6 +32,10 @@
#define LOCALE_NAME_USER_DEFAULT NULL
#endif
#ifndef LOCALE_NAME_MAX_LENGTH
#define LOCALE_NAME_MAX_LENGTH 85
#endif
#ifndef MUI_LANGUAGE_NAME
#define MUI_LANGUAGE_NAME 8
#endif
@ -398,6 +402,9 @@ public:
wxDL_INIT_FUNC(ms_, GetUserPreferredUILanguages, dllKernel32);
if (!ms_GetUserPreferredUILanguages)
return false;
wxDL_INIT_FUNC(ms_, GetUserDefaultLocaleName, dllKernel32);
if (!ms_GetUserDefaultLocaleName)
return false;
wxDL_INIT_FUNC(ms_, CompareStringEx, dllKernel32);
if ( !ms_CompareStringEx )
@ -415,19 +422,29 @@ public:
if (CanUse())
{
ULONG numberOfLanguages = 0;
ULONG bufferSize = 0;
if (ms_GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages, NULL, &bufferSize))
// Check if Windows supports preferred UI languages.
// Note: Windows 8.x might support them as well, but Windows 7
// and below definitely do not.
if (wxGetWinVersion() >= wxWinVersion_10)
{
wxScopedArray<WCHAR> languages(bufferSize);
if (ms_GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages, languages.get(), &bufferSize))
ULONG numberOfLanguages = 0;
ULONG bufferSize = 0;
if (ms_GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages, NULL, &bufferSize))
{
WCHAR* buf = languages.get();
for (unsigned k = 0; k < numberOfLanguages; ++k)
wxScopedArray<WCHAR> languages(bufferSize);
if (ms_GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages, languages.get(), &bufferSize))
{
const wxString language(buf);
preferred.push_back(language);
buf += language.length() + 1;
WCHAR* buf = languages.get();
for (unsigned k = 0; k < numberOfLanguages; ++k)
{
const wxString language(buf);
preferred.push_back(language);
buf += language.length() + 1;
}
}
else
{
wxLogLastError(wxT("GetUserPreferredUILanguages"));
}
}
else
@ -437,7 +454,16 @@ public:
}
else
{
wxLogLastError(wxT("GetUserPreferredUILanguages"));
// Use the default user locale for Windows 7 resp Windows 8.x and below
wchar_t buf[LOCALE_NAME_MAX_LENGTH];
if (!ms_GetUserDefaultLocaleName(buf, LOCALE_NAME_MAX_LENGTH))
{
preferred.push_back(buf);
}
else
{
wxLogLastError(wxT("GetUserDefaultLocaleName"));
}
}
}
else
@ -724,6 +750,9 @@ private:
typedef BOOL (WINAPI *GetUserPreferredUILanguages_t)(DWORD, ULONG*, WCHAR*, ULONG*);
static GetUserPreferredUILanguages_t ms_GetUserPreferredUILanguages;
typedef int (WINAPI* GetUserDefaultLocaleName_t)(LPWSTR, int);
static GetUserDefaultLocaleName_t ms_GetUserDefaultLocaleName;
// Note: we currently don't use NLSVERSIONINFO output parameter and so we
// don't bother dealing with the different sizes of this struct under
// different OS versions and define the function type as using "void*" to
@ -766,6 +795,7 @@ private:
wxUILocaleImplName::GetLocaleInfoEx_t wxUILocaleImplName::ms_GetLocaleInfoEx;
wxUILocaleImplName::SetThreadPreferredUILanguages_t wxUILocaleImplName::ms_SetThreadPreferredUILanguages;
wxUILocaleImplName::GetUserPreferredUILanguages_t wxUILocaleImplName::ms_GetUserPreferredUILanguages;
wxUILocaleImplName::GetUserDefaultLocaleName_t wxUILocaleImplName::ms_GetUserDefaultLocaleName;
wxUILocaleImplName::CompareStringEx_t wxUILocaleImplName::ms_CompareStringEx;
// ----------------------------------------------------------------------------

View File

@ -26,6 +26,8 @@
#include "wx/unix/private/uilocale.h"
#include "wx/intl.h"
#include "wx/log.h"
#include "wx/tokenzr.h"
#include "wx/utils.h"
#include <locale.h>
@ -33,6 +35,8 @@
#include <langinfo.h>
#endif
#define TRACE_I18N wxS("i18n")
namespace
{
@ -559,6 +563,32 @@ wxVector<wxString> wxUILocaleImpl::GetPreferredUILanguages()
{
wxVector<wxString> preferred;
// 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
//
// 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
wxString languageFromEnv;
if (wxGetNonEmptyEnvVar("LANGUAGE", &languageFromEnv))
{
wxStringTokenizer tknzr(languageFromEnv, ":");
while (tknzr.HasMoreTokens())
{
const wxString tok = tknzr.GetNextToken();
if (const wxLanguageInfo* li = wxUILocale::FindLanguageInfo(tok))
{
preferred.push_back(li->CanonicalName);
}
}
if (!preferred.empty())
return preferred;
wxLogTrace(TRACE_I18N, " - LANGUAGE was set, but it didn't contain any languages recognized by the system");
}
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;
if (!wxGetNonEmptyEnvVar(wxS("LC_ALL"), &langFull) &&