23 #include <boost/locale.hpp>
25 #include <type_traits>
28 #pragma GCC diagnostic push
29 #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
33 #pragma GCC diagnostic pop
36 #define DBG_G LOG_STREAM(debug, lg::general())
37 #define LOG_G LOG_STREAM(info, lg::general())
38 #define WRN_G LOG_STREAM(warn, lg::general())
39 #define ERR_G LOG_STREAM(err, lg::general())
41 namespace bl = boost::locale;
45 class default_utf8_locale_name
48 static const std::string& name()
51 static default_utf8_locale_name* lname =
new default_utf8_locale_name();
55 default_utf8_locale_name()
58 LOG_G <<
"Generating default locale";
63 const bl::info& locale_info = std::use_facet<bl::info>(default_locale);
64 name_ += locale_info.language();
65 if(!locale_info.country().empty())
66 name_ +=
"_" + locale_info.country();
68 if(!locale_info.variant().empty())
69 name_ +=
"@" + locale_info.variant();
71 catch(
const std::exception&
e)
73 ERR_G <<
"Failed to generate default locale string. message:" <<
e.what();
75 LOG_G <<
"Finished generating default locale, default is now '" << name_ <<
"'";
80 class wesnoth_message_format :
public bl::message_format<char>
83 wesnoth_message_format(
const std::locale& base,
const std::set<std::string>&
domains,
const std::set<std::string>& paths)
86 const bl::info& inf = std::use_facet<bl::info>(base);
87 if(inf.language() ==
"c") {
90 std::string lang_name_short = inf.language();
91 std::string lang_name_long = lang_name_short;
92 if(!inf.country().empty()) {
93 lang_name_long +=
'_';
94 lang_name_long += inf.country();
96 if(!inf.variant().empty()) {
97 lang_name_long +=
'@';
98 lang_name_long += inf.variant();
99 lang_name_short +=
'@';
100 lang_name_short += inf.variant();
102 DBG_G <<
"Loading po files for language " << lang_name_long;
104 DBG_G <<
"Searching for po files for domain " << domain;
106 for(
auto base_path : paths) {
107 DBG_G <<
"Searching in dir " << base_path;
108 if(base_path[base_path.length()-1] !=
'/') {
113 path = base_path + lang_name_long +
".po";
118 path = base_path + lang_name_short +
".po";
127 LOG_G <<
"Loading language file from " <<
path;
130 po_file->exceptions(std::ios::badbit);
132 extra_messages_.emplace(get_base().domain(domain), cat);
136 log_po_error(lang_name_long, domain,
e.what());
137 }
catch(
const std::ios::failure&) {
138 log_po_error(lang_name_long, domain, strerror(errno));
143 static void log_po_error(
const std::string& lang,
const std::string&
dom,
const std::string&
detail) {
144 ERR_G <<
"Error opening language file for " << lang <<
", textdomain " <<
dom
148 const char*
get(
int domain_id,
const char* ctx,
const char* msg_id)
const override
150 auto& base = get_base();
151 const char*
msg = base.get(domain_id, ctx, msg_id);
153 auto iter = extra_messages_.find(domain_id);
154 if(iter == extra_messages_.end()) {
157 auto& catalog = iter->second;
158 const char* lookup = ctx ? catalog.pgettext(ctx, msg_id) : catalog.gettext(msg_id);
159 if(lookup != msg_id) {
167 #if BOOST_VERSION < 108300
168 const char*
get(
int domain_id,
const char* ctx,
const char* sid,
int n)
const override
170 const char*
get(
int domain_id,
const char* ctx,
const char* sid, bl::count_type
n)
const override
173 auto& base = get_base();
174 const char*
msg = base.get(domain_id, ctx, sid,
n);
176 auto iter = extra_messages_.find(domain_id);
177 if(iter == extra_messages_.end()) {
180 auto& catalog = iter->second;
181 const char* lookup = ctx ? catalog.npgettext(ctx, sid, sid,
n) : catalog.ngettext(sid, sid,
n);
190 int domain(
const std::string& domain)
const override
192 auto& base = get_base();
193 return base.domain(domain);
196 const char* convert(
const char*
msg, std::string& buffer)
const override
198 auto& base = get_base();
199 return base.convert(
msg, buffer);
202 const bl::message_format<char>& get_base()
const
204 return std::use_facet<bl::message_format<char>>(base_loc_);
207 std::locale base_loc_;
208 std::map<int, spirit_po::default_catalog> extra_messages_;
210 struct translation_manager
212 translation_manager()
215 , current_language_(default_utf8_locale_name::name())
220 const bl::localization_backend_manager& g_mgr = bl::localization_backend_manager::global();
221 for(
const std::string& name : g_mgr.get_all_backends())
223 LOG_G <<
"Found boost locale backend: '" << name <<
"'";
226 generator_.use_ansi_encoding(
false);
227 #if BOOST_VERSION < 108100
228 generator_.categories(bl::message_facet | bl::information_facet | bl::collation_facet | bl::formatting_facet | bl::convert_facet);
229 generator_.characters(bl::char_facet);
231 generator_.categories(bl::category_t::message | bl::category_t::information | bl::category_t::collation | bl::category_t::formatting | bl::category_t::convert);
232 generator_.characters(bl::char_facet_t::char_f);
237 update_locale_internal();
240 void add_messages_domain(
const std::string& domain)
242 if(loaded_domains_.find(domain) != loaded_domains_.end())
247 if(domain.find(
'/') != std::string::npos)
253 ERR_G <<
"illegal textdomain name '" << domain
254 <<
"', skipping textdomain";
258 generator_.add_messages_domain(domain);
259 loaded_domains_.insert(domain);
262 void add_messages_path(
const std::string&
path)
264 if(loaded_paths_.find(
path) != loaded_paths_.end())
268 generator_.add_messages_path(
path);
269 loaded_paths_.insert(
path);
272 void set_default_messages_domain(
const std::string& domain)
274 generator_.set_default_messages_domain(domain);
280 std::string::size_type at_pos = language.rfind(
'@');
283 current_language_ = default_utf8_locale_name::name();
285 else if(at_pos != std::string::npos)
287 current_language_ = language.substr(0, at_pos) +
".UTF-8" + language.substr(at_pos);
291 current_language_ = language +
".UTF-8";
314 void update_locale_internal()
318 LOG_G <<
"attempting to generate locale by name '" << current_language_ <<
"'";
319 current_locale_ = generator_.generate(current_language_);
320 current_locale_ = std::locale(current_locale_,
new wesnoth_message_format(current_locale_, loaded_domains_, loaded_paths_));
321 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
322 LOG_G <<
"updated locale to '" << current_language_ <<
"' locale is now '" << current_locale_.name() <<
"' ( "
323 <<
"name='" <<
info.name()
324 <<
"' country='" <<
info.country()
325 <<
"' language='" <<
info.language()
326 <<
"' encoding='" <<
info.encoding()
327 <<
"' variant='" <<
info.variant() <<
"')";
329 catch(
const bl::conv::conversion_error&
e)
331 assert(std::has_facet<bl::info>(current_locale_));
332 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
333 ERR_G <<
"Failed to update locale due to conversion error (" <<
e.what() <<
") locale is now: "
334 <<
"name='" <<
info.name()
335 <<
"' country='" <<
info.country()
336 <<
"' language='" <<
info.language()
337 <<
"' encoding='" <<
info.encoding()
338 <<
"' variant='" <<
info.variant()
341 catch(
const std::runtime_error&
e)
343 assert(std::has_facet<bl::info>(current_locale_));
344 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
345 ERR_G <<
"Failed to update locale due to runtime error (" <<
e.what() <<
") locale is now: "
346 <<
"name='" <<
info.name()
347 <<
"' country='" <<
info.country()
348 <<
"' language='" <<
info.language()
349 <<
"' encoding='" <<
info.encoding()
350 <<
"' variant='" <<
info.variant()
356 std::string debug_description()
358 std::stringstream res;
359 const bl::localization_backend_manager& g_mgr = bl::localization_backend_manager::global();
360 for(
const std::string& name : g_mgr.get_all_backends())
362 res <<
"has backend: '" << name <<
"',";
364 if(std::has_facet<bl::info>(current_locale_)) {
365 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
366 res <<
" locale: (name='" <<
info.name()
367 <<
"' country='" <<
info.country()
368 <<
"' language='" <<
info.language()
369 <<
"' encoding='" <<
info.encoding()
370 <<
"' variant='" <<
info.variant()
373 if(std::has_facet<bl::collator<char>>(current_locale_)) {
374 res <<
"has bl::collator<char> facet, ";
376 #if BOOST_VERSION < 108100
377 res <<
"generator categories='" << generator_.categories() <<
"'";
379 res <<
"generator categories='" <<
389 update_locale_internal();
391 return current_locale_;
395 std::set<std::string> loaded_paths_;
396 std::set<std::string> loaded_domains_;
397 std::string current_language_;
399 std::locale current_locale_;
403 translation_manager& get_manager()
405 static translation_manager* mng =
new translation_manager();
410 std::string ascii_to_lowercase(
const std::string& str)
413 result.reserve(str.length());
414 std::transform(str.begin(), str.end(), std::back_inserter(result), [](
char c)
416 return c >=
'A' && c <=
'Z' ? c | 0x20 : c;
425 std::string
dgettext(
const char* domain,
const char* msgid)
434 std::string
dsgettext (
const char * domainname,
const char *msgid)
436 std::string msgval =
dgettext (domainname, msgid);
437 if (msgval == msgid) {
438 const char* firsthat = std::strchr (msgid,
'^');
439 if (firsthat ==
nullptr)
442 msgval = firsthat + 1;
449 inline const char* is_unlocalized_string2(
const std::string& str,
const char* singular,
const char* plural)
451 if (str == singular) {
464 std::string
dsngettext (
const char * domainname,
const char *singular,
const char *plural,
int n)
466 std::string msgval = bl::dngettext(domainname, singular, plural,
n, get_manager().
get_locale());
468 auto original = is_unlocalized_string2(msgval, singular, plural);
470 const char* firsthat = std::strchr (original,
'^');
471 if (firsthat ==
nullptr)
474 msgval = firsthat + 1;
481 LOG_G <<
"adding textdomain '" << domain <<
"' in directory '" << directory <<
"'";
482 get_manager().add_messages_domain(domain);
483 get_manager().add_messages_path(directory);
484 get_manager().update_locale();
489 LOG_G <<
"set_default_textdomain: '" << domain <<
"'";
490 get_manager().set_default_messages_domain(domain);
494 void set_language(
const std::string& language,
const std::vector<std::string>* )
498 LOG_G <<
"setting language to '" << language <<
"'";
499 get_manager().set_language(language);
502 int compare(
const std::string& s1,
const std::string& s2)
506 return std::use_facet<std::collate<char>>(get_manager().get_locale()).
compare(s1.c_str(), s1.c_str() + s1.size(), s2.c_str(), s2.c_str() + s2.size());
507 }
catch(
const std::bad_cast&) {
508 static bool bad_cast_once =
false;
511 ERR_G <<
"locale set-up for compare() is broken, falling back to std::string::compare()";
512 bad_cast_once =
true;
515 return s1.compare(s2);
519 int icompare(
const std::string& s1,
const std::string& s2)
524 return compare(ascii_to_lowercase(s1), ascii_to_lowercase(s2));
528 #if BOOST_VERSION < 108100
529 return std::use_facet<bl::collator<char>>(get_manager().get_locale()).
compare(
530 bl::collator_base::secondary, s1, s2);
532 return std::use_facet<bl::collator<char>>(get_manager().get_locale()).
compare(
533 bl::collate_level::secondary, s1, s2);
535 }
catch(
const std::bad_cast&) {
536 static bool bad_cast_once =
false;
539 ERR_G <<
"locale set-up for icompare() is broken, falling back to std::string::compare()";
542 ERR_G << get_manager().debug_description();
543 }
catch (
const std::exception&
e) {
546 bad_cast_once =
true;
550 return ascii_to_lowercase(s1).compare(ascii_to_lowercase(s2));
557 std::basic_ostringstream<char>
dummy;
560 dummy << bl::as::ftime(format) << mktime(const_cast<std::tm*>(time));
565 bool ci_search(
const std::string& s1,
const std::string& s2)
567 const std::locale& locale = get_manager().get_locale();
569 std::string ls1 = bl::to_lower(s1, locale);
570 std::string ls2 = bl::to_lower(s2, locale);
572 return std::search(ls1.begin(), ls1.end(),
573 ls2.begin(), ls2.end()) != ls1.end();
578 return std::use_facet<boost::locale::info>(get_manager().
get_locale());
static catalog from_istream(std::istream &is, warning_channel_type w=warning_channel_type())
Declarations for File-IO.
const language_def & get_locale()
Standard logging facilities (interface).
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
static bool file_exists(const bfs::path &fpath)
std::unique_ptr< std::istream > scoped_istream
static log_domain dom("general")
static domain_map * domains
rng * generator
This generator is automatically synced during synced context.
void set_language(const std::string &language, const std::vector< std::string > *)
std::string egettext(char const *msgid)
static std::string gettext(const char *str)
void bind_textdomain(const char *domain, const char *directory, const char *)
void set_default_textdomain(const char *domain)
int compare(const std::string &s1, const std::string &s2)
Case-sensitive lexicographical comparison.
int icompare(const std::string &s1, const std::string &s2)
Case-insensitive lexicographical comparison.
const boost::locale::info & get_effective_locale_info()
A facet that holds general information about the effective locale.
std::string strftime(const std::string &format, const std::tm *time)
std::string dgettext(const char *domain, const char *msgid)
bool ci_search(const std::string &s1, const std::string &s2)
std::string dsgettext(const char *domainname, const char *msgid)
std::string dsngettext(const char *domainname, const char *singular, const char *plural, int n)
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
static map_location::direction n