Allow creating wxUILocale objects for any locale

Creating such objects (without using them for the UI) is supported under
all platforms, so allow doing it.

Note that this is only supported under Unix systems when locale_t and
related functionality is available, but this should be the case just
about everywhere by now.

Add a test (or, rather, replace an existing test which was disabled by
default) checking that we can now get locale information about any
locale, not necessarily the currently used one.
This commit is contained in:
Vadim Zeitlin
2021-08-29 14:50:54 +02:00
parent 07e79b7736
commit 45f9908e05
8 changed files with 193 additions and 38 deletions

View File

@@ -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.
//

View File

@@ -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().

View File

@@ -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.

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 */

View File

@@ -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

View File

@@ -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.