diff --git a/include/wx/uilocale.h b/include/wx/uilocale.h index c0a7c02642..bcf641b57d 100644 --- a/include/wx/uilocale.h +++ b/include/wx/uilocale.h @@ -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 GetPreferredUILanguages(); diff --git a/interface/wx/uilocale.h b/interface/wx/uilocale.h index 0474b609f2..eb6f80e090 100644 --- a/interface/wx/uilocale.h +++ b/interface/wx/uilocale.h @@ -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. diff --git a/samples/internat/internat.cpp b/samples/internat/internat.cpp index 94236a982f..53ca7ebbb1 100644 --- a/samples/internat/internat.cpp +++ b/samples/internat/internat.cpp @@ -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 ) { diff --git a/src/common/intl.cpp b/src/common/intl.cpp index 14ce479622..823c3b3566 100644 --- a/src/common/intl.cpp +++ b/src/common/intl.cpp @@ -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); } diff --git a/src/common/translation.cpp b/src/common/translation.cpp index f806598904..b31f748a71 100644 --- a/src/common/translation.cpp +++ b/src/common/translation.cpp @@ -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 - #include -#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& arr) +{ + wxString s; + for (wxVector::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 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 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 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 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::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); } diff --git a/src/common/uilocale.cpp b/src/common/uilocale.cpp index a5851a91ab..1e90766f84 100644 --- a/src/common/uilocale.cpp +++ b/src/common/uilocale.cpp @@ -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 */ diff --git a/src/msw/uilocale.cpp b/src/msw/uilocale.cpp index 79e831556d..a188146406 100644 --- a/src/msw/uilocale.cpp +++ b/src/msw/uilocale.cpp @@ -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 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 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; // ---------------------------------------------------------------------------- diff --git a/src/unix/uilocale.cpp b/src/unix/uilocale.cpp index b06af8d752..3dfb63ff6a 100644 --- a/src/unix/uilocale.cpp +++ b/src/unix/uilocale.cpp @@ -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 @@ -33,6 +35,8 @@ #include #endif +#define TRACE_I18N wxS("i18n") + namespace { @@ -559,6 +563,32 @@ wxVector wxUILocaleImpl::GetPreferredUILanguages() { wxVector 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) &&