Skip to content

Commit

Permalink
[gnc-ui-utils.cpp] use icu::RBNF to convert numbers to long strings
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherlam committed Apr 10, 2024
1 parent 8c94132 commit b706509
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 130 deletions.
1 change: 0 additions & 1 deletion bindings/app-utils.i
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ GNCPrintAmountInfo gnc_price_print_info (const gnc_commodity *curr,
GNCPrintAmountInfo gnc_share_print_info_places (int decplaces);
const char * xaccPrintAmount (gnc_numeric val, GNCPrintAmountInfo info);

gchar *number_to_words(gdouble val, gint64 denom);
const gchar *printable_value (gdouble val, gint denom);

gboolean gnc_reverse_balance (const Account *account);
Expand Down
152 changes: 24 additions & 128 deletions libgnucash/app-utils/gnc-ui-util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include <string.h>
#include <cinttypes>
#include <unicode/listformatter.h>
#include <unicode/rbnf.h>

#include "qof.h"
#include "gnc-prefs.h"
Expand Down Expand Up @@ -1496,149 +1497,44 @@ gnc_wrap_text_with_bidi_ltr_isolate (const char* text)
/********************************************************************\
********************************************************************/

#define FUDGE .00001

/* This function is basically untranslatable. I'd
guess out of the 29 translations we have, 20 will have their number
wordings in a totally different way than English has (not to
mention gender-dependent number endings). Which means this
word-by-word translation will be useless or even plain
wrong. For this reason, we don't even start to pretend a
word-by-word translation would be of any use, so we don't mark any
of these strings for translation. cstim, 2007-04-15. */
static const char* small_numbers[] =
{
/* Translators: This section is for generating the "amount, in
words" field when printing a check. This function gets the
wording right for English, but unfortunately not for most other
languages. Decide for yourself whether the check printing is
actually needed in your language; if not, you can safely skip the
translation of all of these strings. */
"Zero", "One", "Two", "Three", "Four",
"Five", "Six", "Seven", "Eight", "Nine",
"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen",
"Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen",
"Twenty"
};
static const char* medium_numbers[] =
#ifdef _MSC_VER
static double round(double x)
{
"Zero", "Ten", "Twenty", "Thirty", "Forty",
"Fifty", "Sixty", "Seventy", "Eighty", "Ninety"
};
static const char* big_numbers[] =
{
/* Translators: This is the word for the number 10^2 */
"Hundred",
/* Translators: This is the word for the number 10^3 */
"Thousand",
/* Translators: This is the word for the number 10^6, one thousand
thousands. */
"Million",
/* Translators: This is the word for the number 10^9, one thousand
millions. WATCH OUT: In British English and many other languages
this word is used for 10^12 which is one million millions! In
contrast to this, here in GnuCash this is used in the American
English meaning of 10^9. */
"Billion",
/* Translators: This is the word for the number 10^12, one million
millions. */
"Trillion",
/* Translators: This is the word for the number 10^15 */
"Quadrillion",
/* Translators: This is the word for the number 10^18 */
"Quintillion"
};
// A simple round() implementation because MSVC doesn't seem to have that
return floor(x + 0.5);
}
#endif

static char*
integer_to_words(gint64 val)
static std::string
number_to_words(double val)
{
if (val == 0)
return g_strdup("zero");
if (val < 0)
val = -val;

auto result = g_string_sized_new(100);

while (val >= 1000)
{
int log_val = log10(val) / 3 + FUDGE;
int pow_val = exp(log_val * 3 * G_LN10) + FUDGE;
int this_part = val / pow_val;
val -= this_part * pow_val;
auto tmp = integer_to_words(this_part);
g_string_append_printf(result, "%s %s ", tmp, gettext(big_numbers[log_val]));
g_free(tmp);
}

if (val >= 100)
{
int this_part = val / 100;
val -= this_part * 100;
g_string_append_printf(result, "%s %s ",
gettext(small_numbers[this_part]),
gettext(big_numbers[0]));
}

if (val > 20)
UErrorCode status{U_ZERO_ERROR};
icu::RuleBasedNumberFormat formatter{icu::URBNF_SPELLOUT, icu::Locale{}, status};
icu::UnicodeString result;
std::string words;
if (U_FAILURE(status))
{
int this_part = val / 10;
val -= this_part * 10;
g_string_append(result, gettext(medium_numbers[this_part]));
g_string_append_c(result, ' ');
PERR("Error creating formatter: %s", u_errorName(status));
return "";
}

if (val > 0)
formatter.format (std::fabs(val), result, status);
if (U_FAILURE(status))
{
int this_part = val;
g_string_append(result, gettext(small_numbers[this_part]));
g_string_append_c(result, ' ');
PERR("Error formatting number: %s", u_errorName(status));
return "";
}

result = g_string_truncate(result, result->len - 1);
return g_string_free(result, FALSE);
}

#ifdef _MSC_VER
static double round(double x)
{
// A simple round() implementation because MSVC doesn't seem to have that
return floor(x + 0.5);
}
#endif
result.toUTF8String(words);
DEBUG ("Number %f in words: %s", val, words.c_str());

char*
number_to_words(double val, int64_t denom)
{
if (val < 0) val = -val;
if (denom < 0) denom = -denom;

auto int_part = floor(val);
auto frac_part = static_cast<int64_t>(round((val - int_part) * denom));

auto int_string = integer_to_words(int_part);
/* Inside of the gettext macro _(...) we must not use any macros but
only plain string literals. For this reason, convert the strings
separately. */
auto nomin_string = g_strdup_printf("%02" PRId64, frac_part);
auto denom_string = g_strdup_printf("%" PRId64, denom);
auto full_string =
/* Translators: This is for the "amount, in words" field in check
printing. The first %s is the integer amount of dollars (or
whatever currency), the second and third %s the cent amount as
a fraction, e.g. 47/100. */
g_strdup_printf("%s and %s/%s",
int_string, nomin_string, denom_string);
g_free(int_string);
g_free(nomin_string);
g_free(denom_string);
return full_string;
return words;
}

char*
numeric_to_words(gnc_numeric val)
{
return number_to_words(gnc_numeric_to_double(val),
gnc_numeric_denom(val));
return g_strdup(number_to_words (gnc_numeric_to_double(val)).c_str());
}

const char*
Expand Down
1 change: 0 additions & 1 deletion libgnucash/app-utils/gnc-ui-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,6 @@ const char* xaccPrintAmount (gnc_numeric val, GNCPrintAmountInfo info);
int xaccSPrintAmount (char* buf, gnc_numeric val, GNCPrintAmountInfo info);

const char* printable_value(gdouble val, gint denom);
char* number_to_words(gdouble val, gint64 denom);
char* numeric_to_words(gnc_numeric val);

/** Parses in_str to obtain a numeric result. The
Expand Down

0 comments on commit b706509

Please sign in to comment.