The Battle for Wesnoth  1.19.22+dev
language.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "filesystem.hpp"
17 #include "gettext.hpp"
18 #include "language.hpp"
19 #include "log.hpp"
21 #include "serialization/parser.hpp"
23 #include "game_config_manager.hpp"
24 
25 #include <clocale>
26 
27 #ifdef _WIN32
28 #include <windows.h>
29 #if !defined(_MSC_VER) && !defined(__MINGW32__)
30 extern "C" int _putenv(const char*);
31 #endif
32 #endif
33 
34 #ifdef __APPLE__
35 #include <cerrno>
36 #endif
37 
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())
42 
43 namespace {
44  language_def current_language;
45  std::vector<config> languages;
46  std::vector<language_def> known_languages;
47  utils::string_map strings_;
48  int min_translation_percent = 80;
49 }
50 
51 bool load_strings(bool complain);
52 
54 {
55  return ((language == a.language) /* && (localename == a.localename) */ );
56 }
57 
59 {
60  static bool result = true;
61  return result;
62 }
63 
64 const t_string& symbol_table::operator[](const std::string& key) const
65 {
66  const utils::string_map::const_iterator i = strings_.find(key);
67  if(i != strings_.end()) {
68  return i->second;
69  } else {
70  static t_string empty_string;
71  // Let's do it the painful way (untlb means untranslatABLE).
72  // It will cause problem if somebody stores more than one reference at once
73  // but I don't really care since this path is an error path and it should
74  // not have been taken in the first place. -- silene
75  empty_string = "UNTLB " + key;
76  return empty_string;
77  }
78 }
79 
80 const t_string& symbol_table::operator[](const char* key) const
81 {
82  return (*this)[std::string(key)];
83 }
84 
85 utils::string_map::const_iterator symbol_table::find(const std::string& key) const
86 {
87  return strings_.find(key);
88 }
89 
90 utils::string_map::const_iterator symbol_table::end() const
91 {
92  return strings_.end();
93 }
94 
96  : language(t_string(N_("System default language"), "wesnoth"))
97  , sort_name("A")
98 {
99 }
100 
102 #ifndef _WIN32
103  : localename(cfg["locale"])
104  , alternates(utils::split(cfg["alternates"]))
105 #else
106  : localename(cfg["windows_locale"].str("C"))
107  , alternates(utils::split(cfg["windows_alternates"]))
108 #endif
109  , language(cfg["name"].t_str())
110  , sort_name(cfg["sort_name"].str(language))
111  , rtl(cfg["dir"] == "rtl")
112  , percent(cfg["percent"].to_int())
113 {
114 }
115 
117 {
118  try {
119  config cfg = io::read(*preprocess_file(filesystem::get_wml_location("hardwired/language.cfg").value()));
120 
121  known_languages.clear();
122  known_languages.emplace_back(); // System default language
123 
124  for(const config& lang : cfg.child_range("locale")) {
125  known_languages.emplace_back(lang);
126  }
127 
128  return true;
129 
130  } catch(const utils::bad_optional_access&) {
131  return false;
132  } catch(const config::error&) {
133  return false;
134  }
135 }
136 
137 std::vector<language_def> get_languages(bool all)
138 {
139  // We sort every time, the local might have changed which can modify the
140  // sort order.
141  std::sort(known_languages.begin(), known_languages.end());
142 
143  if(all || min_translation_percent == 0) {
144  LOG_G << "Found " << known_languages.size() << " known languages";
145  return known_languages;
146  }
147 
148  std::vector<language_def> result;
149  std::copy_if(known_languages.begin(), known_languages.end(), std::back_inserter(result),
150  [](const language_def& lang) { return lang.percent >= min_translation_percent; });
151 
152  LOG_G << "Found " << result.size() << " sufficiently translated languages";
153  return result;
154 }
155 
157 {
158  return min_translation_percent;
159 }
160 
161 void set_min_translation_percent(int percent) {
162  min_translation_percent = percent;
163 }
164 
165 
166 static void wesnoth_setlocale(int category, const std::string& slocale,
167  std::vector<std::string> const *alternates)
168 {
169  std::string locale = slocale;
170 
171  //category is never LC_MESSAGES since that case was moved to gettext.cpp to remove the dependency to libintl.h in this file
172  //that's why code like if (category == LC_MESSAGES) is outcommented here.
173 #ifdef __APPLE__
174  //if (category == LC_MESSAGES && setenv("LANG", locale.c_str(), 1) == -1) {
175  // ERR_G << "setenv LANG failed: " << strerror(errno);
176  //}
177 #endif
178 
179  char *res = nullptr;
180  std::vector<std::string>::const_iterator i;
181  if (alternates) i = alternates->begin();
182 
183  for (;;)
184  {
185  std::string lang = locale, extra;
186  std::string::size_type pos = locale.find('@');
187  if (pos != std::string::npos) {
188  lang.erase(pos);
189  extra = locale.substr(pos);
190  }
191 
192  /*
193  * The "" is the last item to work-around a problem in glibc picking
194  * the non utf8 locale instead an utf8 version if available.
195  */
196  char const *encoding[] { ".utf-8", ".UTF-8", "" };
197  for (int j = 0; j != 3; ++j)
198  {
199  locale = lang + encoding[j] + extra;
200  res = std::setlocale(category, locale.c_str());
201  if (res) {
202  LOG_G << "Set locale to '" << locale << "' result: '" << res << "'.";
203  goto done;
204  }
205  }
206 
207  if (!alternates || i == alternates->end()) break;
208  locale = *i;
209  ++i;
210  }
211 
212  WRN_G << "setlocale() failed for '" << slocale << "'.";
213 
214  if (category == LC_TIME) {
215  time_locale_correct() = false;
216  }
217 
218 #ifndef _WIN32
219  //if(category == LC_MESSAGES) {
220  // WRN_G << "Setting LANGUAGE to '" << slocale << "'.";
221  // setenv("LANGUAGE", slocale.c_str(), 1);
222  // std::setlocale(LC_MESSAGES, "");
223  //}
224 #endif
225 
226  done:
227  DBG_G << "Numeric locale: " << std::setlocale(LC_NUMERIC, nullptr);
228  DBG_G << "Full locale: " << std::setlocale(LC_ALL, nullptr);
229 }
230 
231 void set_language(const language_def& locale)
232 {
233  strings_.clear();
234 
235  std::string locale_lc;
236  locale_lc.resize(locale.localename.size());
237  std::transform(locale.localename.begin(), locale.localename.end(), locale_lc.begin(), tolower);
238 
239  current_language = locale;
240  time_locale_correct() = true;
241 
242  wesnoth_setlocale(LC_COLLATE, locale.localename, &locale.alternates);
243  wesnoth_setlocale(LC_TIME, locale.localename, &locale.alternates);
245  load_strings(false);
246 }
247 
248 bool load_strings(bool complain)
249 {
250  if(complain && languages.empty()) {
251  PLAIN_LOG << "No [language] block found";
252  return false;
253  }
254  for(const config& lang : languages) {
255  for(const auto& [key, value] : lang.attribute_range()) {
256  strings_[key] = value.t_str();
257  }
258  }
259 
260  return true;
261 }
262 
263 const language_def& get_language() { return current_language; }
264 
266 {
267  assert(!known_languages.empty());
268 
269  const std::string& prefs_locale = prefs::get().locale();
270  if(prefs_locale.empty() == false) {
271  translation::set_language(prefs_locale, nullptr);
272  for(const language_def& def : known_languages) {
273  if(prefs_locale == def.localename) {
274  return def;
275  }
276  }
277  LOG_G << "'" << prefs_locale << "' locale not found in known array; defaulting to system locale";
278  return known_languages[0];
279  }
280 
281  LOG_G << "locale could not be determined; defaulting to system locale";
282  return known_languages[0];
283 }
284 
286 {
287  for (const config &t : cfg.child_range("textdomain"))
288  {
289  const std::string &name = t["name"];
290  const std::string &path = t["path"];
291 
292  if(path.empty()) {
294  } else if(auto location = filesystem::get_binary_dir_location("", path)) {
295  t_string::add_textdomain(name, location.value());
296  } else {
297  // If location is empty, this causes a crash on Windows, so we disallow adding empty domains
298  WRN_G << "no location found for '" << path << "', skipping textdomain";
299  }
300  }
301 }
302 
304 {
305  languages.clear();
306  for (const config &l : cfg.child_range("language")) {
307  languages.push_back(l);
308  }
309  return load_strings(true);
310 }
double t
Definition: astarsearch.cpp:63
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
child_itors child_range(std::string_view key)
Definition: config.cpp:268
A class grating read only view to a vector of config objects, viewed as one config with all children ...
static prefs & get()
static void add_textdomain(const std::string &name, const std::string &path)
Definition: tstring.cpp:657
const config * cfg
Declarations for File-IO.
std::size_t i
Definition: function.cpp:1031
#define N_(String)
Definition: gettext.hpp:105
const language_def & get_locale()
Definition: language.cpp:265
std::vector< language_def > get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:137
static void wesnoth_setlocale(int category, const std::string &slocale, std::vector< std::string > const *alternates)
Definition: language.cpp:166
void init_textdomains(const game_config_view &cfg)
Initializes the list of textdomains from a configuration object.
Definition: language.cpp:285
bool load_strings(bool complain)
Definition: language.cpp:248
bool & time_locale_correct()
Definition: language.cpp:58
#define WRN_G
Definition: language.cpp:40
int get_min_translation_percent()
Definition: language.cpp:156
const language_def & get_language()
Definition: language.cpp:263
bool load_language_list()
Definition: language.cpp:116
bool init_strings(const game_config_view &cfg)
Initializes certain English strings.
Definition: language.cpp:303
void set_language(const language_def &locale)
Definition: language.cpp:231
#define LOG_G
Definition: language.cpp:39
#define DBG_G
Definition: language.cpp:38
void set_min_translation_percent(int percent)
Definition: language.cpp:161
Standard logging facilities (interface).
#define PLAIN_LOG
Definition: log.hpp:296
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir)
Returns a translated path to the actual file or directory, if it exists.
utils::optional< std::string > get_binary_dir_location(const std::string &type, const std::string &filename)
Returns a complete path to the actual directory of a given type, if it exists.
std::string get_intl_dir()
std::string path
Definition: filesystem.cpp:106
config read(std::istream &in, abstract_validator *validator)
Definition: parser.cpp:610
void set_language(const std::string &language, const std::vector< std::string > *)
Definition: gettext.cpp:494
constexpr auto transform
Definition: ranges.hpp:45
std::map< std::string, t_string > string_map
std::vector< std::string > split(const config_attribute_value &val)
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map &defines)
Function to use the WML preprocessor on a file.
bool operator==(const language_def &) const
Definition: language.cpp:53
t_string language
Definition: language.hpp:34
std::vector< std::string > alternates
Definition: language.hpp:33
std::string localename
Definition: language.hpp:32
language_def()
Creates the 'System Default Language' definition.
Definition: language.cpp:95
utils::string_map::const_iterator end() const
Definition: language.cpp:90
const t_string & operator[](const std::string &key) const
Look up the string mappings given in [language] tags.
Definition: language.cpp:64
utils::string_map::const_iterator find(const std::string &key) const
Look up the string mappings given in [language] tags.
Definition: language.cpp:85