diff --git a/include/wx/private/uilocale.h b/include/wx/private/uilocale.h index af8e3dcedc..6b0e7fbe48 100644 --- a/include/wx/private/uilocale.h +++ b/include/wx/private/uilocale.h @@ -33,9 +33,17 @@ public: // It may return NULL in case of failure. static wxUILocaleImpl* CreateUserDefault(); + // Create locale object for the given locale. + // + // It may return NULL in case of failure. + static wxUILocaleImpl* CreateForLocale(const wxLocaleIdent& locId); + // This function exists only for wxLocale compatibility and creates the // locale corresponding to the given language. // + // It is implemented in terms of CreateForLocale() for non-MSW platforms, + // but under MSW it is different for compatibility reasons. + // // The language passed to this function is a valid language, i.e. neither // wxLANGUAGE_UNKNOWN nor wxLANGUAGE_DEFAULT. // diff --git a/include/wx/uilocale.h b/include/wx/uilocale.h index 8dd81824ad..cb27bb0a70 100644 --- a/include/wx/uilocale.h +++ b/include/wx/uilocale.h @@ -45,6 +45,9 @@ public: // Get the object corresponding to the currently used locale. static const wxUILocale& GetCurrent(); + // Create the object corresponding to the given locale. + explicit wxUILocale(const wxLocaleIdent& localeId); + // Get the platform-dependent name of the current locale. wxString GetName() const; @@ -62,7 +65,8 @@ public: ~wxUILocale(); private: - // Ctor is private, use static accessor to get objects of this class. + // Default ctor is private and exists only for implementation reasons, + // wxUILocale objects can't be invalid. wxUILocale() : m_impl(NULL) { } // Used by UseDefault(). diff --git a/interface/wx/uilocale.h b/interface/wx/uilocale.h index 639e6a71b8..a3ee7ec65e 100644 --- a/interface/wx/uilocale.h +++ b/interface/wx/uilocale.h @@ -94,6 +94,21 @@ public: */ static const wxUILocale& GetCurrent(); + /** + Creates the locale corresponding to the given locale identifier. + + In the simplest case, this can be used as following: + @code + const wxUILocale loc("fr"); + @endcode + but more precise locale identifiers can be used, see wxLocaleIdent + description for more details. + + If @a localeId is not recognized or not supported, default ("C") locale + is used instead. + */ + explicit wxUILocale(const wxLocaleIdent& localeId); + /** Compares two strings using comparison rules of the given locale. diff --git a/src/common/uilocale.cpp b/src/common/uilocale.cpp index 0cd095c879..7dbdc63be5 100644 --- a/src/common/uilocale.cpp +++ b/src/common/uilocale.cpp @@ -37,6 +37,31 @@ wxUILocale wxUILocale::ms_current; // implementation // ============================================================================ +#ifndef __WINDOWS__ + +/* static */ +wxUILocaleImpl* wxUILocaleImpl::CreateForLanguage(const wxLanguageInfo& info) +{ + wxLocaleIdent locId; + + // Strings in our language database are of the form "lang[_region[@mod]]". + wxString rest; + locId.Language(info.CanonicalName.BeforeFirst('_', &rest)); + + if ( !rest.empty() ) + { + wxString mod; + locId.Region(rest.BeforeFirst('@', &mod)); + + if ( !mod.empty() ) + locId.Modifier(mod); + } + + return CreateForLocale(locId); +} + +#endif // !__WINDOWS__ + /* static */ bool wxUILocale::UseDefault() { @@ -84,6 +109,13 @@ const wxUILocale& wxUILocale::GetCurrent() return ms_current; } +wxUILocale::wxUILocale(const wxLocaleIdent& localeId) +{ + m_impl = wxUILocaleImpl::CreateForLocale(localeId); + if ( !m_impl ) + m_impl = wxUILocaleImpl::CreateStdC(); +} + void wxUILocale::SetImpl(wxUILocaleImpl* impl) { delete m_impl; diff --git a/src/msw/uilocale.cpp b/src/msw/uilocale.cpp index ee48f3633f..d3c7079ff7 100644 --- a/src/msw/uilocale.cpp +++ b/src/msw/uilocale.cpp @@ -142,7 +142,7 @@ wxString wxLocaleIdent::GetName() const // LCID-based wxUILocale implementation for MSW // ---------------------------------------------------------------------------- -// TODO-XP: Drop it when we don't support XP any longer. +// TODO-XP: Replace with wxUILocaleImplName when we don't support XP any longer. class wxUILocaleImplLCID : public wxUILocaleImpl { public: @@ -367,6 +367,21 @@ wxUILocaleImpl* wxUILocaleImpl::CreateForLanguage(const wxLanguageInfo& info) return new wxUILocaleImplLCID(info.GetLCID()); } +/* static */ +wxUILocaleImpl* wxUILocaleImpl::CreateForLocale(const wxLocaleIdent& locId) +{ + if ( !wxUILocaleImplName::CanUse() ) + { + // We could try finding the LCID matching the name, but support for XP + // will be dropped soon, so it just doesn't seem worth to do it (note + // that LocaleNameToLCID() itself is not available in XP neither, so we + // can't just use it here). + return NULL; + } + + return new wxUILocaleImplName(locId.GetName()); +} + /* static */ int wxUILocale::CompareStrings(const wxString& lhs, diff --git a/src/osx/core/uilocale.mm b/src/osx/core/uilocale.mm index 7c9625ec28..6fb591dde1 100644 --- a/src/osx/core/uilocale.mm +++ b/src/osx/core/uilocale.mm @@ -78,9 +78,10 @@ public: { } - static wxUILocaleImplCF* Create(const wxString& name) + static wxUILocaleImplCF* Create(const wxLocaleIdent& locId) { - CFLocaleRef cfloc = CFLocaleCreate(kCFAllocatorDefault, wxCFStringRef(name)); + CFLocaleRef cfloc = CFLocaleCreate(kCFAllocatorDefault, + wxCFStringRef(locId.GetName())); if ( !cfloc ) return NULL; @@ -125,7 +126,7 @@ wxUILocaleImplCF::GetInfo(wxLocaleInfo index, wxLocaleCategory cat) const /* static */ wxUILocaleImpl* wxUILocaleImpl::CreateStdC() { - return wxUILocaleImplCF::Create("C"); + return wxUILocaleImplCF::Create(wxLocaleIdent("C")); } /* static */ @@ -135,9 +136,9 @@ wxUILocaleImpl* wxUILocaleImpl::CreateUserDefault() } /* static */ -wxUILocaleImpl* wxUILocaleImpl::CreateForLanguage(const wxLanguageInfo& info) +wxUILocaleImpl* wxUILocaleImpl::CreateForLocale(const wxLocaleIdent& locId) { - return wxUILocaleImplCF::Create(info.CanonicalName); + return wxUILocaleImplCF::Create(locId); } /* static */ diff --git a/src/unix/uilocale.cpp b/src/unix/uilocale.cpp index eccb44594d..3889e6271a 100644 --- a/src/unix/uilocale.cpp +++ b/src/unix/uilocale.cpp @@ -41,8 +41,7 @@ namespace class wxUILocaleImplUnix : public wxUILocaleImpl { public: - // Locale argument may be NULL to not change it at all. - explicit wxUILocaleImplUnix(const char* locale); + explicit wxUILocaleImplUnix(wxLocaleIdent locId); ~wxUILocaleImplUnix() wxOVERRIDE; bool Use() wxOVERRIDE; @@ -50,8 +49,23 @@ public: wxString GetInfo(wxLocaleInfo index, wxLocaleCategory cat) const wxOVERRIDE; private: - // This pointer is owned by this class and may be NULL. - char* const m_name; +#ifdef HAVE_LANGINFO_H + // Call nl_langinfo_l() if available, or nl_langinfo() otherwise. + const char* GetLangInfo(nl_item item) const; +#endif // HAVE_LANGINFO_H + +#ifdef HAVE_LOCALE_T + // On success, set m_locale and change m_locId to the given one. + // Otherwise just return false. + bool TryCreateLocale(const wxLocaleIdent& locId); +#endif // HAVE_LOCALE_T + + wxLocaleIdent m_locId; + +#ifdef HAVE_LOCALE_T + // Initially null, allocated on demand when needed, use GetLocale(). + locale_t m_locale; +#endif // HAVE_LOCALE_T wxDECLARE_NO_COPY_CLASS(wxUILocaleImplUnix); }; @@ -146,26 +160,76 @@ const char *wxSetlocaleTryAll(int c, const wxString& lc) // wxUILocale implementation for Unix // ---------------------------------------------------------------------------- -wxUILocaleImplUnix::wxUILocaleImplUnix(const char* locale) - : m_name(locale ? strdup(locale) : NULL) +#ifdef HAVE_LOCALE_T + +bool +wxUILocaleImplUnix::TryCreateLocale(const wxLocaleIdent& locId) { + m_locale = newlocale(LC_ALL_MASK, locId.GetName(), NULL); + if ( !m_locale ) + return false; + + m_locId = locId; + return true; +} + +#endif // HAVE_LOCALE_T + +wxUILocaleImplUnix::wxUILocaleImplUnix(wxLocaleIdent locId) + : m_locId(locId) +{ +#ifdef HAVE_LOCALE_T + if ( !TryCreateLocale(locId) ) + { + // 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. + if ( locId.GetRegion().empty() ) + { + const wxLanguageInfo* const info = + wxLocale::FindLanguageInfo(locId.GetLanguage()); + if ( info ) + { + wxString region = info->CanonicalName.AfterFirst('_'); + if ( !region.empty() ) + { + // We never have encoding in our canonical names, but we + // can have modifiers, so get rid of them if necessary. + region = region.BeforeFirst('@'); + + TryCreateLocale(locId.Region(region)); + } + } + } + + // And sometimes the locale without encoding is not available, but one + // with UTF-8 encoding is, so try this too. + if ( !m_locale && locId.GetCharset().empty() ) + { + TryCreateLocale(locId.Charset("UTF-8")); + } + } +#endif // HAVE_LOCALE_T } wxUILocaleImplUnix::~wxUILocaleImplUnix() { - free(m_name); +#ifdef HAVE_LOCALE_T + if ( m_locale ) + freelocale(m_locale); +#endif // HAVE_LOCALE_T } bool wxUILocaleImplUnix::Use() { - if ( !m_name ) + if ( m_locId.IsDefault() ) { // This is the default locale, it is already in use. return true; } - const wxString& shortName = wxString::FromAscii(m_name); + const wxString& shortName = m_locId.GetName(); if ( !wxSetlocaleTryAll(LC_ALL, shortName) ) { @@ -194,9 +258,25 @@ wxUILocaleImplUnix::Use() wxString wxUILocaleImplUnix::GetName() const { - return wxString::FromAscii(m_name ? m_name : setlocale(LC_ALL, NULL)); + return m_locId.GetName(); } +#ifdef HAVE_LANGINFO_H + +const char* +wxUILocaleImplUnix::GetLangInfo(nl_item item) const +{ +#ifdef HAVE_LOCALE_T + // We assume that we have nl_langinfo_l() if we have locale_t. + if ( m_locale ) + return nl_langinfo_l(item, m_locale); +#endif // HAVE_LOCALE_T + + return nl_langinfo(item); +} + +#endif // HAVE_LANGINFO_H + wxString wxUILocaleImplUnix::GetInfo(wxLocaleInfo index, wxLocaleCategory cat) const { @@ -206,29 +286,29 @@ wxUILocaleImplUnix::GetInfo(wxLocaleInfo index, wxLocaleCategory cat) const case wxLOCALE_THOUSANDS_SEP: #ifdef MON_THOUSANDS_SEP if ( cat == wxLOCALE_CAT_MONEY ) - return nl_langinfo(MON_THOUSANDS_SEP); + return GetLangInfo(MON_THOUSANDS_SEP); #endif - return nl_langinfo(THOUSEP); + return GetLangInfo(THOUSEP); case wxLOCALE_DECIMAL_POINT: #ifdef MON_DECIMAL_POINT if ( cat == wxLOCALE_CAT_MONEY ) - return nl_langinfo(MON_DECIMAL_POINT); + return GetLangInfo(MON_DECIMAL_POINT); #endif - return nl_langinfo(RADIXCHAR); + return GetLangInfo(RADIXCHAR); case wxLOCALE_SHORT_DATE_FMT: - return nl_langinfo(D_FMT); + return GetLangInfo(D_FMT); case wxLOCALE_DATE_TIME_FMT: - return nl_langinfo(D_T_FMT); + return GetLangInfo(D_T_FMT); case wxLOCALE_TIME_FMT: - return nl_langinfo(T_FMT); + return GetLangInfo(T_FMT); case wxLOCALE_LONG_DATE_FMT: - return wxGetDateFormatOnly(nl_langinfo(D_T_FMT)); + return wxGetDateFormatOnly(GetLangInfo(D_T_FMT)); default: wxFAIL_MSG( "unknown wxLocaleInfo value" ); @@ -249,19 +329,19 @@ wxUILocaleImplUnix::GetInfo(wxLocaleInfo index, wxLocaleCategory cat) const /* static */ wxUILocaleImpl* wxUILocaleImpl::CreateStdC() { - return new wxUILocaleImplUnix(NULL); + return new wxUILocaleImplUnix("C"); } /* static */ wxUILocaleImpl* wxUILocaleImpl::CreateUserDefault() { - return new wxUILocaleImplUnix(""); + return new wxUILocaleImplUnix(wxLocaleIdent()); } /* static */ -wxUILocaleImpl* wxUILocaleImpl::CreateForLanguage(const wxLanguageInfo& info) +wxUILocaleImpl* wxUILocaleImpl::CreateForLocale(const wxLocaleIdent& locId) { - return new wxUILocaleImplUnix(info.CanonicalName); + return new wxUILocaleImplUnix(locId); } #endif // wxUSE_INTL diff --git a/tests/intl/intltest.cpp b/tests/intl/intltest.cpp index a1fdcd09a6..7fa9e1bb30 100644 --- a/tests/intl/intltest.cpp +++ b/tests/intl/intltest.cpp @@ -240,17 +240,17 @@ TEST_CASE("wxLocale::Default", "[locale]") #endif // wxUSE_UNICODE -// This test doesn't run by default as it only works in locales using decimal -// point as separator, which doesn't need to be the case. -TEST_CASE("wxUILocale::GetInfo", "[.][uilocale]") +TEST_CASE("wxUILocale::GetInfo", "[uilocale]") { - REQUIRE( wxUILocale::UseDefault() ); + CHECK( wxUILocale("en").GetInfo(wxLOCALE_DECIMAL_POINT) == "." ); + CHECK( wxUILocale("de").GetInfo(wxLOCALE_DECIMAL_POINT) == "," ); + CHECK( wxUILocale("ru").GetInfo(wxLOCALE_DECIMAL_POINT) == "," ); - const wxUILocale& loc = wxUILocale::GetCurrent(); - - WARN( "Using locale " << loc.GetName() ); - - CHECK( loc.GetInfo(wxLOCALE_DECIMAL_POINT) == "." ); + // This one shows that "Swiss High German" locale (de_CH) correctly uses + // dot, and not comma, as decimal separator, even under macOS, where POSIX + // APIs use incorrect (identical to "German") definitions for this locale. + CHECK( wxUILocale(wxLocaleIdent("de").Region("CH")). + GetInfo(wxLOCALE_DECIMAL_POINT) == "." ); } // Just a small helper to make the test below shorter.