28 #include <boost/locale.hpp> 32 #pragma GCC diagnostic push 33 #pragma GCC diagnostic ignored "-Wmissing-field-initializers" 37 #pragma GCC diagnostic pop 40 #define DBG_G LOG_STREAM(debug, lg::general()) 41 #define LOG_G LOG_STREAM(info, lg::general()) 42 #define WRN_G LOG_STREAM(warn, lg::general()) 43 #define ERR_G LOG_STREAM(err, lg::general()) 45 namespace bl = boost::locale;
48 std::mutex& get_mutex() {
static std::mutex* m =
new std::mutex();
return *m; }
50 class default_utf8_locale_name
53 static const std::string&
name()
56 static default_utf8_locale_name* lname =
new default_utf8_locale_name();
60 default_utf8_locale_name()
63 LOG_G <<
"Generating default locale\n";
68 const bl::info& locale_info = std::use_facet<bl::info>(default_locale);
69 name_ += locale_info.language();
70 if(!locale_info.country().empty())
71 name_ +=
"_" + locale_info.country();
73 if(!locale_info.variant().empty())
74 name_ +=
"@" + locale_info.variant();
76 catch(
const std::exception&
e)
78 ERR_G <<
"Failed to generate default locale string. message:" << e.what() << std::endl;
80 LOG_G <<
"Finished generating default locale, default is now '" << name_ <<
"'\n";
85 class wesnoth_message_format :
public bl::message_format<char>
89 wesnoth_message_format(std::locale base,
const std::set<std::string>&
domains,
const std::set<std::string>& paths)
92 const bl::info& inf = std::use_facet<bl::info>(base);
93 if(inf.language() ==
"c") {
96 std::string lang_name_short = inf.language();
97 std::string lang_name_long = lang_name_short;
98 if(!inf.country().empty()) {
99 lang_name_long +=
'_';
100 lang_name_long += inf.country();
102 if(!inf.variant().empty()) {
103 lang_name_long +=
'@';
104 lang_name_long += inf.variant();
105 lang_name_short +=
'@';
106 lang_name_short += inf.variant();
108 DBG_G <<
"Loading po files for language " << lang_name_long <<
'\n';
109 for(
auto& domain : domains) {
110 DBG_G <<
"Searching for po files for domain " << domain <<
'\n';
112 for(
auto base_path : paths) {
113 DBG_G <<
"Searching in dir " << base_path <<
'\n';
114 if(base_path[base_path.length()-1] !=
'/') {
119 path = base_path + lang_name_long +
".po";
120 DBG_G <<
" Trying path " << path <<
'\n';
124 path = base_path + lang_name_short +
".po";
125 DBG_G <<
" Trying path " << path <<
'\n';
133 LOG_G <<
"Loading language file from " << path <<
'\n';
136 po_file->exceptions(std::ios::badbit);
137 const po_catalog& cat = po_catalog::from_istream(*po_file);
138 extra_messages_.emplace(get_base().domain(domain), cat);
140 throw_po_error(lang_name_long, domain, e.what());
141 }
catch(
const std::ios::failure&) {
142 throw_po_error(lang_name_long, domain, strerror(errno));
147 [[noreturn]]
static void throw_po_error(
const std::string& lang,
const std::string&
dom,
const std::string&
detail) {
148 std::ostringstream
err;
149 err <<
"Error opening language file for " << lang <<
", textdomain " << dom
150 <<
":\n " << detail <<
'\n';
155 const char*
get(
int domain_id,
const char* ctx,
const char* msg_id)
const override 157 auto& base = get_base();
158 const char*
msg = base.get(domain_id, ctx, msg_id);
160 auto iter = extra_messages_.find(domain_id);
161 if(iter == extra_messages_.end()) {
164 auto& catalog = iter->second;
165 const char* lookup = ctx ? catalog.pgettext(ctx, msg_id) : catalog.gettext(msg_id);
166 if(lookup != msg_id) {
174 const char*
get(
int domain_id,
const char* ctx,
const char* sid,
int n)
const override 176 auto& base = get_base();
177 const char*
msg = base.get(domain_id, ctx, sid,
n);
179 auto iter = extra_messages_.find(domain_id);
180 if(iter == extra_messages_.end()) {
183 auto& catalog = iter->second;
184 const char* lookup = ctx ? catalog.npgettext(ctx, sid, sid,
n) : catalog.ngettext(sid, sid,
n);
193 int domain(
const std::string& domain)
const override 195 auto& base = get_base();
196 return base.domain(domain);
199 const char* convert(
const char*
msg, std::string& buffer)
const override 201 auto& base = get_base();
202 return base.convert(msg, buffer);
205 const bl::message_format<char>& get_base()
const 207 return std::use_facet<bl::message_format<char>>(base_loc_);
210 std::locale base_loc_;
211 std::map<int, po_catalog> extra_messages_;
213 struct translation_manager
215 translation_manager()
223 const bl::localization_backend_manager& g_mgr = bl::localization_backend_manager::global();
224 for(
const std::string&
name : g_mgr.get_all_backends())
226 LOG_G <<
"Found boost locale backend: '" <<
name <<
"'\n";
229 generator_.use_ansi_encoding(
false);
230 generator_.categories(bl::message_facet | bl::information_facet | bl::collation_facet | bl::formatting_facet | bl::convert_facet);
231 generator_.characters(bl::char_facet);
235 update_locale_internal();
238 void add_messages_domain(
const std::string& domain)
240 if(loaded_domains_.find(domain) != loaded_domains_.end())
245 if(domain.find(
'/') != std::string::npos)
251 ERR_G <<
"illegal textdomain name '" << domain
252 <<
"', skipping textdomain\n";
256 generator_.add_messages_domain(domain);
257 loaded_domains_.insert(domain);
260 void add_messages_path(
const std::string&
path)
262 if(loaded_paths_.find(path) != loaded_paths_.end())
266 generator_.add_messages_path(path);
267 loaded_paths_.insert(path);
270 void set_default_messages_domain(
const std::string& domain)
272 generator_.set_default_messages_domain(domain);
278 std::string::size_type at_pos = language.rfind(
'@');
283 else if(at_pos != std::string::npos)
285 current_language_ = language.substr(0, at_pos) +
".UTF-8" + language.substr(at_pos);
289 current_language_ = language +
".UTF-8";
299 void update_locale_internal()
303 LOG_G <<
"attempting to generate locale by name '" << current_language_ <<
"'\n";
304 current_locale_ = generator_.generate(current_language_);
305 current_locale_ = std::locale(current_locale_,
new wesnoth_message_format(current_locale_, loaded_domains_, loaded_paths_));
306 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
307 LOG_G <<
"updated locale to '" << current_language_ <<
"' locale is now '" << current_locale_.name() <<
"' ( " 308 <<
"name='" << info.name()
309 <<
"' country='" << info.country()
310 <<
"' language='" << info.language()
311 <<
"' encoding='" << info.encoding()
312 <<
"' variant='" << info.variant() <<
"')\n";
314 catch(
const bl::conv::conversion_error&)
316 assert(std::has_facet<bl::info>(current_locale_));
317 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
318 ERR_G <<
"Failed to update locale due to conversion error, locale is now: " 319 <<
"name='" << info.name()
320 <<
"' country='" << info.country()
321 <<
"' language='" << info.language()
322 <<
"' encoding='" << info.encoding()
323 <<
"' variant='" << info.variant()
329 std::string debug_description()
331 std::stringstream res;
332 const bl::localization_backend_manager& g_mgr = bl::localization_backend_manager::global();
333 for(
const std::string&
name : g_mgr.get_all_backends())
335 res <<
"has backend: '" <<
name <<
"',";
337 if(std::has_facet<bl::info>(current_locale_)) {
338 const bl::info&
info = std::use_facet<bl::info>(current_locale_);
339 res <<
" locale: (name='" << info.name()
340 <<
"' country='" << info.country()
341 <<
"' language='" << info.language()
342 <<
"' encoding='" << info.encoding()
343 <<
"' variant='" << info.variant()
346 if(std::has_facet<bl::collator<char>>(current_locale_)) {
347 res <<
"has bl::collator<char> facet, ";
349 res <<
"generator categories='" << generator_.categories() <<
"'";
357 update_locale_internal();
359 return current_locale_;
363 std::set<std::string> loaded_paths_;
364 std::set<std::string> loaded_domains_;
365 std::string current_language_;
367 std::locale current_locale_;
371 translation_manager& get_manager()
373 static translation_manager* mng =
new translation_manager();
378 std::string ascii_to_lowercase(
const std::string& str)
381 result.reserve(str.length());
382 std::transform(str.begin(), str.end(), std::back_inserter(result), [](
char c)
384 return c >=
'A' &&
c <=
'Z' ?
c | 0x20 :
c;
393 std::string
dgettext(
const char* domain,
const char* msgid)
395 std::lock_guard<std::mutex> lock(get_mutex());
400 std::lock_guard<std::mutex> lock(get_mutex());
404 std::string
dsgettext (
const char * domainname,
const char *msgid)
406 std::string msgval =
dgettext (domainname, msgid);
407 if (msgval == msgid) {
408 const char* firsthat = std::strchr (msgid,
'^');
409 if (firsthat ==
nullptr)
412 msgval = firsthat + 1;
417 std::string
dsngettext (
const char * domainname,
const char *singular,
const char *plural,
int n)
420 std::lock_guard<std::mutex> lock(get_mutex());
421 std::string msgval = bl::dngettext(domainname, singular, plural, n, get_manager().
get_locale());
422 if (msgval == singular) {
423 const char* firsthat = std::strchr (singular,
'^');
424 if (firsthat ==
nullptr)
427 msgval = firsthat + 1;
434 LOG_G <<
"adding textdomain '" << domain <<
"' in directory '" << directory <<
"'\n";
435 std::lock_guard<std::mutex> lock(get_mutex());
436 get_manager().add_messages_domain(domain);
437 get_manager().add_messages_path(directory);
438 get_manager().update_locale();
443 LOG_G <<
"set_default_textdomain: '" << domain <<
"'\n";
444 std::lock_guard<std::mutex> lock(get_mutex());
445 get_manager().set_default_messages_domain(domain);
453 LOG_G <<
"setting language to '" << language <<
"' \n";
454 std::lock_guard<std::mutex> lock(get_mutex());
455 get_manager().set_language(language);
458 int compare(
const std::string& s1,
const std::string& s2)
460 std::lock_guard<std::mutex> lock(get_mutex());
463 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());
464 }
catch(
const std::bad_cast&) {
465 static bool bad_cast_once =
false;
468 ERR_G <<
"locale set-up for compare() is broken, falling back to std::string::compare()\n";
469 bad_cast_once =
true;
472 return s1.compare(s2);
476 int icompare(
const std::string& s1,
const std::string& s2)
481 return compare(ascii_to_lowercase(s1), ascii_to_lowercase(s2));
483 std::lock_guard<std::mutex> lock(get_mutex());
486 return std::use_facet<bl::collator<char>>(get_manager().get_locale()).
compare(
487 bl::collator_base::secondary, s1, s2);
488 }
catch(
const std::bad_cast&) {
489 static bool bad_cast_once =
false;
492 ERR_G <<
"locale set-up for icompare() is broken, falling back to std::string::compare()\n";
495 ERR_G << get_manager().debug_description() <<
"\n";
496 }
catch (
const std::exception&
e) {
497 ERR_G << e.what() <<
"\n";
499 bad_cast_once =
true;
503 return ascii_to_lowercase(s1).compare(ascii_to_lowercase(s2));
510 std::basic_ostringstream<char>
dummy;
511 std::lock_guard<std::mutex> lock(get_mutex());
514 dummy << bl::as::ftime(format) << mktime(const_cast<std::tm*>(time));
519 bool ci_search(
const std::string& s1,
const std::string& s2)
521 std::lock_guard<std::mutex> lock(get_mutex());
522 const std::locale& locale = get_manager().get_locale();
524 std::string ls1 = bl::to_lower(s1, locale);
525 std::string ls2 = bl::to_lower(s2, locale);
527 return std::search(ls1.begin(), ls1.end(),
528 ls2.begin(), ls2.end()) != ls1.end();
533 std::lock_guard<std::mutex> lock(get_mutex());
534 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
static l_noret error(LoadState *S, const char *why)
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.
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.