Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[gnc-ui-utils.cpp] use icu::RBNF to convert numbers to long strings #1913

Draft
wants to merge 2 commits into
base: stable
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
162 changes: 37 additions & 125 deletions libgnucash/app-utils/gnc-ui-util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

#include <config.h>

#include <numeric>

#ifdef __MINGW32__
#define __USE_MINGW_ANSI_STDIO 1
#endif
Expand All @@ -43,6 +45,7 @@
#include <string.h>
#include <cinttypes>
#include <unicode/listformatter.h>
#include <boost/locale.hpp>

#include "qof.h"
#include "gnc-prefs.h"
Expand All @@ -54,6 +57,7 @@
#include "gnc-session.h"
#include "engine-helpers.h"
#include "gnc-locale-utils.h"
#include "gnc-locale-utils.hpp"

#define GNC_PREF_CURRENCY_CHOICE_LOCALE "currency-choice-locale"
#define GNC_PREF_CURRENCY_CHOICE_OTHER "currency-choice-other"
Expand Down Expand Up @@ -1496,106 +1500,41 @@ 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[] =
{
"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"
};
using StrStrVec = std::vector<std::pair<std::string, std::string>>;

static char*
integer_to_words(gint64 val)
static std::string
string_replace_substring (const std::string& input, const StrStrVec& replacements)
{
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)
{
int this_part = val / 10;
val -= this_part * 10;
g_string_append(result, gettext(medium_numbers[this_part]));
g_string_append_c(result, ' ');
}
std::string result = input;
for (const auto& [key, val] : replacements)
for (auto pos = result.find(key); pos != std::string::npos; pos = result.find(key))
result.replace(pos, key.length(), val);
return result;
}

if (val > 0)
static std::string
number_to_words(double val, int64_t denom)
{
double int_part;
const int frac_part = std::round(std::modf (std::fabs(val), &int_part) * denom);
std::ostringstream ss;
ss.imbue(gnc_get_boost_locale());
auto num_to_words = [&ss](auto num)
{
int this_part = val;
g_string_append(result, gettext(small_numbers[this_part]));
g_string_append_c(result, ' ');
}
ss.str("");
ss << boost::locale::as::spellout << num;
return ss.str();
};
StrStrVec replacements = {
{"{int_part}", num_to_words(int_part)},
{"{frac_part}", num_to_words(frac_part)},
{"{value}", num_to_words(val)},
{"{int_part_num}", std::to_string (int_part)},
{"{frac_part_num}", std::to_string (frac_part)},
{"{denom_num}", std::to_string (denom)},
};

result = g_string_truncate(result, result->len - 1);
return g_string_free(result, FALSE);
return string_replace_substring("{int_part} and {frac_part_num}/{denom_num}", replacements);
}

#ifdef _MSC_VER
Expand All @@ -1606,39 +1545,12 @@ static double round(double x)
}
#endif

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

char*
numeric_to_words(gnc_numeric val)
{
return number_to_words(gnc_numeric_to_double(val),
gnc_numeric_denom(val));
auto words = number_to_words (gnc_numeric_to_double(val), gnc_numeric_denom(val));
PWARN ("Number %s in words: %s", gnc_num_dbg_to_string (val), words.c_str());
return g_strdup(words.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
Loading