mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
Provide a single static instance of C++ locale.
We can't use std::locale::global because all streams imbue it by default and if it's not 'C' (aka std::locale::classic) then we must imbue all the streams that we don't want localized, and that's most of them. Provides error checking for setting the C++ locale from the environment. This is necessary both because the environment might have an invalid locale, which would cause an unhandled exception crash. On windows std::locale("") can't handle some Microsoft-style locale strings (e.g. Spanish_Spain) so we use boost::locale's gen("") function to set the locale--though even that can't handle a Microsoft-style locale string with an appended charset (e.g. Spanish_Spain.1252) and that's what glibc's setlocale(LC_ALL, NULL) emits.
This commit is contained in:
parent
cee97be8d4
commit
b4fedff90e
@ -49,6 +49,7 @@ extern "C"
|
||||
#include "gnc-engine.h"
|
||||
}
|
||||
|
||||
#include <gnc-locale-utils.hpp>
|
||||
#include <boost/locale.hpp>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
@ -2320,7 +2321,7 @@ struct cust_prec_punct : std::moneypunct_byname<wchar_t, false> {
|
||||
template<int prec>
|
||||
std::string to_str_with_prec (const gdouble val)
|
||||
{
|
||||
auto loc = std::locale(std::locale(""), new cust_prec_punct<prec>(""));
|
||||
auto loc = std::locale(gnc_get_locale(), new cust_prec_punct<prec>(""));
|
||||
std::wstringstream valstr;
|
||||
valstr.imbue(loc);
|
||||
valstr << std::put_money(val * pow(10, prec));
|
||||
|
@ -64,6 +64,7 @@ extern "C"
|
||||
#include "gnc-tokenizer-fw.hpp"
|
||||
#include "gnc-tokenizer-csv.hpp"
|
||||
|
||||
#include <gnc-locale-utils.hpp>
|
||||
#include <boost/locale.hpp>
|
||||
|
||||
namespace bl = boost::locale;
|
||||
@ -2068,7 +2069,7 @@ CsvImpTransAssist::assist_summary_page_prepare ()
|
||||
try
|
||||
{
|
||||
/* Translators: {1} will be replaced with a filename */
|
||||
text += (bl::format (bl::translate ("The transactions were imported from file '{1}'.")) % m_file_name).str(gen(""));
|
||||
text += (bl::format (bl::translate ("The transactions were imported from file '{1}'.")) % m_file_name).str(gnc_get_locale());
|
||||
text += "</b></span>";
|
||||
}
|
||||
catch (const bl::conv::conversion_error& err)
|
||||
|
@ -25,6 +25,7 @@ set (core_utils_SOURCES
|
||||
gnc-guile-utils.c
|
||||
gnc-jalali.c
|
||||
gnc-locale-utils.c
|
||||
gnc-locale-utils.cpp
|
||||
gnc-path.c
|
||||
)
|
||||
|
||||
@ -116,6 +117,7 @@ set(core_utils_noinst_HEADERS
|
||||
gnc-guile-utils.h
|
||||
gnc-jalali.h
|
||||
gnc-locale-utils.h
|
||||
gnc-locale-utils.hpp
|
||||
gnc-path.h
|
||||
)
|
||||
|
||||
|
@ -64,6 +64,7 @@ extern "C" {
|
||||
#endif
|
||||
}
|
||||
|
||||
#include "gnc-locale-utils.hpp"
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/locale.hpp>
|
||||
#include <iostream>
|
||||
@ -587,9 +588,8 @@ static std::string migrate_gnc_datahome()
|
||||
gen.add_messages_path(gnc_path_get_datadir());
|
||||
gen.add_messages_domain(PACKAGE);
|
||||
|
||||
// std::locale::global(gen(""));
|
||||
std::stringstream migration_msg;
|
||||
migration_msg.imbue(gen(""));
|
||||
migration_msg.imbue(gnc_get_locale());
|
||||
|
||||
/* Step 1: copy directory $HOME/.gnucash to $GNC_DATA_HOME */
|
||||
auto full_copy = copy_recursive (old_dir, gnc_userdata_home);
|
||||
|
78
libgnucash/core-utils/gnc-locale-utils.cpp
Normal file
78
libgnucash/core-utils/gnc-locale-utils.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
/********************************************************************\
|
||||
* gnc-locale-utils.cpp -- provide a default locale for C++ *
|
||||
* Copyright (C) 2019 John Ralls <jralls@ceridwen.us *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License as *
|
||||
* published by the Free Software Foundation; either version 2 of *
|
||||
* the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License*
|
||||
* along with this program; if not, contact: *
|
||||
* *
|
||||
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
||||
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
||||
\********************************************************************/
|
||||
|
||||
#include <clocale>
|
||||
#include <boost/locale.hpp>
|
||||
#include "gnc-locale-utils.hpp"
|
||||
|
||||
/* This function addresses two separate problems: First, if we set
|
||||
* std::locale::global then all streams automagically imbue
|
||||
* themselves with it and we have to re-imbue all of the backends and
|
||||
* logging streams with std::locale::classic() so that data and log
|
||||
* files aren't localized. Second, calling std::locale("") is slow,
|
||||
* so we want to do it only once. Worse, the standard C++ library in
|
||||
* Mingw64 chokes on at least some Microsoft-style locale strings
|
||||
* (e.g. "Spanish_Spain") but libc's setlocale(LC_ALL, NULL) emits
|
||||
* them even if originally fed a Unix-style locale ("es_ES").
|
||||
*
|
||||
* The solution is this function which caches the setlocale() locale
|
||||
* the first time it's called and which uses a boost::locale
|
||||
* generator, which does know what to do with (sometimes adjusted)
|
||||
* Microsoft locale strings.
|
||||
*/
|
||||
const std::locale&
|
||||
gnc_get_locale()
|
||||
{
|
||||
static std::locale cached;
|
||||
static bool tried_already = false;
|
||||
if (!tried_already)
|
||||
{
|
||||
boost::locale::generator gen;
|
||||
tried_already = true;
|
||||
try
|
||||
{
|
||||
cached = gen("");
|
||||
}
|
||||
catch (const std::runtime_error& err)
|
||||
{
|
||||
std::string c_locale(setlocale(LC_ALL, nullptr));
|
||||
std::cerr << "[gnc_get_locale] Failed to create app-default locale from " << c_locale << " because " << err.what() << "\n";
|
||||
auto dot = c_locale.find(".");
|
||||
if (dot != std::string::npos)
|
||||
{
|
||||
try
|
||||
{
|
||||
cached = gen(c_locale.substr(0, dot));
|
||||
}
|
||||
catch (std::runtime_error& err2)
|
||||
{
|
||||
std::cerr << "[gnc_get_locale] Failed to create app-default locale from " << c_locale << " because " << err.what() << " so using the 'C' locale for C++.\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "[gnc_get_locale] Using the 'C' locale for C++\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return cached;
|
||||
}
|
39
libgnucash/core-utils/gnc-locale-utils.hpp
Normal file
39
libgnucash/core-utils/gnc-locale-utils.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
/********************************************************************\
|
||||
* gnc-locale-utils.hpp -- provide a default locale for C++ *
|
||||
* Copyright (C) 2019 John Ralls <jralls@ceridwen.us *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License as *
|
||||
* published by the Free Software Foundation; either version 2 of *
|
||||
* the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License*
|
||||
* along with this program; if not, contact: *
|
||||
* *
|
||||
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
||||
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
||||
\********************************************************************/
|
||||
#ifndef GNC_LOCALE_UTILS_HPP
|
||||
#define GNC_LOCALE_UTILS_HPP
|
||||
|
||||
#include <locale>
|
||||
|
||||
/** Get the default application locale.
|
||||
*
|
||||
* If we set std::locale::global we have to imbue every stream that
|
||||
* we want in the C locale, and that's a lot more than we want imbued
|
||||
* with the application locale. Calling std::locale("") is expensive,
|
||||
* so call this instead.
|
||||
*
|
||||
* @returns A static std::locale representing the one set with
|
||||
* setlocale() in main().
|
||||
*/
|
||||
const std::locale& gnc_get_locale();
|
||||
|
||||
#endif /* GNC_LOCALE_UTILS_HPP */
|
@ -46,6 +46,7 @@ set(test_gnc_path_util_SOURCES
|
||||
${MODULEPATH}/gnc-path.c
|
||||
${MODULEPATH}/binreloc.c
|
||||
${MODULEPATH}/gnc-filepath-utils.cpp
|
||||
${MODULEPATH}/gnc-locale-utils.cpp
|
||||
gtest-path-utilities.cpp
|
||||
${GTEST_SRC})
|
||||
|
||||
|
@ -32,14 +32,19 @@ extern "C"
|
||||
#include <boost/date_time/local_time/local_time.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#include <libintl.h>
|
||||
#include <locale.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <gnc-locale-utils.hpp>
|
||||
#include "gnc-timezone.hpp"
|
||||
#include "gnc-datetime.hpp"
|
||||
#include "qoflog.h"
|
||||
|
||||
static const char* log_module = "gnc.engine";
|
||||
|
||||
#define N_(string) string //So that xgettext will find it
|
||||
|
||||
@ -440,8 +445,7 @@ GncDateTimeImpl::format(const char* format) const
|
||||
//The stream destructor frees the facet, so it must be heap-allocated.
|
||||
auto output_facet(new Facet(normalize_format(format).c_str()));
|
||||
// FIXME Rather than imbueing a locale below we probably should set std::locale::global appropriately somewhere.
|
||||
// At that point the use of cachedLocale mechanism should be removed.
|
||||
ss.imbue(std::locale(cachedLocale, output_facet));
|
||||
ss.imbue(std::locale(gnc_get_locale(), output_facet));
|
||||
ss << m_time;
|
||||
return ss.str();
|
||||
}
|
||||
@ -454,7 +458,7 @@ GncDateTimeImpl::format_zulu(const char* format) const
|
||||
//The stream destructor frees the facet, so it must be heap-allocated.
|
||||
auto output_facet(new Facet(normalize_format(format).c_str()));
|
||||
// FIXME Rather than imbueing a locale below we probably should set std::locale::global appropriately somewhere.
|
||||
ss.imbue(std::locale(std::locale(""), output_facet));
|
||||
ss.imbue(std::locale(gnc_get_locale(), output_facet));
|
||||
ss << m_time.utc_time();
|
||||
return ss.str();
|
||||
}
|
||||
@ -530,7 +534,7 @@ GncDateImpl::format(const char* format) const
|
||||
//The stream destructor frees the facet, so it must be heap-allocated.
|
||||
auto output_facet(new Facet(normalize_format(format).c_str()));
|
||||
// FIXME Rather than imbueing a locale below we probably should set std::locale::global appropriately somewhere.
|
||||
ss.imbue(std::locale(std::locale(""), output_facet));
|
||||
ss.imbue(std::locale(gnc_get_locale(), output_facet));
|
||||
ss << m_greg;
|
||||
return ss.str();
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ set(gtest_qof_LIBS
|
||||
|
||||
set(gtest_engine_INCLUDES
|
||||
${MODULEPATH}
|
||||
${CMAKE_SOURCE_DIR}/libgnucash/core-utils
|
||||
${CMAKE_BINARY_DIR}/common # for config.h
|
||||
${CMAKE_SOURCE_DIR}/common # for platform.h
|
||||
${GLIB2_INCLUDE_DIRS}
|
||||
@ -156,6 +157,7 @@ set(test_gnc_rational_SOURCES
|
||||
${MODULEPATH}/gnc-timezone.cpp
|
||||
${MODULEPATH}/gnc-date.cpp
|
||||
${MODULEPATH}/qoflog.cpp
|
||||
${CMAKE_SOURCE_DIR}/libgnucash/core-utils/gnc-locale-utils.cpp
|
||||
${gtest_engine_win32_SOURCES}
|
||||
gtest-gnc-rational.cpp
|
||||
${GTEST_SRC})
|
||||
@ -171,6 +173,7 @@ set(test_gnc_numeric_SOURCES
|
||||
${MODULEPATH}/gnc-timezone.cpp
|
||||
${MODULEPATH}/gnc-date.cpp
|
||||
${MODULEPATH}/qoflog.cpp
|
||||
${CMAKE_SOURCE_DIR}/libgnucash/core-utils/gnc-locale-utils.cpp
|
||||
${gtest_engine_win32_SOURCES}
|
||||
gtest-gnc-numeric.cpp
|
||||
${GTEST_SRC})
|
||||
@ -189,6 +192,7 @@ set(test_gnc_datetime_SOURCES
|
||||
${MODULEPATH}/gnc-timezone.cpp
|
||||
${MODULEPATH}/gnc-date.cpp
|
||||
${MODULEPATH}/qoflog.cpp
|
||||
${CMAKE_SOURCE_DIR}/libgnucash/core-utils/gnc-locale-utils.cpp
|
||||
${gtest_engine_win32_SOURCES}
|
||||
gtest-gnc-datetime.cpp
|
||||
${GTEST_SRC})
|
||||
|
@ -613,6 +613,7 @@ libgnucash/core-utils/gnc-glib-utils.c
|
||||
libgnucash/core-utils/gnc-guile-utils.c
|
||||
libgnucash/core-utils/gnc-jalali.c
|
||||
libgnucash/core-utils/gnc-locale-utils.c
|
||||
libgnucash/core-utils/gnc-locale-utils.cpp
|
||||
libgnucash/core-utils/gnc-path.c
|
||||
libgnucash/core-utils/gnc-prefs.c
|
||||
libgnucash/doc/doxygen_main_page.c
|
||||
|
Loading…
Reference in New Issue
Block a user