25 #include <boost/locale.hpp>
27 #include <type_traits>
30 #pragma GCC diagnostic push
31 #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
35 #pragma GCC diagnostic pop
38 #define DBG_G LOG_STREAM(debug, lg::general())
39 #define LOG_G LOG_STREAM(info, lg::general())
40 #define WRN_G LOG_STREAM(warn, lg::general())
41 #define ERR_G LOG_STREAM(err, lg::general())
43 namespace bl = boost::locale;
47 class default_utf8_locale_name
50 static const std::string& name()
53 static default_utf8_locale_name* lname =
new default_utf8_locale_name();
57 default_utf8_locale_name()
60 LOG_G <<
"Generating default locale";
65 const bl::info& locale_info = std::use_facet<bl::info>(default_locale);
66 name_ += locale_info.language();
67 if(!locale_info.country().empty())
68 name_ +=
"_" + locale_info.country();
70 if(!locale_info.variant().empty())
71 name_ +=
"@" + locale_info.variant();
73 catch(
const std::exception&
e)
75 ERR_G <<
"Failed to generate default locale string. message:" <<
e.what();
77 LOG_G <<
"Finished generating default locale, default is now '" << name_ <<
"'";
82 class wesnoth_message_format :
public bl::message_format<char>
85 wesnoth_message_format(std::locale base,
const std::set<std::string>&
domains,
const std::set<std::string>& paths)
88 const bl::info& inf = std::use_facet<bl::info>(base);
89 if(inf.language() ==
"c") {
92 std::string lang_name_short = inf.language();
93 std::string lang_name_long = lang_name_short;
94 if(!inf.country().empty()) {
95 lang_name_long +=
'_';
96 lang_name_long += inf.country();
98 if(!inf.variant().empty()) {
99 lang_name_long +=
'@';
100 lang_name_long += inf.variant();
101 lang_name_short +=
'@';
102 lang_name_short += inf.variant();
104 DBG_G <<
"Loading po files for language " << lang_name_long;
106 DBG_G <<
"Searching for po files for domain " << domain;
108 for(
auto base_path : paths) {
109 DBG_G <<
"Searching in dir " << base_path;
110 if(base_path[base_path.length()-1] !=
'/') {
115 path = base_path + lang_name_long +
".po";
120 path = base_path + lang_name_short +
".po";
129 LOG_G <<
"Loading language file from " <<
path;
132 po_file->exceptions(std::ios::badbit);
134 extra_messages_.emplace(get_base().domain(domain), cat);
138 log_po_error(lang_name_long, domain,
e.what());
139 }
catch(
const std::ios::failure&) {
140 log_po_error(lang_name_long, domain, strerror(errno));
145 static void log_po_error(
const std::string& lang,
const std::string&
dom,
const std::string&
detail) {
146 ERR_G <<
"Error opening language file for " << lang <<
", textdomain " <<
dom
150 const char*
get(
int domain_id,
const char* ctx,
const char* msg_id)
const override
152 auto& base = get_base();
153 const char*
msg = base.get(domain_id, ctx, msg_id);
155 auto iter = extra_messages_.find(domain_id);
156 if(iter == extra_messages_.end()) {
159 auto& catalog = iter->second;
160 const char* lookup = ctx ? catalog.pgettext(ctx, msg_id) : catalog.gettext(msg_id);
161 if(lookup != msg_id) {
169 #if BOOST_VERSION < 108300
170 const char*
get(
int domain_id,
const char* ctx,
const char* sid,
int n)
const override
172 const char*
get(
int domain_id,
const char* ctx,
const char* sid, bl::count_type
n)
const override
175 auto& base = get_base();
176 const char*
msg = base.get(domain_id, ctx, sid,
n);
178 auto iter = extra_messages_.find(domain_id);
179 if(iter == extra_messages_.end()) {
182 auto& catalog = iter->second;
183 const char* lookup = ctx ? catalog.npgettext(ctx, sid, sid,
n) : catalog.ngettext(sid, sid,
n);
192 int domain(
const std::string& domain)
const override
194 auto& base = get_base();
195 return base.domain(domain);
198 const char* convert(
const char*
msg, std::string& buffer)
const override
200 auto& base = get_base();
201 return base.convert(
msg, buffer);
204 const bl::message_format<char>& get_base()
const
206 return std::use_facet<bl::message_format<char>>(base_loc_);
209 std::locale base_loc_;
210 std::map<int, spirit_po::default_catalog> extra_messages_;
212 struct translation_manager
214 translation_manager()
217 , current_language_(default_utf8_locale_name::name())
222 const bl::localization_backend_manager& g_mgr = bl::localization_backend_manager::global();
223 for(
const std::string& name : g_mgr.get_all_backends())
225 LOG_G <<
"Found boost locale backend: '" << name <<
"'";
228 generator_.use_ansi_encoding(
false);
229 #if BOOST_VERSION < 108100
230 generator_.categories(bl::message_facet | bl::information_facet | bl::collation_facet | bl::formatting_facet | bl::convert_facet);
231 generator_.characters(bl::char_facet);
233 generator_.categories(bl::category_t::message | bl::category_t::information | bl::category_t::collation | bl::category_t::formatting | bl::category_t::convert);
234 generator_.characters(bl::char_facet_t::char_f);
239 update_locale_internal();
242 void add_messages_domain(
const std::string& domain)
244 if(loaded_domains_.find(domain) != loaded_domains_.end())
249 if(domain.find(
'/') != std::string::npos)
255 ERR_G <<
"illegal textdomain name '" << domain
256 <<
"', skipping textdomain";
260 generator_.add_messages_domain(domain);
261 loaded_domains_.insert(domain);
264 void add_messages_path(
const std::string&
path)
266 if(loaded_paths_.find(
path) != loaded_paths_.end())
270 generator_.add_messages_path(
path);
271 loaded_paths_.insert(
path);
274 void set_default_messages_domain(
const std::string& domain)
276 generator_.set_default_messages_domain(domain);
282 std::string::size_type at_pos =
language.rfind(
'@');
285 current_language_ = default_utf8_locale_name::name();
287 else if(at_pos != std::string::npos)
289 current_language_ =
language.substr(0, at_pos) +
".UTF-8" +
language.substr(at_pos);
293 current_language_ =
language +
".UTF-8";
316 void update_locale_internal()
320 LOG_G <<
"attempting to generate locale by name '" << current_language_ <<
"'";
321 current_locale_ = generator_.generate(current_language_);
322 current_locale_ = std::locale(current_locale_,
new wesnoth_message_format(current_locale_, loaded_domains_, loaded_paths_));
323 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
324 LOG_G <<
"updated locale to '" << current_language_ <<
"' locale is now '" << current_locale_.name() <<
"' ( "
325 <<
"name='" <<
info.name()
326 <<
"' country='" <<
info.country()
327 <<
"' language='" <<
info.language()
328 <<
"' encoding='" <<
info.encoding()
329 <<
"' variant='" <<
info.variant() <<
"')";
331 catch(
const bl::conv::conversion_error&)
333 assert(std::has_facet<bl::info>(current_locale_));
334 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
335 ERR_G <<
"Failed to update locale due to conversion error, locale is now: "
336 <<
"name='" <<
info.name()
337 <<
"' country='" <<
info.country()
338 <<
"' language='" <<
info.language()
339 <<
"' encoding='" <<
info.encoding()
340 <<
"' variant='" <<
info.variant()
343 catch(
const std::runtime_error&)
345 assert(std::has_facet<bl::info>(current_locale_));
346 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
347 ERR_G <<
"Failed to update locale due to runtime error, locale is now: "
348 <<
"name='" <<
info.name()
349 <<
"' country='" <<
info.country()
350 <<
"' language='" <<
info.language()
351 <<
"' encoding='" <<
info.encoding()
352 <<
"' variant='" <<
info.variant()
358 std::string debug_description()
360 std::stringstream res;
361 const bl::localization_backend_manager& g_mgr = bl::localization_backend_manager::global();
362 for(
const std::string& name : g_mgr.get_all_backends())
364 res <<
"has backend: '" << name <<
"',";
366 if(std::has_facet<bl::info>(current_locale_)) {
367 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
368 res <<
" locale: (name='" <<
info.name()
369 <<
"' country='" <<
info.country()
370 <<
"' language='" <<
info.language()
371 <<
"' encoding='" <<
info.encoding()
372 <<
"' variant='" <<
info.variant()
375 if(std::has_facet<bl::collator<char>>(current_locale_)) {
376 res <<
"has bl::collator<char> facet, ";
378 #if BOOST_VERSION < 108100
379 res <<
"generator categories='" << generator_.categories() <<
"'";
381 res <<
"generator categories='" <<
391 update_locale_internal();
393 return current_locale_;
397 std::set<std::string> loaded_paths_;
398 std::set<std::string> loaded_domains_;
399 std::string current_language_;
401 std::locale current_locale_;
405 translation_manager& get_manager()
407 static translation_manager* mng =
new translation_manager();
412 std::string ascii_to_lowercase(
const std::string& str)
415 result.reserve(str.length());
416 std::transform(str.begin(), str.end(), std::back_inserter(result), [](
char c)
418 return c >=
'A' && c <=
'Z' ? c | 0x20 : c;
427 std::string
dgettext(
const char* domain,
const char* msgid)
436 std::string
dsgettext (
const char * domainname,
const char *msgid)
438 std::string msgval =
dgettext (domainname, msgid);
439 if (msgval == msgid) {
440 const char* firsthat = std::strchr (msgid,
'^');
441 if (firsthat ==
nullptr)
444 msgval = firsthat + 1;
451 inline const char* is_unlocalized_string2(
const std::string& str,
const char* singular,
const char* plural)
453 if (str == singular) {
466 std::string
dsngettext (
const char * domainname,
const char *singular,
const char *plural,
int n)
468 std::string msgval = bl::dngettext(domainname, singular, plural,
n, get_manager().
get_locale());
470 auto original = is_unlocalized_string2(msgval, singular, plural);
472 const char* firsthat = std::strchr (original,
'^');
473 if (firsthat ==
nullptr)
476 msgval = firsthat + 1;
483 LOG_G <<
"adding textdomain '" << domain <<
"' in directory '" << directory <<
"'";
484 get_manager().add_messages_domain(domain);
485 get_manager().add_messages_path(directory);
486 get_manager().update_locale();
491 LOG_G <<
"set_default_textdomain: '" << domain <<
"'";
492 get_manager().set_default_messages_domain(domain);
501 get_manager().set_language(
language);
504 int compare(
const std::string& s1,
const std::string& s2)
508 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());
509 }
catch(
const std::bad_cast&) {
510 static bool bad_cast_once =
false;
513 ERR_G <<
"locale set-up for compare() is broken, falling back to std::string::compare()";
514 bad_cast_once =
true;
517 return s1.compare(s2);
521 int icompare(
const std::string& s1,
const std::string& s2)
526 return compare(ascii_to_lowercase(s1), ascii_to_lowercase(s2));
530 #if BOOST_VERSION < 108100
531 return std::use_facet<bl::collator<char>>(get_manager().get_locale()).
compare(
532 bl::collator_base::secondary, s1, s2);
534 return std::use_facet<bl::collator<char>>(get_manager().get_locale()).
compare(
535 bl::collate_level::secondary, s1, s2);
537 }
catch(
const std::bad_cast&) {
538 static bool bad_cast_once =
false;
541 ERR_G <<
"locale set-up for icompare() is broken, falling back to std::string::compare()";
544 ERR_G << get_manager().debug_description();
545 }
catch (
const std::exception&
e) {
548 bad_cast_once =
true;
552 return ascii_to_lowercase(s1).compare(ascii_to_lowercase(s2));
559 std::basic_ostringstream<char> dummy;
562 dummy << bl::as::ftime(format) << mktime(const_cast<std::tm*>(time));
567 bool ci_search(
const std::string& s1,
const std::string& s2)
569 const std::locale& locale = get_manager().get_locale();
571 std::string ls1 = bl::to_lower(s1, locale);
572 std::string ls2 = bl::to_lower(s2, locale);
574 return std::search(ls1.begin(), ls1.end(),
575 ls2.begin(), ls2.end()) != ls1.end();
580 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