27 #include <boost/locale.hpp> 31 #pragma GCC diagnostic push 32 #pragma GCC diagnostic ignored "-Wmissing-field-initializers" 36 #pragma GCC diagnostic pop 39 #define DBG_G LOG_STREAM(debug, lg::general()) 40 #define LOG_G LOG_STREAM(info, lg::general()) 41 #define WRN_G LOG_STREAM(warn, lg::general()) 42 #define ERR_G LOG_STREAM(err, lg::general()) 44 namespace bl = boost::locale;
47 std::mutex& get_mutex() {
static std::mutex* m =
new std::mutex();
return *m; }
49 class default_utf8_locale_name
52 static const std::string& name()
55 static default_utf8_locale_name* lname =
new default_utf8_locale_name();
59 default_utf8_locale_name()
62 LOG_G <<
"Generating default locale\n";
67 const bl::info& locale_info = std::use_facet<bl::info>(default_locale);
68 name_ += locale_info.language();
69 if(!locale_info.country().empty())
70 name_ +=
"_" + locale_info.country();
72 if(!locale_info.variant().empty())
73 name_ +=
"@" + locale_info.variant();
75 catch(
const std::exception&
e)
77 ERR_G <<
"Failed to generate default locale string. message:" << e.what() << std::endl;
79 LOG_G <<
"Finished generating default locale, default is now '" << name_ <<
"'\n";
84 class wesnoth_message_format :
public bl::message_format<char>
87 wesnoth_message_format(std::locale base,
const std::set<std::string>&
domains,
const std::set<std::string>& paths)
90 const bl::info& inf = std::use_facet<bl::info>(base);
91 if(inf.language() ==
"c") {
94 std::string lang_name_short = inf.language();
95 std::string lang_name_long = lang_name_short;
96 if(!inf.country().empty()) {
97 lang_name_long +=
'_';
98 lang_name_long += inf.country();
100 if(!inf.variant().empty()) {
101 lang_name_long +=
'@';
102 lang_name_long += inf.variant();
103 lang_name_short +=
'@';
104 lang_name_short += inf.variant();
106 DBG_G <<
"Loading po files for language " << lang_name_long <<
'\n';
107 for(
auto& domain : domains) {
108 DBG_G <<
"Searching for po files for domain " << domain <<
'\n';
110 for(
auto base_path : paths) {
111 DBG_G <<
"Searching in dir " << base_path <<
'\n';
112 if(base_path[base_path.length()-1] !=
'/') {
117 path = base_path + lang_name_long +
".po";
118 DBG_G <<
" Trying path " << path <<
'\n';
122 path = base_path + lang_name_short +
".po";
123 DBG_G <<
" Trying path " << path <<
'\n';
131 LOG_G <<
"Loading language file from " << path <<
'\n';
134 po_file->exceptions(std::ios::badbit);
136 extra_messages_.emplace(get_base().domain(domain), cat);
140 log_po_error(lang_name_long, domain, e.what());
141 }
catch(
const std::ios::failure&) {
142 log_po_error(lang_name_long, domain, strerror(errno));
147 static void log_po_error(
const std::string& lang,
const std::string&
dom,
const std::string&
detail) {
148 ERR_G <<
"Error opening language file for " << lang <<
", textdomain " << dom
152 const char*
get(
int domain_id,
const char* ctx,
const char* msg_id)
const override 154 auto& base = get_base();
155 const char*
msg = base.get(domain_id, ctx, msg_id);
157 auto iter = extra_messages_.find(domain_id);
158 if(iter == extra_messages_.end()) {
161 auto& catalog = iter->second;
162 const char* lookup = ctx ? catalog.pgettext(ctx, msg_id) : catalog.gettext(msg_id);
163 if(lookup != msg_id) {
171 const char*
get(
int domain_id,
const char* ctx,
const char* sid,
int 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 <<
"'\n";
226 generator_.use_ansi_encoding(
false);
227 generator_.categories(bl::message_facet | bl::information_facet | bl::collation_facet | bl::formatting_facet | bl::convert_facet);
228 generator_.characters(bl::char_facet);
232 update_locale_internal();
235 void add_messages_domain(
const std::string& domain)
237 if(loaded_domains_.find(domain) != loaded_domains_.end())
242 if(domain.find(
'/') != std::string::npos)
248 ERR_G <<
"illegal textdomain name '" << domain
249 <<
"', skipping textdomain\n";
253 generator_.add_messages_domain(domain);
254 loaded_domains_.insert(domain);
257 void add_messages_path(
const std::string&
path)
259 if(loaded_paths_.find(path) != loaded_paths_.end())
263 generator_.add_messages_path(path);
264 loaded_paths_.insert(path);
267 void set_default_messages_domain(
const std::string& domain)
269 generator_.set_default_messages_domain(domain);
275 std::string::size_type at_pos = language.rfind(
'@');
278 current_language_ = default_utf8_locale_name::name();
280 else if(at_pos != std::string::npos)
282 current_language_ = language.substr(0, at_pos) +
".UTF-8" + language.substr(at_pos);
286 current_language_ = language +
".UTF-8";
309 void update_locale_internal()
313 LOG_G <<
"attempting to generate locale by name '" << current_language_ <<
"'\n";
314 current_locale_ = generator_.generate(current_language_);
315 current_locale_ = std::locale(current_locale_,
new wesnoth_message_format(current_locale_, loaded_domains_, loaded_paths_));
316 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
317 LOG_G <<
"updated locale to '" << current_language_ <<
"' locale is now '" << current_locale_.name() <<
"' ( " 318 <<
"name='" << info.name()
319 <<
"' country='" << info.country()
320 <<
"' language='" << info.language()
321 <<
"' encoding='" << info.encoding()
322 <<
"' variant='" << info.variant() <<
"')\n";
324 catch(
const bl::conv::conversion_error&)
326 assert(std::has_facet<bl::info>(current_locale_));
327 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
328 ERR_G <<
"Failed to update locale due to conversion error, locale is now: " 329 <<
"name='" << info.name()
330 <<
"' country='" << info.country()
331 <<
"' language='" << info.language()
332 <<
"' encoding='" << info.encoding()
333 <<
"' variant='" << info.variant()
336 catch(
const std::runtime_error&)
338 assert(std::has_facet<bl::info>(current_locale_));
339 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
340 ERR_G <<
"Failed to update locale due to runtime error, locale is now: " 341 <<
"name='" << info.name()
342 <<
"' country='" << info.country()
343 <<
"' language='" << info.language()
344 <<
"' encoding='" << info.encoding()
345 <<
"' variant='" << info.variant()
351 std::string debug_description()
353 std::stringstream res;
354 const bl::localization_backend_manager& g_mgr = bl::localization_backend_manager::global();
355 for(
const std::string& name : g_mgr.get_all_backends())
357 res <<
"has backend: '" << name <<
"',";
359 if(std::has_facet<bl::info>(current_locale_)) {
360 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
361 res <<
" locale: (name='" << info.name()
362 <<
"' country='" << info.country()
363 <<
"' language='" << info.language()
364 <<
"' encoding='" << info.encoding()
365 <<
"' variant='" << info.variant()
368 if(std::has_facet<bl::collator<char>>(current_locale_)) {
369 res <<
"has bl::collator<char> facet, ";
371 res <<
"generator categories='" << generator_.categories() <<
"'";
379 update_locale_internal();
381 return current_locale_;
385 std::set<std::string> loaded_paths_;
386 std::set<std::string> loaded_domains_;
387 std::string current_language_;
389 std::locale current_locale_;
393 translation_manager& get_manager()
395 static translation_manager* mng =
new translation_manager();
400 std::string ascii_to_lowercase(
const std::string& str)
403 result.reserve(str.length());
404 std::transform(str.begin(), str.end(), std::back_inserter(result), [](
char c)
406 return c >=
'A' &&
c <=
'Z' ?
c | 0x20 :
c;
415 std::string
dgettext(
const char* domain,
const char* msgid)
417 std::scoped_lock lock(get_mutex());
422 std::scoped_lock lock(get_mutex());
426 std::string
dsgettext (
const char * domainname,
const char *msgid)
428 std::string msgval =
dgettext (domainname, msgid);
429 if (msgval == msgid) {
430 const char* firsthat = std::strchr (msgid,
'^');
431 if (firsthat ==
nullptr)
434 msgval = firsthat + 1;
441 inline const char* is_unlocalized_string2(
const std::string& str,
const char* singular,
const char* plural)
443 if (str == singular) {
456 std::string
dsngettext (
const char * domainname,
const char *singular,
const char *plural,
int n)
459 std::scoped_lock lock(get_mutex());
460 std::string msgval = bl::dngettext(domainname, singular, plural, n, get_manager().
get_locale());
461 auto original = is_unlocalized_string2(msgval, singular, plural);
463 const char* firsthat = std::strchr (original,
'^');
464 if (firsthat ==
nullptr)
467 msgval = firsthat + 1;
474 LOG_G <<
"adding textdomain '" << domain <<
"' in directory '" << directory <<
"'\n";
475 std::scoped_lock lock(get_mutex());
476 get_manager().add_messages_domain(domain);
477 get_manager().add_messages_path(directory);
478 get_manager().update_locale();
483 LOG_G <<
"set_default_textdomain: '" << domain <<
"'\n";
484 std::scoped_lock lock(get_mutex());
485 get_manager().set_default_messages_domain(domain);
493 LOG_G <<
"setting language to '" << language <<
"' \n";
494 std::scoped_lock lock(get_mutex());
495 get_manager().set_language(language);
498 int compare(
const std::string& s1,
const std::string& s2)
500 std::scoped_lock lock(get_mutex());
503 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());
504 }
catch(
const std::bad_cast&) {
505 static bool bad_cast_once =
false;
508 ERR_G <<
"locale set-up for compare() is broken, falling back to std::string::compare()\n";
509 bad_cast_once =
true;
512 return s1.compare(s2);
516 int icompare(
const std::string& s1,
const std::string& s2)
521 return compare(ascii_to_lowercase(s1), ascii_to_lowercase(s2));
523 std::scoped_lock lock(get_mutex());
526 return std::use_facet<bl::collator<char>>(get_manager().get_locale()).
compare(
527 bl::collator_base::secondary, s1, s2);
528 }
catch(
const std::bad_cast&) {
529 static bool bad_cast_once =
false;
532 ERR_G <<
"locale set-up for icompare() is broken, falling back to std::string::compare()\n";
535 ERR_G << get_manager().debug_description() <<
"\n";
536 }
catch (
const std::exception&
e) {
537 ERR_G << e.what() <<
"\n";
539 bad_cast_once =
true;
543 return ascii_to_lowercase(s1).compare(ascii_to_lowercase(s2));
550 std::basic_ostringstream<char> dummy;
551 std::scoped_lock lock(get_mutex());
554 dummy << bl::as::ftime(format) << mktime(const_cast<std::tm*>(time));
559 bool ci_search(
const std::string& s1,
const std::string& s2)
561 std::scoped_lock lock(get_mutex());
562 const std::locale& locale = get_manager().get_locale();
564 std::string ls1 = bl::to_lower(s1, locale);
565 std::string ls2 = bl::to_lower(s2, locale);
567 return std::search(ls1.begin(), ls1.end(),
568 ls2.begin(), ls2.end()) != ls1.end();
573 std::scoped_lock lock(get_mutex());
574 return std::use_facet<boost::locale::info>(get_manager().get_locale());
static log_domain dom("general")
void bind_textdomain(const char *domain, const char *directory, const char *)
static domain_map * domains
int compare(const std::string &s1, const std::string &s2)
Case-sensitive lexicographical comparison.
static bool file_exists(const bfs::path &fpath)
const language_def & get_locale()
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
void set_language(const std::string &language, const std::vector< std::string > *)
std::string dsngettext(const char *domainname, const char *singular, const char *plural, int n)
std::string dgettext(const char *domain, const char *msgid)
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
std::string dsgettext(const char *domainname, const char *msgid)
std::string strftime(const std::string &format, const std::tm *time)
std::unique_ptr< std::istream > scoped_istream
void set_default_textdomain(const char *domain)
std::string egettext(char const *msgid)
bool ci_search(const std::string &s1, const std::string &s2)
static std::string gettext(const char *str)
static std::string flush(std::ostringstream &s)
Declarations for File-IO.
static catalog from_istream(std::istream &is, warning_channel_type w=warning_channel_type())
rng * generator
This generator is automatically synced during synced context.
int icompare(const std::string &s1, const std::string &s2)
Case-insensitive lexicographical comparison.
Standard logging facilities (interface).
static map_location::DIRECTION n
const boost::locale::info & get_effective_locale_info()
A facet that holds general information about the effective locale.