Locale: Support for Percent Formatting

Updates BNumberFormat to be able to format
percentages.  Also re-introduces some unit
tests and updates the BNumberFormat ones.

This doesn't actually fix #16312 as the
defaults for percentage formatting don't seem
to track the selected language, but goes part
way there.

Related to #16312

Change-Id: Id6ddf426ce5571f4e8513c0eb1663cf42ac53cb1
Reviewed-on: https://review.haiku-os.org/c/haiku/+/3767
Reviewed-by: Adrien Destugues <pulkomandy@gmail.com>
This commit is contained in:
Andrew Lindesay 2021-03-09 22:06:14 +13:00
parent 4c2d3a38ad
commit 564de1924a
11 changed files with 280 additions and 42 deletions

View File

@ -19,7 +19,7 @@ class BNumberFormatImpl;
class BNumberFormat : public BFormat {
public:
BNumberFormat();
BNumberFormat(const BLocale* locale = NULL);
~BNumberFormat();
ssize_t Format(char* string, size_t maxSize,
@ -34,9 +34,14 @@ public:
status_t FormatMonetary(BString& string,
const double value);
ssize_t FormatPercent(char* string, size_t maxSize,
const double value);
status_t FormatPercent(BString& string,
const double value);
status_t Parse(const BString& string, double& value);
BString GetSeparator(BNumberElement element);
BString GetSeparator(BNumberElement element);
private:
BNumberFormat(const BNumberFormat &other);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2018-2020, Andrew Lindesay, <apl@lindesay.co.nz>.
* Copyright 2018-2021, Andrew Lindesay, <apl@lindesay.co.nz>.
* Copyright 2017, Julian Harnath, <julian.harnath@rwth-aachen.de>.
* Copyright 2015, Axel Dörfler, <axeld@pinc-software.de>.
* Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
@ -15,12 +15,14 @@
#include <Autolock.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <NumberFormat.h>
#include <ScrollBar.h>
#include <StringFormat.h>
#include <StringForSize.h>
#include <package/hpkg/Strings.h>
#include <Window.h>
#include "Logger.h"
#include "MainWindow.h"
#include "WorkStatusView.h"
@ -40,6 +42,8 @@ static const char* skPackageStatePending = B_TRANSLATE_MARK(
inline BString
package_state_to_string(PackageInfoRef ref)
{
static BNumberFormat numberFormat;
switch (ref->State()) {
case NONE:
return B_TRANSLATE(skPackageStateAvailable);
@ -52,7 +56,11 @@ package_state_to_string(PackageInfoRef ref)
case DOWNLOADING:
{
BString data;
data.SetToFormat("%3.2f%%", ref->DownloadProgress() * 100.0);
float fraction = ref->DownloadProgress();
if (numberFormat.FormatPercent(data, fraction) != B_OK) {
HDERROR("unable to format the percentage");
data = "???";
}
return data;
}
case PENDING:

View File

@ -3,6 +3,7 @@
* Copyright 2010-2011, Oliver Tappe, zooey@hirschkaefer.de.
* Copyright 2012, John Scipione, jscipione@gmail.com
* Copyright 2017, Adrien Destugues, pulkomandy@pulkomandy.tk
* Copyright 2021, Andrew Lindesay, apl@lindesay.co.nz
* All rights reserved. Distributed under the terms of the MIT License.
*/
@ -32,11 +33,18 @@ public:
NumberFormat* GetInteger(BFormattingConventions* convention);
NumberFormat* GetFloat(BFormattingConventions* convention);
NumberFormat* GetCurrency(BFormattingConventions* convention);
NumberFormat* GetPercent(BFormattingConventions* convention);
ssize_t ApplyFormatter(NumberFormat* formatter, char* string,
size_t maxSize, const double value);
status_t ApplyFormatter(NumberFormat* formatter, BString& string,
const double value);
private:
NumberFormat* fIntegerFormat;
NumberFormat* fFloatFormat;
NumberFormat* fCurrencyFormat;
NumberFormat* fPercentFormat;
};
@ -46,6 +54,7 @@ BNumberFormatImpl::BNumberFormatImpl()
fIntegerFormat = NULL;
fFloatFormat = NULL;
fCurrencyFormat = NULL;
fPercentFormat = NULL;
}
@ -54,6 +63,7 @@ BNumberFormatImpl::~BNumberFormatImpl()
delete fIntegerFormat;
delete fFloatFormat;
delete fCurrencyFormat;
delete fPercentFormat;
}
@ -123,8 +133,61 @@ BNumberFormatImpl::GetCurrency(BFormattingConventions* convention)
}
BNumberFormat::BNumberFormat()
: BFormat()
NumberFormat*
BNumberFormatImpl::GetPercent(BFormattingConventions* convention)
{
if (fPercentFormat == NULL) {
UErrorCode err = U_ZERO_ERROR;
fPercentFormat = NumberFormat::createInstance(
*BFormattingConventions::Private(convention).ICULocale(),
UNUM_PERCENT, err);
if (fPercentFormat == NULL)
return NULL;
if (U_FAILURE(err)) {
delete fPercentFormat;
fPercentFormat = NULL;
return NULL;
}
}
return fPercentFormat;
}
ssize_t
BNumberFormatImpl::ApplyFormatter(NumberFormat* formatter, char* string,
size_t maxSize, const double value)
{
BString fullString;
status_t status = ApplyFormatter(formatter, fullString, value);
if (status != B_OK)
return status;
return strlcpy(string, fullString.String(), maxSize);
}
status_t
BNumberFormatImpl::ApplyFormatter(NumberFormat* formatter, BString& string,
const double value)
{
if (formatter == NULL)
return B_NO_MEMORY;
UnicodeString icuString;
formatter->format(value, icuString);
string.Truncate(0);
BStringByteSink stringConverter(&string);
icuString.toUTF8(stringConverter);
return B_OK;
}
BNumberFormat::BNumberFormat(const BLocale* locale)
: BFormat(locale)
{
fPrivateData = new BNumberFormatImpl();
}
@ -211,31 +274,32 @@ BNumberFormat::Format(BString& string, const int32 value)
ssize_t
BNumberFormat::FormatMonetary(char* string, size_t maxSize, const double value)
{
BString fullString;
status_t status = FormatMonetary(fullString, value);
if (status != B_OK)
return status;
return strlcpy(string, fullString.String(), maxSize);
return fPrivateData->ApplyFormatter(
fPrivateData->GetCurrency(&fConventions), string, maxSize, value);
}
status_t
BNumberFormat::FormatMonetary(BString& string, const double value)
{
NumberFormat* formatter = fPrivateData->GetCurrency(&fConventions);
return fPrivateData->ApplyFormatter(
fPrivateData->GetCurrency(&fConventions), string, value);
}
if (formatter == NULL)
return B_NO_MEMORY;
UnicodeString icuString;
formatter->format(value, icuString);
ssize_t
BNumberFormat::FormatPercent(char* string, size_t maxSize, const double value)
{
return fPrivateData->ApplyFormatter(
fPrivateData->GetPercent(&fConventions), string, maxSize, value);
}
string.Truncate(0);
BStringByteSink stringConverter(&string);
icuString.toUTF8(stringConverter);
return B_OK;
status_t
BNumberFormat::FormatPercent(BString& string, const double value)
{
return fPrivateData->ApplyFormatter(
fPrivateData->GetPercent(&fConventions), string, value);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 Haiku, Inc.
* Copyright 2014-2021 Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
@ -279,61 +279,61 @@ DateFormatTest::TestDayNames()
BString buffer;
status_t result = format.GetDayName(1, buffer);
CPPUNIT_ASSERT_EQUAL(BString("Sunday"), buffer);
CPPUNIT_ASSERT_EQUAL(BString("Monday"), buffer);
CPPUNIT_ASSERT_EQUAL(B_OK, result);
buffer.Truncate(0);
result = format.GetDayName(2, buffer);
CPPUNIT_ASSERT_EQUAL(BString("Monday"), buffer);
CPPUNIT_ASSERT_EQUAL(BString("Tuesday"), buffer);
CPPUNIT_ASSERT_EQUAL(B_OK, result);
buffer.Truncate(0);
result = format.GetDayName(1, buffer, B_FULL_DATE_FORMAT);
CPPUNIT_ASSERT_EQUAL(BString("Sunday"), buffer);
CPPUNIT_ASSERT_EQUAL(BString("Monday"), buffer);
CPPUNIT_ASSERT_EQUAL(B_OK, result);
buffer.Truncate(0);
result = format.GetDayName(2, buffer, B_FULL_DATE_FORMAT);
CPPUNIT_ASSERT_EQUAL(BString("Monday"), buffer);
CPPUNIT_ASSERT_EQUAL(BString("Tuesday"), buffer);
CPPUNIT_ASSERT_EQUAL(B_OK, result);
buffer.Truncate(0);
result = format.GetDayName(1, buffer, B_LONG_DATE_FORMAT);
CPPUNIT_ASSERT_EQUAL(BString("Sun"), buffer);
CPPUNIT_ASSERT_EQUAL(BString("Mon"), buffer);
CPPUNIT_ASSERT_EQUAL(B_OK, result);
buffer.Truncate(0);
result = format.GetDayName(2, buffer, B_LONG_DATE_FORMAT);
CPPUNIT_ASSERT_EQUAL(BString("Mon"), buffer);
CPPUNIT_ASSERT_EQUAL(BString("Tue"), buffer);
CPPUNIT_ASSERT_EQUAL(B_OK, result);
buffer.Truncate(0);
result = format.GetDayName(1, buffer, B_MEDIUM_DATE_FORMAT);
CPPUNIT_ASSERT_EQUAL(BString("Su"), buffer);
CPPUNIT_ASSERT_EQUAL(BString("Mo"), buffer);
CPPUNIT_ASSERT_EQUAL(B_OK, result);
buffer.Truncate(0);
result = format.GetDayName(2, buffer, B_MEDIUM_DATE_FORMAT);
CPPUNIT_ASSERT_EQUAL(BString("Mo"), buffer);
CPPUNIT_ASSERT_EQUAL(BString("Tu"), buffer);
CPPUNIT_ASSERT_EQUAL(B_OK, result);
buffer.Truncate(0);
result = format.GetDayName(1, buffer, B_SHORT_DATE_FORMAT);
CPPUNIT_ASSERT_EQUAL(BString("S"), buffer);
CPPUNIT_ASSERT_EQUAL(BString("M"), buffer);
CPPUNIT_ASSERT_EQUAL(B_OK, result);
buffer.Truncate(0);
result = format.GetDayName(2, buffer, B_SHORT_DATE_FORMAT);
CPPUNIT_ASSERT_EQUAL(BString("M"), buffer);
CPPUNIT_ASSERT_EQUAL(BString("T"), buffer);
CPPUNIT_ASSERT_EQUAL(B_OK, result);
}

View File

@ -43,13 +43,14 @@ Addon catalogTestAddOn
: be
;
UnitTestLib localekittest.so :
UnitTestLib liblocaletest.so :
LocaleKitTestAddon.cpp
CollatorTest.cpp
DateFormatTest.cpp
DurationFormatTest.cpp
LanguageTest.cpp
NumberFormatTest.cpp
RelativeDateTimeFormatTest.cpp
StringFormatTest.cpp
UnicodeCharTest.cpp

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 Haiku, Inc.
* Copyright 2014-2021 Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
@ -23,12 +23,47 @@ LanguageTest::~LanguageTest()
void
LanguageTest::TestLanguage()
LanguageTest::TestLanguageNameFrenchInEnglish()
{
BLanguage language("fr_FR");
// GIVEN
BLanguage languageFrench("fr_FR");
BLanguage languageEnglish("en_US");
BString name;
language.GetName(name);
// WHEN
languageFrench.GetName(name, &languageEnglish);
// get the name of French in English
// THEN
CPPUNIT_ASSERT_EQUAL(BString("French (France)"), name);
}
void
LanguageTest::TestLanguageNameFrenchInFrench()
{
// GIVEN
BLanguage languageFrench("fr_FR");
BString name;
// WHEN
languageFrench.GetName(name, &languageFrench);
// get the name of French in French
// THEN
CPPUNIT_ASSERT_EQUAL(BString("français (France)"), name);
}
void
LanguageTest::TestLanguagePropertiesFrench()
{
// GIVEN
BLanguage language("fr_FR");
// WHEN
// THEN
CPPUNIT_ASSERT_EQUAL(BString("fr"), language.Code());
CPPUNIT_ASSERT(language.Direction() == B_LEFT_TO_RIGHT);
}
@ -40,7 +75,14 @@ LanguageTest::AddTests(BTestSuite& parent)
CppUnit::TestSuite& suite = *new CppUnit::TestSuite("LanguageTest");
suite.addTest(new CppUnit::TestCaller<LanguageTest>(
"LanguageTest::TestLanguage", &LanguageTest::TestLanguage));
"LanguageTest::TestLanguageNameFrenchInEnglish",
&LanguageTest::TestLanguageNameFrenchInEnglish));
suite.addTest(new CppUnit::TestCaller<LanguageTest>(
"LanguageTest::TestLanguageNameFrenchInFrench",
&LanguageTest::TestLanguageNameFrenchInFrench));
suite.addTest(new CppUnit::TestCaller<LanguageTest>(
"LanguageTest::TestLanguagePropertiesFrench",
&LanguageTest::TestLanguagePropertiesFrench));
parent.addTest("LanguageTest", &suite);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 Haiku, Inc.
* Copyright 2014-2021 Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#ifndef LANGUAGE_TEST_H
@ -15,7 +15,9 @@ public:
LanguageTest();
virtual ~LanguageTest();
void TestLanguage();
void TestLanguageNameFrenchInEnglish();
void TestLanguageNameFrenchInFrench();
void TestLanguagePropertiesFrench();
static void AddTests(BTestSuite& suite);
};

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 Haiku, Inc.
* Copyright 2014-2021 Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
@ -11,6 +11,7 @@
#include "DateFormatTest.h"
#include "DurationFormatTest.h"
#include "LanguageTest.h"
#include "NumberFormatTest.h"
#include "RelativeDateTimeFormatTest.h"
#include "StringFormatTest.h"
#include "UnicodeCharTest.h"
@ -25,6 +26,7 @@ getTestSuite()
DateFormatTest::AddTests(*suite);
DurationFormatTest::AddTests(*suite);
LanguageTest::AddTests(*suite);
NumberFormatTest::AddTests(*suite);
StringFormatTest::AddTests(*suite);
RelativeDateTimeFormatTest::AddTests(*suite);
UnicodeCharTest::AddTests(*suite);

View File

@ -0,0 +1,83 @@
/*
* Copyright 2021 Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#include "NumberFormatTest.h"
#include <NumberFormat.h>
#include <Locale.h>
#include <cppunit/TestCaller.h>
#include <cppunit/TestSuite.h>
NumberFormatTest::NumberFormatTest()
{
}
NumberFormatTest::~NumberFormatTest()
{
}
void
NumberFormatTest::TestPercentTurkish()
{
_TestGeneralPercent("tr", "%2");
}
void
NumberFormatTest::TestPercentEnglish()
{
_TestGeneralPercent("en_US", "2%");
}
void
NumberFormatTest::TestPercentGerman()
{
_TestGeneralPercent("de", "2\xc2\xa0%");
// 2<non-breaking-space>%
}
void
NumberFormatTest::_TestGeneralPercent(const char* languageCode,
const char* expected)
{
// GIVEN
BLanguage turkishLanguage(languageCode);
BFormattingConventions formattingConventions(languageCode);
BLocale turkishLocale(&turkishLanguage, &formattingConventions);
BNumberFormat numberFormat(&turkishLocale);
BString output;
double input = 0.025;
// WHEN
status_t result = numberFormat.FormatPercent(output, input);
// THEN
CPPUNIT_ASSERT_EQUAL(B_OK, result);
CPPUNIT_ASSERT_EQUAL(BString(expected), output);
}
/*static*/ void
NumberFormatTest::AddTests(BTestSuite& parent)
{
CppUnit::TestSuite& suite = *new CppUnit::TestSuite("NumberFormatTest");
suite.addTest(new CppUnit::TestCaller<NumberFormatTest>(
"NumberFormatTest::TestPercentTurkish",
&NumberFormatTest::TestPercentTurkish));
suite.addTest(new CppUnit::TestCaller<NumberFormatTest>(
"NumberFormatTest::TestPercentEnglish",
&NumberFormatTest::TestPercentEnglish));
suite.addTest(new CppUnit::TestCaller<NumberFormatTest>(
"NumberFormatTest::TestPercentGerman",
&NumberFormatTest::TestPercentGerman));
parent.addTest("NumberFormatTest", &suite);
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2021 Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#ifndef NUMBER_FORMAT_TEST_H
#define NUMBER_FORMAT_TEST_H
#include <TestCase.h>
#include <TestSuite.h>
class NumberFormatTest: public BTestCase {
public:
NumberFormatTest();
virtual ~NumberFormatTest();
void TestPercentTurkish();
void TestPercentEnglish();
void TestPercentGerman();
static void AddTests(BTestSuite& suite);
private:
void _TestGeneralPercent(const char* languageCode,
const char* expected);
};
#endif

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 Haiku, inc.
* Copyright 2014-2021 Haiku, inc.
* Distributed under the terms of the MIT License.
*/
@ -9,6 +9,7 @@
#include <Url.h>
#include <UrlProtocolListener.h>
#include <TestCase.h>
#include <TestSuite.h>