From 564de1924adb6cd42a921c9de63d9d96f47a21f4 Mon Sep 17 00:00:00 2001 From: Andrew Lindesay Date: Tue, 9 Mar 2021 22:06:14 +1300 Subject: [PATCH] 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 --- headers/os/locale/NumberFormat.h | 9 +- src/apps/haikudepot/ui/PackageListView.cpp | 12 ++- src/kits/locale/NumberFormat.cpp | 98 ++++++++++++++++---- src/tests/kits/locale/DateFormatTest.cpp | 22 ++--- src/tests/kits/locale/Jamfile | 3 +- src/tests/kits/locale/LanguageTest.cpp | 52 ++++++++++- src/tests/kits/locale/LanguageTest.h | 6 +- src/tests/kits/locale/LocaleKitTestAddon.cpp | 4 +- src/tests/kits/locale/NumberFormatTest.cpp | 83 +++++++++++++++++ src/tests/kits/locale/NumberFormatTest.h | 30 ++++++ src/tests/kits/net/service/DataTest.h | 3 +- 11 files changed, 280 insertions(+), 42 deletions(-) create mode 100644 src/tests/kits/locale/NumberFormatTest.cpp create mode 100644 src/tests/kits/locale/NumberFormatTest.h diff --git a/headers/os/locale/NumberFormat.h b/headers/os/locale/NumberFormat.h index 834632ac9f..76b483dcf9 100644 --- a/headers/os/locale/NumberFormat.h +++ b/headers/os/locale/NumberFormat.h @@ -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); diff --git a/src/apps/haikudepot/ui/PackageListView.cpp b/src/apps/haikudepot/ui/PackageListView.cpp index 64184385d0..b720b37dae 100644 --- a/src/apps/haikudepot/ui/PackageListView.cpp +++ b/src/apps/haikudepot/ui/PackageListView.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020, Andrew Lindesay, . + * Copyright 2018-2021, Andrew Lindesay, . * Copyright 2017, Julian Harnath, . * Copyright 2015, Axel Dörfler, . * Copyright 2013-2014, Stephan Aßmus . @@ -15,12 +15,14 @@ #include #include #include +#include #include #include #include #include #include +#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: diff --git a/src/kits/locale/NumberFormat.cpp b/src/kits/locale/NumberFormat.cpp index 546f576737..90a1be4a6d 100644 --- a/src/kits/locale/NumberFormat.cpp +++ b/src/kits/locale/NumberFormat.cpp @@ -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); } diff --git a/src/tests/kits/locale/DateFormatTest.cpp b/src/tests/kits/locale/DateFormatTest.cpp index 20905f3372..9b25d5a7fa 100644 --- a/src/tests/kits/locale/DateFormatTest.cpp +++ b/src/tests/kits/locale/DateFormatTest.cpp @@ -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); } diff --git a/src/tests/kits/locale/Jamfile b/src/tests/kits/locale/Jamfile index 5d29c72815..e02ae24866 100644 --- a/src/tests/kits/locale/Jamfile +++ b/src/tests/kits/locale/Jamfile @@ -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 diff --git a/src/tests/kits/locale/LanguageTest.cpp b/src/tests/kits/locale/LanguageTest.cpp index 48cabe15a1..7513eb8d11 100644 --- a/src/tests/kits/locale/LanguageTest.cpp +++ b/src/tests/kits/locale/LanguageTest.cpp @@ -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::TestLanguage", &LanguageTest::TestLanguage)); + "LanguageTest::TestLanguageNameFrenchInEnglish", + &LanguageTest::TestLanguageNameFrenchInEnglish)); + suite.addTest(new CppUnit::TestCaller( + "LanguageTest::TestLanguageNameFrenchInFrench", + &LanguageTest::TestLanguageNameFrenchInFrench)); + suite.addTest(new CppUnit::TestCaller( + "LanguageTest::TestLanguagePropertiesFrench", + &LanguageTest::TestLanguagePropertiesFrench)); parent.addTest("LanguageTest", &suite); } diff --git a/src/tests/kits/locale/LanguageTest.h b/src/tests/kits/locale/LanguageTest.h index 5cdf40839d..a1ce5a8b64 100644 --- a/src/tests/kits/locale/LanguageTest.h +++ b/src/tests/kits/locale/LanguageTest.h @@ -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); }; diff --git a/src/tests/kits/locale/LocaleKitTestAddon.cpp b/src/tests/kits/locale/LocaleKitTestAddon.cpp index f2649de282..349225c0f8 100644 --- a/src/tests/kits/locale/LocaleKitTestAddon.cpp +++ b/src/tests/kits/locale/LocaleKitTestAddon.cpp @@ -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); diff --git a/src/tests/kits/locale/NumberFormatTest.cpp b/src/tests/kits/locale/NumberFormatTest.cpp new file mode 100644 index 0000000000..140c4a120c --- /dev/null +++ b/src/tests/kits/locale/NumberFormatTest.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2021 Haiku, Inc. + * Distributed under the terms of the MIT License. + */ +#include "NumberFormatTest.h" + +#include +#include + +#include +#include + + +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% +} + + +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::TestPercentTurkish", + &NumberFormatTest::TestPercentTurkish)); + suite.addTest(new CppUnit::TestCaller( + "NumberFormatTest::TestPercentEnglish", + &NumberFormatTest::TestPercentEnglish)); + suite.addTest(new CppUnit::TestCaller( + "NumberFormatTest::TestPercentGerman", + &NumberFormatTest::TestPercentGerman)); + + parent.addTest("NumberFormatTest", &suite); +} diff --git a/src/tests/kits/locale/NumberFormatTest.h b/src/tests/kits/locale/NumberFormatTest.h new file mode 100644 index 0000000000..e2c408f993 --- /dev/null +++ b/src/tests/kits/locale/NumberFormatTest.h @@ -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 +#include + + +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 diff --git a/src/tests/kits/net/service/DataTest.h b/src/tests/kits/net/service/DataTest.h index 6d1dbae6f5..a45988d082 100644 --- a/src/tests/kits/net/service/DataTest.h +++ b/src/tests/kits/net/service/DataTest.h @@ -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 +#include #include #include