Support more aspects in wxUILocale and wxNumberFormatter

Add support for currency amounts, including functions for retrieving
currency symbol and currency code for the current locale and the
possibility to format such amounts according to the locale rules.

Allow retrieving more information about numeric values formatting, for
both numbers and currency amounts, including grouping information when
group separator is used and number of digits after decimal separator and
also use them when formatting numbers.

Finally add function to retrieve the measurement system (metric or
imperial) used by the current locale.

See #1781.

Closes #25765.
This commit is contained in:
Ulrich Telle
2025-10-09 23:16:22 +02:00
committed by Vadim Zeitlin
parent 9469d6d4c8
commit 8c46e47ecf
13 changed files with 1534 additions and 30 deletions

View File

@@ -12,6 +12,8 @@
#include "wx/defs.h"
#include <vector>
// ----------------------------------------------------------------------------
// wxLayoutDirection: used by wxWindow, wxDC etc
// ----------------------------------------------------------------------------
@@ -122,6 +124,53 @@ enum wxLocaleForm
wxLOCALE_FORM_ENGLISH
};
enum class wxMeasurementSystem
{
Unknown,
Metric,
NonMetric
};
enum class wxCurrencySymbolPosition
{
PrefixNoSep,
PrefixWithSep,
SuffixNoSep,
SuffixWithSep
};
struct wxLocaleNumberFormatting
{
wxLocaleNumberFormatting() = default;
wxLocaleNumberFormatting(const wxString& groupSeparator_, const std::vector<int>& grouping_,
const wxString& decimalSeparator_, int fractionalDigits_)
: groupSeparator(groupSeparator_), grouping(grouping_),
decimalSeparator(decimalSeparator_), fractionalDigits(fractionalDigits_)
{
}
wxString groupSeparator;
std::vector<int> grouping;
wxString decimalSeparator;
int fractionalDigits = 0;
};
struct wxLocaleCurrencyInfo
{
wxLocaleCurrencyInfo() = default;
wxLocaleCurrencyInfo(const wxString& symbol_, const wxString& code_,
const wxCurrencySymbolPosition currencySymbolPos_,
const wxLocaleNumberFormatting currencyFormat_)
: currencySymbol(symbol_), currencyCode(code_),
currencySymbolPos(currencySymbolPos_),
currencyFormat(currencyFormat_)
{
}
wxString currencySymbol; // the currency symbol (for example "$")
wxString currencyCode; // the currency ISO code (for example "USD")
wxCurrencySymbolPosition currencySymbolPos = wxCurrencySymbolPosition::PrefixWithSep;
wxLocaleNumberFormatting currencyFormat;
};
// ----------------------------------------------------------------------------
// wxLanguageInfo: encapsulates wxLanguage to OS native lang.desc.
// translation information

View File

@@ -25,6 +25,9 @@ public:
Style_NoTrailingZeroes = 0x02, // Only for floating point numbers
Style_SignPlus = 0x04,
Style_SignSpace = 0x08,
Style_Currency = 0x10, // Currency, without currency symbol
Style_CurrencySymbol = 0x20, // Currency with currency symbol
Style_CurrencyCode = 0x40, // Currency with ISO 4217 code
};
// Format a number as a string. By default, the thousands separator is
@@ -75,18 +78,24 @@ public:
// number. Also used by ToString().
static void RemoveTrailingZeroes(wxString& s);
// Remove currency symbol or code
static wxString RemoveCurrencySymbolOrCode(wxString s, int style);
private:
// Post-process the string representing an integer.
static wxString PostProcessIntString(wxString s, int style);
// Add the thousands separators to a string representing a number without
// the separators. This is used by ToString(Style_WithThousandsSep).
static void AddThousandsSeparators(wxString& s);
static void AddThousandsSeparators(wxString& s, int style);
// Add the sign prefix to a string representing a number without
// the prefix. This is used by ToString().
static void AddSignPrefix(wxString& s, int style);
// Add currency symbol or code depending on style
static void AddCurrency(wxString& s, int style);
// Remove all thousands separators from a string representing a number.
static void RemoveThousandsSeparators(wxString& s);
};

View File

@@ -96,6 +96,14 @@ public:
#endif // wxUSE_DATETIME
virtual wxLayoutDirection GetLayoutDirection() const = 0;
virtual wxLocaleNumberFormatting GetNumberFormatting() const = 0;
virtual wxString GetCurrencySymbol() const = 0;
virtual wxString GetCurrencyCode() const = 0;
virtual wxCurrencySymbolPosition GetCurrencySymbolPosition() const = 0;
virtual wxLocaleCurrencyInfo GetCurrencyInfo() const = 0;
virtual wxMeasurementSystem UsesMetricSystem() const = 0;
virtual int CompareStrings(const wxString& lhs, const wxString& rhs,
int flags) const = 0;

View File

@@ -186,6 +186,27 @@ public:
// Query the layout direction of the current locale.
wxLayoutDirection GetLayoutDirection() const;
// Query infos about number formatting of the current locale
wxLocaleNumberFormatting GetNumberFormatting() const;
// Query the curreny symbol of the current locale
wxString GetCurrencySymbol() const;
// Query the currency code of the current locale
wxString GetCurrencyCode() const;
// Query the currency symbol position of the current locale
wxCurrencySymbolPosition GetCurrencySymbolPosition() const;
// Query the currency infos of the current locale
wxLocaleCurrencyInfo GetCurrencyInfo() const;
// Query whether the current locale uses the metric system
wxMeasurementSystem UsesMetricSystem() const;
// Guess from the region whether the current locale uses the metric system
static wxMeasurementSystem GuessMetricSystemFromRegion(const wxLocaleIdent& idLocale);
// Compares two strings in the order defined by this locale.
int CompareStrings(const wxString& lhs, const wxString& rhs,
int flags = wxCompare_CaseSensitive) const;

View File

@@ -37,6 +37,8 @@ public:
/**
If this flag is given, thousands separators will be inserted in the
number string representation as defined by the current UI locale.
Thousands separators will be applied according to the grouping rules
of the current UI locale.
*/
Style_WithThousandsSep = 0x01,
@@ -74,6 +76,40 @@ public:
@since 3.3.1
*/
Style_SignSpace = 0x08,
/**
If this flag is given, the number will be interpreted
as a currency value and formatted according to the rules
of the current UI locale.
@since 3.3.2
*/
Style_Currency = 0x10,
/**
If this flag is given, the currency symbol of the current
UI locale will be prepended or appended to the currency value
according to the rules of the current locale.
The style will only take effect, if it is combined with Style_Currency.
The style should not be combined with Style_CurrencyCode.
@since 3.3.2
*/
Style_CurrencySymbol = 0x20, // Currency with currency symbol
/**
If this flag is given, the ISO 42127 currency code of the current
UI locale will be prepended to the currency value. It will be separated
from the currency value by a space character.
The style will only take effect, if it is combined with Style_Currency.
The style should not be combined with Style_CurrencySymbol.
@since 3.3.2
*/
Style_CurrencyCode = 0x40, // Currency with ISO 4217 code
};
/**
@@ -180,4 +216,22 @@ public:
*/
static void RemoveTrailingZeroes(wxString& str);
/**
Remove currency symbol or code, and grouping separators,
according to the given style flags.
@note This function allows to remove the currency symbol or code
from a string with a formatted currency value, so that
FromString() can be used afterwards to retrieve the numerical value.
Additionally, grouping separators are removed, if necessary.
@param[in] str
The string to remove the currency symbol or code or grouping separators from.
@param flags
Combination of values from the Style enumeration.
@since 3.3.2
*/
static wxString RemoveCurrencySymbolOrCode(wxString str, int flags);
};

View File

@@ -296,6 +296,106 @@ public:
*/
wxLayoutDirection GetLayoutDirection() const;
/**
Return all settings related to number formatting for the current locale.
The information includes:
- the grouping separator
- the grouping specification
- the decimal separator
- the number of fractional digits
@return
The wxLocaleNumberFormatting structure with the number formatting details.
@since 3.3.2
*/
wxLocaleNumberFormatting GetNumberFormatting() const;
/**
Query the currency symbol of the current locale.
@return
The currency symbol that is typically used locally for currency values.
If no currency symbol could be determined, an empty string will be returned.
@since 3.3.2
*/
wxString GetCurrencySymbol() const;
/**
Query the ISO 4217 currency code of the current locale.
@return
The 3-letter ISO 4217 currency code.
If no currency code could be determined, an empty string will be returned.
@since 3.3.2
*/
wxString GetCurrencyCode() const;
/**
Query the currency symbol position of the current locale.
The currency symbol can be positioned in one of 4 ways:
as a prefix or suffix to the currency value, either separated from the value
by a space character or not. Accordingly, one of the following values is returned:
- wxCurrencySymbolPosition::PrefixWithSep
- wxCurrencySymbolPosition::PrefixNoSep
- wxCurrencySymbolPosition::SuffixWithSep
- wxCurrencySymbolPosition::SuffixNoSep
@note The position of the currency symbol does not affect the representation
with the currency code. The currency code is always placed before the
currency value and separated by a space.
@return
The currency symbol position.
@since 3.3.2
*/
wxCurrencySymbolPosition GetCurrencySymbolPosition() const;
/**
Return all settings related to currency formatting for the current locale.
The currency information includes the following items:
- the currency symbol
- the currency code
- the currency symbol position
- the currency value formatting information
@return
The wxLocaleCurrencyInfo structure.
@since 3.3.2
*/
wxLocaleCurrencyInfo GetCurrencyInfo() const;
/**
Query whether the current locale uses the metric system
@note If wxMeasurementSystem::Unknown is returned,
GuessMetricSystemFromRegion() can be used as a fallback
@return
The measurement system, one of the values
wxMeasurementSystem::Metric, wxMeasurementSystem::NonMetric,
or wxMeasurementSystem::Unknown.
@since 3.3.2
@see GuessMetricSystemFromRegion()
*/
wxMeasurementSystem UsesMetricSystem() const;
/**
Guess whether the current locale uses the metric system
base on from the region part of the locale identification.
@param idLocale
The id of the locale for which the measurement system should be guessed.
@return
The guessed measurement system, one of the values
wxMeasurementSystem::Metric, wxMeasurementSystem::NonMetric,
or wxMeasurementSystem::Unknown.
@since 3.3.2
*/
static wxMeasurementSystem GuessMetricSystemFromRegion(const wxLocaleIdent& idLocale);
/**
Return true if locale is supported on the current system.

View File

@@ -83,13 +83,15 @@ void ReplaceSeparatorIfNecessary(wxString& s, wxChar sepOld, wxChar sepNew)
wxString wxNumberFormatter::PostProcessIntString(wxString s, int style)
{
if ( style & Style_WithThousandsSep )
AddThousandsSeparators(s);
AddThousandsSeparators(s, style);
wxASSERT_MSG( !(style & Style_NoTrailingZeroes),
"Style_NoTrailingZeroes can't be used with integer values" );
AddSignPrefix(s, style);
AddCurrency(s, style);
return s;
}
@@ -121,13 +123,15 @@ wxString wxNumberFormatter::ToString(double val, int precision, int style)
ReplaceSeparatorIfNecessary(s, '.', GetDecimalSeparator());
if ( style & Style_WithThousandsSep )
AddThousandsSeparators(s);
AddThousandsSeparators(s, style);
if ( style & Style_NoTrailingZeroes )
RemoveTrailingZeroes(s);
AddSignPrefix(s, style);
AddCurrency(s, style);
return s;
}
@@ -148,17 +152,33 @@ wxString wxNumberFormatter::Format(const wxString& format, double val)
return s;
}
void wxNumberFormatter::AddThousandsSeparators(wxString& s)
void wxNumberFormatter::AddThousandsSeparators(wxString& s, int style)
{
// Thousands separators for numbers in scientific format are not relevant.
if ( s.find_first_of("eE") != wxString::npos )
return;
wxChar thousandsSep;
if ( !GetThousandsSeparatorIfUsed(&thousandsSep) )
wxString groupingSeparator;
std::vector<int> grouping;
wxString decimalSeparator = GetDecimalSeparator();
#if wxUSE_INTL
wxLocaleNumberFormatting numForm;
if (style & Style_Currency)
numForm = wxUILocale::GetCurrent().GetCurrencyInfo().currencyFormat;
else
numForm = wxUILocale::GetCurrent().GetNumberFormatting();
if (!numForm.groupSeparator.empty())
{
groupingSeparator = numForm.groupSeparator;
grouping = numForm.grouping;
decimalSeparator = numForm.decimalSeparator;
}
#endif // wxUSE_INTL
if (groupingSeparator.empty() )
return;
size_t pos = s.find(GetDecimalSeparator());
size_t pos = s.find(decimalSeparator);
if ( pos == wxString::npos )
{
// Start grouping at the end of an integer number.
@@ -169,17 +189,47 @@ void wxNumberFormatter::AddThousandsSeparators(wxString& s)
// before their start.
const size_t start = s.find_first_of("0123456789");
// We currently group digits by 3 independently of the locale. This is not
// the right thing to do and we should use lconv::grouping (under POSIX)
// and GetLocaleInfo(LOCALE_SGROUPING) (under MSW) to get information about
// the correct grouping to use. This is something that needs to be done at
// wxLocale level first and then used here in the future (TODO).
const size_t GROUP_LEN = 3;
if (grouping.empty())
return;
while ( pos > start + GROUP_LEN )
// The vector grouping conatins a list of group length.
// Beginning from the right, each group length is applied once
// to determine the next position of a group separator,
// unless the last entry entry is a zero, in which case
// the last group length is applied repeatedly.
size_t groupIndex = 0;
size_t groupLen = grouping[0];
size_t nextGroupLen = groupLen;
// Repeat while the group length is valid (i.e. > 0)
// and the potential group separator position lies
// within the number.
while (groupLen > 0 && pos > start)
{
pos -= GROUP_LEN;
s.insert(pos, thousandsSep);
// Determine next group separator position
pos = (pos >= groupLen) ? pos - groupLen : 0;
// Apply group separator if it is within the number
if (pos > start)
s.insert(pos, groupingSeparator);
// Select next group length
if (++groupIndex < grouping.size())
{
// Set the group length to the next group length entry
// unless the next group length is zero,
// in which case the last group length remains in effect.
nextGroupLen = grouping[groupIndex];
if (nextGroupLen != 0)
groupLen = nextGroupLen;
}
else if (nextGroupLen != 0)
{
// If the next group length is not zero,
// the last group length was already applied once, and
// no further group is to be applied.
groupLen = 0;
}
}
}
@@ -232,6 +282,101 @@ void wxNumberFormatter::AddSignPrefix(wxString& s, int style)
}
}
void wxNumberFormatter::AddCurrency(wxString& s, int style)
{
#if wxUSE_INTL
if (style & Style_Currency)
{
auto currencyInfo = wxUILocale::GetCurrent().GetCurrencyInfo();
wxString currencyStr;
wxCurrencySymbolPosition currencyPos{ wxCurrencySymbolPosition::PrefixWithSep };
if (style & Style_CurrencySymbol)
{
currencyStr = currencyInfo.currencySymbol;
currencyPos = currencyInfo.currencySymbolPos;
}
else if (style & Style_CurrencyCode)
{
currencyStr = currencyInfo.currencyCode;
}
if (!currencyStr.empty())
{
switch (currencyPos)
{
case wxCurrencySymbolPosition::PrefixWithSep:
s = currencyStr + wxString(" ") + s;
break;
case wxCurrencySymbolPosition::PrefixNoSep:
s = currencyStr + s;
break;
case wxCurrencySymbolPosition::SuffixWithSep:
s = s + wxString(" ") + currencyStr;
break;
case wxCurrencySymbolPosition::SuffixNoSep:
s = s + currencyStr;
break;
}
}
}
#else
wxUnused(s);
wxUnused(style);
#endif // wxUSE_INTL
}
wxString wxNumberFormatter::RemoveCurrencySymbolOrCode(wxString s, int style)
{
#if wxUSE_INTL
if (style & Style_Currency)
{
auto currencyInfo = wxUILocale::GetCurrent().GetCurrencyInfo();
wxString thousandsSep = currencyInfo.currencyFormat.groupSeparator;
wxString currencyStr;
wxCurrencySymbolPosition currencyPos{ wxCurrencySymbolPosition::PrefixWithSep };
if (style & Style_CurrencySymbol)
{
currencyStr = currencyInfo.currencySymbol;
currencyPos = currencyInfo.currencySymbolPos;
}
else if (style & Style_CurrencyCode)
{
currencyStr = currencyInfo.currencyCode;
}
if (!currencyStr.empty())
{
wxString valueStr;
switch (currencyPos)
{
case wxCurrencySymbolPosition::PrefixWithSep:
currencyStr += wxString(" ");
// Fall through to case without separator
case wxCurrencySymbolPosition::PrefixNoSep:
if (s.StartsWith(currencyStr, &valueStr))
s = valueStr;
break;
case wxCurrencySymbolPosition::SuffixWithSep:
currencyStr = wxString(" ") + currencyStr;
// Fall through to case without separator
case wxCurrencySymbolPosition::SuffixNoSep:
if (s.EndsWith(currencyStr, &valueStr))
s = valueStr;
break;
}
}
if (style & Style_WithThousandsSep && !thousandsSep.empty())
{
s.Replace(thousandsSep, wxString());
}
}
return s;
#else
wxUnused(style);
return s;
#endif // wxUSE_INTL
}
// ----------------------------------------------------------------------------
// Conversion from strings
// ----------------------------------------------------------------------------

View File

@@ -994,6 +994,70 @@ wxLayoutDirection wxUILocale::GetLayoutDirection() const
return dir;
}
wxLocaleNumberFormatting wxUILocale::GetNumberFormatting() const
{
if (!m_impl)
return wxLocaleNumberFormatting();
return m_impl->GetNumberFormatting();
}
wxString wxUILocale::GetCurrencySymbol() const
{
if (!m_impl)
return wxString();
return m_impl->GetCurrencySymbol();
}
wxString wxUILocale::GetCurrencyCode() const
{
if (!m_impl)
return wxString();
return m_impl->GetCurrencyCode();
}
wxCurrencySymbolPosition wxUILocale::GetCurrencySymbolPosition() const
{
if (!m_impl)
return wxCurrencySymbolPosition::PrefixWithSep;
return m_impl->GetCurrencySymbolPosition();
}
wxLocaleCurrencyInfo wxUILocale::GetCurrencyInfo() const
{
if (!m_impl)
return wxLocaleCurrencyInfo();
return m_impl->GetCurrencyInfo();
}
wxMeasurementSystem wxUILocale::UsesMetricSystem() const
{
if (!m_impl)
return wxMeasurementSystem::Unknown;
return m_impl->UsesMetricSystem();
}
wxMeasurementSystem wxUILocale::GuessMetricSystemFromRegion(const wxLocaleIdent& idLocale)
{
wxString region = idLocale.GetRegion();
// In 2025 only in the United States, Liberia, and Myanmar
// the metric system is not the default measurement system.
if (!region.empty())
{
if (region == "US" || region == "LR" || region == "MM")
return wxMeasurementSystem::NonMetric;
else
return wxMeasurementSystem::Metric;
}
else
return wxMeasurementSystem::Unknown;
}
int
wxUILocale::CompareStrings(const wxString& lhs,
const wxString& rhs,

View File

@@ -28,8 +28,12 @@
#include "wx/scopedarray.h"
#include "wx/dynlib.h"
#include "wx/tokenzr.h"
#include "wx/wxcrt.h"
#include <array>
#include <vector>
#ifndef LOCALE_NAME_USER_DEFAULT
#define LOCALE_NAME_USER_DEFAULT nullptr
#endif
@@ -304,6 +308,45 @@ public:
return wxLayout_Default;
}
wxLocaleNumberFormatting GetNumberFormatting() const override
{
wxLocaleNumberFormatting numForm;
numForm.decimalSeparator = ".";
numForm.groupSeparator = "";
numForm.grouping = {};
numForm.fractionalDigits = 2;
return numForm;
}
wxString GetCurrencySymbol() const override
{
return "$";
}
wxString GetCurrencyCode() const override
{
return "USD";
}
wxCurrencySymbolPosition GetCurrencySymbolPosition() const override
{
return wxCurrencySymbolPosition::PrefixWithSep;
}
wxLocaleCurrencyInfo GetCurrencyInfo() const override
{
return wxLocaleCurrencyInfo(
GetCurrencySymbol(),
GetCurrencyCode(),
GetCurrencySymbolPosition(),
GetNumberFormatting());
}
wxMeasurementSystem UsesMetricSystem() const override
{
return wxMeasurementSystem::Metric;
}
int CompareStrings(const wxString& lhs, const wxString& rhs,
int flags) const override
{
@@ -472,13 +515,15 @@ public:
switch ( index )
{
case wxLOCALE_THOUSANDS_SEP:
str = DoGetInfo(LOCALE_STHOUSAND);
str = DoGetInfo(cat == wxLOCALE_CAT_MONEY
? LOCALE_SMONTHOUSANDSEP
: LOCALE_STHOUSAND);
break;
case wxLOCALE_DECIMAL_POINT:
str = DoGetInfo(cat == wxLOCALE_CAT_MONEY
? LOCALE_SMONDECIMALSEP
: LOCALE_SDECIMAL);
? LOCALE_SMONDECIMALSEP
: LOCALE_SDECIMAL);
break;
case wxLOCALE_SHORT_DATE_FMT:
@@ -642,6 +687,53 @@ public:
return m_layoutDir;
}
wxLocaleNumberFormatting GetNumberFormatting() const override
{
return DoGetNumberFormatting(wxLOCALE_CAT_NUMBER);
}
wxString GetCurrencySymbol() const override
{
return DoGetInfo(LOCALE_SCURRENCY);
}
wxString GetCurrencyCode() const override
{
return wxString(DoGetInfo(LOCALE_SINTLSYMBOL)).Left(3);
}
wxCurrencySymbolPosition GetCurrencySymbolPosition() const override
{
static std::array<wxCurrencySymbolPosition, 4> symPos = {
wxCurrencySymbolPosition::PrefixNoSep, wxCurrencySymbolPosition::SuffixNoSep,
wxCurrencySymbolPosition::PrefixWithSep, wxCurrencySymbolPosition::SuffixWithSep };
wxString posStr = wxString(DoGetInfo(LOCALE_ICURRENCY));
unsigned int posIdx;
return posStr.ToUInt(&posIdx) && posIdx < symPos.size()
? symPos[posIdx]
: wxCurrencySymbolPosition::PrefixWithSep;
}
wxLocaleCurrencyInfo GetCurrencyInfo() const override
{
wxLocaleNumberFormatting currencyFormatting = DoGetNumberFormatting(wxLOCALE_CAT_MONEY);
return wxLocaleCurrencyInfo(
GetCurrencySymbol(),
GetCurrencyCode(),
GetCurrencySymbolPosition(),
currencyFormatting);
}
wxMeasurementSystem UsesMetricSystem() const override
{
wxString str = DoGetInfo(LOCALE_IMEASURE);
if (!str.empty())
{
return (str.IsSameAs("0")) ? wxMeasurementSystem::Metric : wxMeasurementSystem::NonMetric;
}
return wxMeasurementSystem::Unknown;
}
int CompareStrings(const wxString& lhs, const wxString& rhs,
int flags) const override
{
@@ -696,6 +788,36 @@ private:
return buf;
}
wxLocaleNumberFormatting DoGetNumberFormatting(wxLocaleCategory cat) const
{
wxString groupSeparator = DoGetInfo(cat == wxLOCALE_CAT_MONEY
? LOCALE_SMONTHOUSANDSEP
: LOCALE_STHOUSAND);
wxString groupingInfo = DoGetInfo(cat == wxLOCALE_CAT_MONEY
? LOCALE_SMONGROUPING
: LOCALE_SGROUPING);
wxString decimalSeparator = DoGetInfo(cat == wxLOCALE_CAT_MONEY
? LOCALE_SMONDECIMALSEP
: LOCALE_SDECIMAL);
wxString digits = DoGetInfo(cat == wxLOCALE_CAT_MONEY
? LOCALE_ICURRDIGITS
: LOCALE_IDIGITS);
int fractionalDigits;
if (digits.empty() || !digits.ToInt(&fractionalDigits))
fractionalDigits = 0;
// Extract grouping lengths from groupingInfo
std::vector<int> grouping;
for (wxStringTokenizer tokenizer(groupingInfo, ";"); tokenizer.HasMoreTokens();)
{
int value = 0;
if (tokenizer.GetNextToken().ToInt(&value))
grouping.push_back(value);
}
return wxLocaleNumberFormatting(groupSeparator, grouping, decimalSeparator, fractionalDigits);
}
const wchar_t* const m_name;
mutable wxLayoutDirection m_layoutDir = wxLayout_Default;

View File

@@ -27,13 +27,12 @@
#include "wx/osx/core/cfref.h"
#include "wx/osx/core/cfstring.h"
#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>
#import <Foundation/NSLocale.h>
#import <Foundation/NSDateFormatter.h>
#import <Foundation/Foundation.h>
#include "wx/osx/private/uilocale.h"
#include <vector>
extern wxString
wxGetInfoFromCFLocale(CFLocaleRef cfloc, wxLocaleInfo index, wxLocaleCategory cat);
@@ -165,10 +164,20 @@ public:
#endif // wxUSE_DATETIME
wxLayoutDirection GetLayoutDirection() const override;
wxLocaleNumberFormatting GetNumberFormatting() const override;
wxString GetCurrencySymbol() const override;
wxString GetCurrencyCode() const override;
wxCurrencySymbolPosition GetCurrencySymbolPosition() const override;
wxLocaleCurrencyInfo GetCurrencyInfo() const override;
wxMeasurementSystem UsesMetricSystem() const override;
int CompareStrings(const wxString& lhs, const wxString& rhs,
int flags) const override;
private:
wxLocaleNumberFormatting DoGetNumberFormatting(wxLocaleCategory cat) const;
NSLocale* const m_nsloc;
wxDECLARE_NO_COPY_CLASS(wxUILocaleImplCF);
@@ -349,6 +358,132 @@ wxUILocaleImplCF::GetLayoutDirection() const
return wxLayout_Default;
}
wxLocaleNumberFormatting
wxUILocaleImplCF::GetNumberFormatting() const
{
return DoGetNumberFormatting(wxLOCALE_CAT_NUMBER);
}
wxString
wxUILocaleImplCF::GetCurrencySymbol() const
{
NSString* str = [m_nsloc currencySymbol];
return wxCFStringRef::AsString(str);
}
wxString
wxUILocaleImplCF::GetCurrencyCode() const
{
NSString* str = [m_nsloc currencyCode];
return wxCFStringRef::AsString(str);
}
wxCurrencySymbolPosition
wxUILocaleImplCF::GetCurrencySymbolPosition() const
{
wxCFRef<NSNumberFormatter*> cfRefFormatter = [[NSNumberFormatter alloc] init];
NSNumberFormatter* formatter = cfRefFormatter.get();
formatter.locale = m_nsloc;
formatter.numberStyle = NSNumberFormatterCurrencyStyle;
NSString* formatted = [formatter stringFromNumber:@123];
NSString* symbol = formatter.currencySymbol;
NSRange symbolRange = [formatted rangeOfString:symbol];
BOOL symbolBefore = (symbolRange.location == 0);
wxCurrencySymbolPosition symbolPos = wxCurrencySymbolPosition::PrefixWithSep;
BOOL hasSpace = NO;
if (symbolRange.location != NSNotFound)
{
if (symbolBefore)
{
// Check character succeeding the currency symbol
NSUInteger idx = NSMaxRange(symbolRange);
if (idx < formatted.length)
{
unichar after = [formatted characterAtIndex:idx];
hasSpace = [[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:after];
}
symbolPos = (hasSpace)
? wxCurrencySymbolPosition::PrefixWithSep
: wxCurrencySymbolPosition::PrefixNoSep;
}
else
{
// Check character preceding the currency symbol
if (symbolRange.location > 0)
{
unichar before = [formatted characterAtIndex:symbolRange.location - 1];
hasSpace = [[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:before];
}
symbolPos = (hasSpace)
? wxCurrencySymbolPosition::SuffixWithSep
: wxCurrencySymbolPosition::SuffixNoSep;
}
}
return symbolPos;
}
wxLocaleNumberFormatting
wxUILocaleImplCF::DoGetNumberFormatting(wxLocaleCategory cat) const
{
wxCFRef<NSNumberFormatter*> cfRefFormatter = [[NSNumberFormatter alloc] init];
NSNumberFormatter* formatter = cfRefFormatter.get();
formatter.locale = m_nsloc;
formatter.numberStyle = (cat == wxLOCALE_CAT_MONEY)
? NSNumberFormatterCurrencyStyle
: NSNumberFormatterDecimalStyle;
wxString groupSeparator = wxCFStringRef::AsString(formatter.groupingSeparator);
std::vector<int> grouping;
int groupingSize = (int) formatter.groupingSize;
int secondaryGroupingSize = (int) formatter.secondaryGroupingSize;
if (groupingSize > 0)
{
if (secondaryGroupingSize > 0 && secondaryGroupingSize != groupingSize)
{
grouping.push_back(groupingSize);
grouping.push_back(secondaryGroupingSize);
grouping.push_back(0);
}
else
{
grouping.push_back(groupingSize);
grouping.push_back(0);
}
}
wxString decimalSeparator = wxCFStringRef::AsString(formatter.decimalSeparator);
int fractionalDigits = (int) formatter.minimumFractionDigits;
return wxLocaleNumberFormatting(groupSeparator, grouping, decimalSeparator, fractionalDigits);
}
wxLocaleCurrencyInfo
wxUILocaleImplCF::GetCurrencyInfo() const
{
wxLocaleNumberFormatting currencyFormatting = DoGetNumberFormatting(wxLOCALE_CAT_MONEY);
return wxLocaleCurrencyInfo(
GetCurrencySymbol(),
GetCurrencyCode(),
GetCurrencySymbolPosition(),
currencyFormatting);
}
wxMeasurementSystem
wxUILocaleImplCF::UsesMetricSystem() const
{
if ([m_nsloc respondsToSelector:@selector(usesMetricSystem)])
{
BOOL isMetric = [m_nsloc usesMetricSystem];
return (isMetric) ? wxMeasurementSystem::Metric : wxMeasurementSystem::NonMetric;
}
return wxMeasurementSystem::Unknown;
}
/* static */
wxUILocaleImpl* wxUILocaleImpl::CreateStdC()
{

View File

@@ -41,11 +41,27 @@
#include "wx/tokenzr.h"
#include "wx/utils.h"
#include <array>
#include <vector>
#define TRACE_I18N wxS("i18n")
namespace
{
// Enumeration for accessing member variables of the localeconv structure
// used as fallback, if nl_langinfo_l or nl_langinfo are not available
enum wxLocaleConvAttr
{
LOCALE_CONV_THOUSANDS_SEP,
LOCALE_CONV_DECIMAL_POINT,
LOCALE_CONV_GROUPING,
LOCALE_CONV_DIGITS,
LOCALE_CONV_CURRENCY_SYMBOL,
LOCALE_CONV_CURRENCY_CODE,
LOCALE_CONV_CURRENCY_SYM_POS
};
// Small helper function: get the value of the given environment variable and
// return true only if the variable was found and has non-empty value.
inline bool wxGetNonEmptyEnvVar(const wxString& name, wxString* value)
@@ -191,6 +207,13 @@ public:
#endif // wxUSE_DATETIME
wxLayoutDirection GetLayoutDirection() const override;
wxLocaleNumberFormatting GetNumberFormatting() const override;
wxString GetCurrencySymbol() const override;
wxString GetCurrencyCode() const override;
wxCurrencySymbolPosition GetCurrencySymbolPosition() const override;
wxLocaleCurrencyInfo GetCurrencyInfo() const override;
wxMeasurementSystem UsesMetricSystem() const override;
int CompareStrings(const wxString& lhs, const wxString& rhs,
int flags) const override;
@@ -214,6 +237,11 @@ private:
const wxString& GetCodeSet() const;
wxLocaleNumberFormatting DoGetNumberFormatting(wxLocaleCategory cat) const;
#if !(defined(HAVE_LANGINFO_H) && defined(__GLIBC__))
wxString DoGetInfoFromLocaleConv(wxLocaleConvAttr index, wxLocaleCategory cat) const;
#endif
wxLocaleIdent m_locId;
@@ -520,7 +548,7 @@ wxUILocaleImplUnix::GetLangInfo(nl_item item) const
if ( m_locale )
return nl_langinfo_l(item, m_locale);
#else
TempLocaleSetter setThisLocale(LC_CTYPE, m_locId.GetName());
TempLocaleSetter setThisLocale(LC_ALL, m_locId.GetName());
#endif // HAVE_LOCALE_T
return nl_langinfo(item);
@@ -534,7 +562,7 @@ wxUILocaleImplUnix::GetLangInfoWide(nl_item item) const
if ( m_locale )
return (wchar_t*) nl_langinfo_l(item, m_locale);
#else
TempLocaleSetter setThisLocale(LC_CTYPE, m_locId.GetName());
TempLocaleSetter setThisLocale(LC_ALL, m_locId.GetName());
#endif // HAVE_LOCALE_T
return (wchar_t*) nl_langinfo(item);
@@ -591,7 +619,6 @@ wxUILocaleImplUnix::GetInfo(wxLocaleInfo index, wxLocaleCategory cat) const
#else
wxUnusedVar(cat);
#endif
return GetLangInfo(RADIXCHAR);
case wxLOCALE_SHORT_DATE_FMT:
@@ -832,6 +859,88 @@ wxUILocaleImplUnix::GetLayoutDirection() const
return wxLayout_Default;
}
wxLocaleNumberFormatting
wxUILocaleImplUnix::GetNumberFormatting() const
{
return DoGetNumberFormatting(wxLOCALE_CAT_NUMBER);
}
wxString
wxUILocaleImplUnix::GetCurrencySymbol() const
{
#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__)
wxString currencyStr = wxString(GetLangInfo(CURRENCY_SYMBOL), wxCSConv(GetCodeSet()));
if (!currencyStr.empty() &&
(currencyStr[0] == '+' ||
currencyStr[0] == '-' ||
currencyStr[0] == '.'))
{
currencyStr.erase(0, 1);
}
return currencyStr;
#else
return DoGetInfoFromLocaleConv(LOCALE_CONV_CURRENCY_SYMBOL, wxLOCALE_CAT_DEFAULT);
#endif
}
wxString
wxUILocaleImplUnix::GetCurrencyCode() const
{
#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__)
wxString currencyCode = wxString(GetLangInfo(INT_CURR_SYMBOL), wxCSConv(GetCodeSet()));
return currencyCode.Left(3);
#else
return DoGetInfoFromLocaleConv(LOCALE_CONV_CURRENCY_CODE, wxLOCALE_CAT_DEFAULT);
#endif
}
wxCurrencySymbolPosition
wxUILocaleImplUnix::GetCurrencySymbolPosition() const
{
static std::array<wxCurrencySymbolPosition, 4> symPos = {
wxCurrencySymbolPosition::PrefixNoSep, wxCurrencySymbolPosition::SuffixNoSep,
wxCurrencySymbolPosition::PrefixWithSep, wxCurrencySymbolPosition::SuffixWithSep };
wxUint32 posIdx = 0;
#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__)
const char* csPrecedes = GetLangInfo(P_CS_PRECEDES);
const char* sepBySpace = GetLangInfo(P_SEP_BY_SPACE);
posIdx += (csPrecedes[0] == 0) ? 1 : 0;
posIdx += (sepBySpace[0] == 0) ? 0 : 2;
#else
wxString symbolPosition = DoGetInfoFromLocaleConv(LOCALE_CONV_CURRENCY_SYM_POS, wxLOCALE_CAT_DEFAULT);
posIdx = symbolPosition.GetChar(0).GetValue();
#endif
return (posIdx < symPos.size()) ? symPos[posIdx] : wxCurrencySymbolPosition::PrefixWithSep;
}
wxLocaleCurrencyInfo
wxUILocaleImplUnix::GetCurrencyInfo() const
{
wxLocaleNumberFormatting currencyFormatting = DoGetNumberFormatting(wxLOCALE_CAT_MONEY);
return wxLocaleCurrencyInfo(
GetCurrencySymbol(),
GetCurrencyCode(),
GetCurrencySymbolPosition(),
currencyFormatting);
}
wxMeasurementSystem
wxUILocaleImplUnix::UsesMetricSystem() const
{
#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__)
wxString measureStr = GetLangInfo(_NL_MEASUREMENT_MEASUREMENT);
if (!measureStr.empty() && measureStr[0].GetValue() == 1)
return wxMeasurementSystem::Metric;
else if (!measureStr.empty() && measureStr[0].GetValue() == 2)
return wxMeasurementSystem::NonMetric;
else
return wxMeasurementSystem::Unknown;
#else
return wxUILocale::GuessMetricSystemFromRegion(GetLocaleId());
#endif
}
int
wxUILocaleImplUnix::CompareStrings(const wxString& lhs, const wxString& rhs,
int WXUNUSED(flags)) const
@@ -854,6 +963,166 @@ wxUILocaleImplUnix::CompareStrings(const wxString& lhs, const wxString& rhs,
return 0;
}
wxLocaleNumberFormatting
wxUILocaleImplUnix::DoGetNumberFormatting(wxLocaleCategory cat) const
{
wxString groupSeparator;
#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__)
const char* groupSep =
#ifdef MON_THOUSANDS_SEP
(cat == wxLOCALE_CAT_MONEY)
? GetLangInfo(MON_THOUSANDS_SEP)
: GetLangInfo(THOUSEP);
#else
GetLangInfo(THOUSEP);
#endif
groupSeparator = wxString(groupSep, wxCSConv(GetCodeSet()));
#else
groupSeparator = DoGetInfoFromLocaleConv(LOCALE_CONV_THOUSANDS_SEP, cat);
#endif
std::vector<int> grouping;
#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__)
const char* groupingInfo =
#ifdef MON_GROUPING
(cat == wxLOCALE_CAT_MONEY)
? GetLangInfo(MON_GROUPING)
: GetLangInfo(GROUPING);
#else
GetLangInfo(GROUPING);
#endif
// Include the terminating '\0' in total length
size_t groupLen = strlen(groupingInfo) + 1;
for (size_t j = 0; j < groupLen; ++j)
{
auto val = groupingInfo[j];
if (val == CHAR_MAX)
break;
grouping.push_back(static_cast<int>(val));
}
#else
wxString groupingStr = DoGetInfoFromLocaleConv(LOCALE_CONV_GROUPING, cat);
for (auto ch : groupingStr)
{
auto val = ch.GetValue();
if (val == CHAR_MAX)
break;
grouping.push_back(static_cast<int>(val));
}
#endif
wxString decimalSeparator;
#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__)
const char* decimalSep =
#ifdef MON_DECIMAL_POINT
(cat == wxLOCALE_CAT_MONEY)
? GetLangInfo(MON_DECIMAL_POINT)
: GetLangInfo(RADIXCHAR);
#else
GetLangInfo(RADIXCHAR);
#endif
decimalSeparator = wxString(decimalSep, wxCSConv(GetCodeSet()));
#else
decimalSeparator = DoGetInfoFromLocaleConv(LOCALE_CONV_DECIMAL_POINT, cat);
#endif
int fractionalDigits = 0;
#if defined(HAVE_LANGINFO_H) && defined(__GLIBC__)
const char* fracDigits = GetLangInfo(FRAC_DIGITS);
fractionalDigits = (fracDigits) ? fracDigits[0] : 0;
#else
wxString fracDigits = DoGetInfoFromLocaleConv(LOCALE_CONV_DIGITS, cat);
fractionalDigits = (!fracDigits.empty()) ? fracDigits.GetChar(0).GetValue() : 0;
#endif
return wxLocaleNumberFormatting(groupSeparator, grouping, decimalSeparator, fractionalDigits);
}
#if !(defined(HAVE_LANGINFO_H) && defined(__GLIBC__))
wxString
wxUILocaleImplUnix::DoGetInfoFromLocaleConv(wxLocaleConvAttr index, wxLocaleCategory cat) const
{
// localeconv accesses only the global locale
// temporarily set this locale
TempLocaleSetter setThisLocale(LC_ALL, m_locId.GetName());
lconv* const lc = localeconv();
if (!lc)
return wxString();
switch (index)
{
case LOCALE_CONV_THOUSANDS_SEP:
{
if (cat == wxLOCALE_CAT_MONEY)
return wxString(lc->mon_thousands_sep, wxConvLibc);
else
return wxString(lc->thousands_sep, wxConvLibc);
}
case LOCALE_CONV_DECIMAL_POINT:
{
if (cat == wxLOCALE_CAT_MONEY)
return wxString(lc->mon_decimal_point, wxConvLibc);
else
return wxString(lc->decimal_point, wxConvLibc);
}
case LOCALE_CONV_GROUPING:
{
wxString groupingStr;
const char* grouping;
if (cat == wxLOCALE_CAT_MONEY)
grouping = lc->mon_grouping;
else
grouping = lc->grouping;
size_t groupLen = strlen(grouping);
for (size_t j = 0; j < groupLen; ++j)
groupingStr.Append(wxUniChar(grouping[j]));
groupingStr.Append(wxUniChar(0));
return groupingStr;
}
case LOCALE_CONV_CURRENCY_SYMBOL:
{
return wxString(lc->currency_symbol, wxConvLibc);
}
case LOCALE_CONV_CURRENCY_CODE:
{
return wxString(lc->int_curr_symbol, wxConvLibc).Left(3);
}
case LOCALE_CONV_DIGITS:
{
wxString currencyDigitsStr(wxUniChar(lc->frac_digits));
return currencyDigitsStr;
}
case LOCALE_CONV_CURRENCY_SYM_POS:
{
char csPrecedes = lc->p_cs_precedes;
char sepBySpace = lc->p_sep_by_space;
wxUint32 posIdx = 0;
posIdx += (csPrecedes == 0) ? 1 : 0;
posIdx += (sepBySpace == 0) ? 0 : 2;
wxString symbolPosition;
symbolPosition.Append(wxUniChar(posIdx));
return symbolPosition;
}
default:
wxFAIL_MSG("unknown localeconv value");
}
return wxString();
}
#endif
/* static */
wxUILocaleImpl* wxUILocaleImpl::CreateStdC()
{

View File

@@ -417,11 +417,35 @@ TEST_CASE("wxUILocale::IsSupported", "[uilocale]")
TEST_CASE("wxUILocale::GetInfo", "[uilocale]")
{
CHECK( wxUILocale::FromTag("en").GetInfo(wxLOCALE_DECIMAL_POINT) == "." );
const wxUILocale locEN(wxUILocale::FromTag("en-US"));
CHECK( locEN.GetInfo(wxLOCALE_DECIMAL_POINT) == "." );
CHECK( locEN.GetCurrencySymbol() == "$");
CHECK( locEN.GetCurrencyCode() == "USD");
CHECK( locEN.GetCurrencyInfo().currencyFormat.fractionalDigits == 2);
CHECK( locEN.GetCurrencySymbolPosition() == wxCurrencySymbolPosition::PrefixNoSep );
CHECK( locEN.UsesMetricSystem() == wxMeasurementSystem::NonMetric);
const wxUILocale locDE(wxUILocale::FromTag("de"));
if ( CheckSupported(locDE, "German") )
CHECK( locDE.GetInfo(wxLOCALE_DECIMAL_POINT) == "," );
const wxUILocale locDE(wxUILocale::FromTag("de-DE"));
if (CheckSupported(locDE, "German"))
{
CHECK( locDE.GetInfo(wxLOCALE_DECIMAL_POINT) == ",");
CHECK( locDE.GetCurrencySymbol() == L"\u20AC");
CHECK( locDE.GetCurrencyCode() == "EUR");
CHECK( locDE.GetCurrencyInfo().currencyFormat.fractionalDigits == 2);
CHECK( locDE.GetCurrencySymbolPosition() == wxCurrencySymbolPosition::SuffixWithSep);
CHECK( locDE.UsesMetricSystem() == wxMeasurementSystem::Metric);
}
const wxUILocale locFR(wxUILocale::FromTag("fr-FR"));
if (CheckSupported(locFR, "French"))
{
CHECK( locFR.GetInfo(wxLOCALE_DECIMAL_POINT) == ",");
CHECK( locFR.GetCurrencySymbol() == L"\u20AC");
CHECK( locFR.GetCurrencyCode() == "EUR");
CHECK( locFR.GetCurrencyInfo().currencyFormat.fractionalDigits == 2);
CHECK( locFR.GetCurrencySymbolPosition() == wxCurrencySymbolPosition::SuffixWithSep);
CHECK( locFR.UsesMetricSystem() == wxMeasurementSystem::Metric);
}
// This one shows that "Swiss High German" locale (de_CH) correctly uses
// dot, and not comma, as decimal separator, even under macOS, where POSIX

File diff suppressed because it is too large Load Diff