The Battle for Wesnoth  1.15.1+dev
language.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #include "filesystem.hpp"
16 #include "gettext.hpp"
17 #include "language.hpp"
18 #include "log.hpp"
19 #include "preferences/general.hpp"
20 #include "serialization/parser.hpp"
22 
23 #include <stdexcept>
24 #include <clocale>
25 
26 #ifdef _WIN32
27 #include <windows.h>
28 #if !defined(_MSC_VER) && !defined(__MINGW32__)
29 extern "C" int _putenv(const char*);
30 #endif
31 #endif
32 
33 #ifdef __APPLE__
34 #include <cerrno>
35 #endif
36 
37 #define DBG_G LOG_STREAM(debug, lg::general())
38 #define LOG_G LOG_STREAM(info, lg::general())
39 #define WRN_G LOG_STREAM(warn, lg::general())
40 #define ERR_G LOG_STREAM(err, lg::general())
41 
42 namespace {
43  language_def current_language;
44  std::vector<config> languages_;
45  utils::string_map strings_;
46  int min_translation_percent = 80;
47 }
48 
50 
51 bool load_strings(bool complain);
52 
54 {
55  return get_language().rtl;
56 }
57 
59 {
60  return ((language == a.language) /* && (localename == a.localename) */ );
61 }
62 
64 
66 {
67  static bool result = true;
68  return result;
69 }
70 
71 const t_string& symbol_table::operator[](const std::string& key) const
72 {
73  const utils::string_map::const_iterator i = strings_.find(key);
74  if(i != strings_.end()) {
75  return i->second;
76  } else {
77  static t_string empty_string;
78  // Let's do it the painful way (untlb means untranslatABLE).
79  // It will cause problem if somebody stores more than one reference at once
80  // but I don't really care since this path is an error path and it should
81  // not have been taken in the first place. -- silene
82  empty_string = "UNTLB " + key;
83  return empty_string;
84  }
85 }
86 
87 const t_string& symbol_table::operator[](const char* key) const
88 {
89  return (*this)[std::string(key)];
90 }
91 
93 {
94  config cfg;
95  try {
97  read(cfg, *stream);
98  } catch(const config::error &) {
99  return false;
100  }
101 
102  known_languages.clear();
103  known_languages.emplace_back("", t_string(N_("System default language"), "wesnoth"), "ltr", "", "A", "100");
104 
105  for (const config &lang : cfg.child_range("locale"))
106  {
107  known_languages.emplace_back(
108  lang["locale"], lang["name"], lang["dir"],
109  lang["alternates"], lang["sort_name"], lang["percent"]);
110  }
111 
112  return true;
113 }
114 
116 {
117  // We sort every time, the local might have changed which can modify the
118  // sort order.
119  std::sort(known_languages.begin(), known_languages.end());
120 
121  if(min_translation_percent == 0) {
122  return known_languages;
123  }
124 
125  language_list result;
126  std::copy_if(known_languages.begin(), known_languages.end(), std::back_inserter(result),
127  [](const language_def& lang) { return lang.percent >= min_translation_percent; });
128 
129  return result;
130 }
131 
133  min_translation_percent = percent;
134 }
135 
136 static void wesnoth_setlocale(int category, const std::string& slocale,
137  std::vector<std::string> const *alternates)
138 {
139  std::string locale = slocale;
140  // FIXME: ideally we should check LANGUAGE and on first invocation
141  // use that value, so someone with es would get the game in Spanish
142  // instead of en_US the first time round
143  // LANGUAGE overrides other settings, so for now just get rid of it
144  // FIXME: add configure check for unsetenv
145 
146  //category is never LC_MESSAGES since that case was moved to gettext.cpp to remove the dependency to libintl.h in this file
147  //that's why code like if (category == LC_MESSAGES) is outcommented here.
148 #ifndef _WIN32
149  unsetenv ("LANGUAGE"); // void so no return value to check
150 #endif
151 #ifdef __APPLE__
152  //if (category == LC_MESSAGES && setenv("LANG", locale.c_str(), 1) == -1) {
153  // ERR_G << "setenv LANG failed: " << strerror(errno);
154  //}
155 #endif
156 
157 #ifdef _WIN32
158  std::string win_locale(locale, 0, 2);
159  #include "language_win32.ii"
160  //if(category == LC_MESSAGES) {
161  // SetEnvironmentVariableA("LANG", win_locale.c_str());
162  // std::string env = "LANGUAGE=" + locale;
163  // _putenv(env.c_str());
164  // return;
165  //}
166  locale = win_locale;
167 #endif
168 
169  char *res = nullptr;
170  std::vector<std::string>::const_iterator i;
171  if (alternates) i = alternates->begin();
172 
173  for (;;)
174  {
175  std::string lang = locale, extra;
176  std::string::size_type pos = locale.find('@');
177  if (pos != std::string::npos) {
178  lang.erase(pos);
179  extra = locale.substr(pos);
180  }
181 
182  /*
183  * The "" is the last item to work-around a problem in glibc picking
184  * the non utf8 locale instead an utf8 version if available.
185  */
186  char const *encoding[] { ".utf-8", ".UTF-8", "" };
187  for (int j = 0; j != 3; ++j)
188  {
189  locale = lang + encoding[j] + extra;
190  res = std::setlocale(category, locale.c_str());
191  if (res) {
192  LOG_G << "Set locale to '" << locale << "' result: '" << res << "'.\n";
193  goto done;
194  }
195  }
196 
197  if (!alternates || i == alternates->end()) break;
198  locale = *i;
199  ++i;
200  }
201 
202  WRN_G << "setlocale() failed for '" << slocale << "'." << std::endl;
203 
204  if (category == LC_TIME) {
205  time_locale_correct() = false;
206  }
207 
208 #ifndef _WIN32
209  //if(category == LC_MESSAGES) {
210  // WRN_G << "Setting LANGUAGE to '" << slocale << "'." << std::endl;
211  // setenv("LANGUAGE", slocale.c_str(), 1);
212  // std::setlocale(LC_MESSAGES, "");
213  //}
214 #endif
215 
216  done:
217  DBG_G << "Numeric locale: " << std::setlocale(LC_NUMERIC, nullptr) << '\n';
218  DBG_G << "Full locale: " << std::setlocale(LC_ALL, nullptr) << '\n';
219 }
220 
221 void set_language(const language_def& locale)
222 {
223  strings_.clear();
224 
225  std::string locale_lc;
226  locale_lc.resize(locale.localename.size());
227  std::transform(locale.localename.begin(),locale.localename.end(),locale_lc.begin(),tolower);
228 
229  current_language = locale;
230  time_locale_correct() = true;
231 
232  wesnoth_setlocale(LC_COLLATE, locale.localename, &locale.alternates);
233  wesnoth_setlocale(LC_TIME, locale.localename, &locale.alternates);
235  load_strings(false);
236 }
237 
238 bool load_strings(bool complain)
239 {
240  DBG_G << "Loading strings\n";
241  config cfg;
242 
243  LOG_G << "There are " << languages_.size() << " [language] blocks\n";
244  if (complain && languages_.empty()) {
245  std::cerr << "No [language] block found\n";
246  return false;
247  }
248  for (const config &lang : languages_) {
249  DBG_G << "[language]\n";
250  for (const config::attribute &j : lang.attribute_range()) {
251  DBG_G << j.first << "=\"" << j.second << "\"\n";
252  strings_[j.first] = j.second;
253  }
254  DBG_G << "[/language]\n";
255  }
256  DBG_G << "done\n";
257 
258  return true;
259 }
260 
261 const language_def& get_language() { return current_language; }
262 
264 {
265  //TODO: Add in support for querying the locale on Windows
266 
267  assert(!known_languages.empty());
268 
269  const std::string& prefs_locale = preferences::language();
270  if(prefs_locale.empty() == false) {
271  translation::set_language(prefs_locale, nullptr);
272  for(language_list::const_iterator i = known_languages.begin();
273  i != known_languages.end(); ++i) {
274  if (prefs_locale == i->localename)
275  return *i;
276  }
277  LOG_G << "'" << prefs_locale << "' locale not found in known array; defaulting to system locale\n";
278  return known_languages[0];
279  }
280 
281 #if 0
282  const char* const locale = getenv("LANG");
283  #ifdef _WIN32
284  std::string win_locale = locale
285  #include "language_win32.ii"
286  return win_locale;
287  #endif
288  if(locale != nullptr && strlen(locale) >= 2) {
289  //we can't pass pointers into the string to the std::string
290  //constructor because some STL implementations don't support
291  //it (*cough* MSVC++6)
292  std::string res(2,'z');
293  res[0] = tolower(locale[0]);
294  res[1] = tolower(locale[1]);
295  return res;
296  }
297 #endif
298 
299  LOG_G << "locale could not be determined; defaulting to system locale\n";
300  return known_languages[0];
301 }
302 
303 void init_textdomains(const config& cfg)
304 {
305  for (const config &t : cfg.child_range("textdomain"))
306  {
307  const std::string &name = t["name"];
308  const std::string &path = t["path"];
309 
310  if(path.empty()) {
312  } else {
313  std::string location = filesystem::get_binary_dir_location("", path);
314 
315  if (location.empty()) {
316  //if location is empty, this causes a crash on Windows, so we
317  //disallow adding empty domains
318  WRN_G << "no location found for '" << path << "', skipping textdomain" << std::endl;
319  } else {
320  t_string::add_textdomain(name, location);
321  }
322  }
323  }
324 }
325 
326 bool init_strings(const config& cfg)
327 {
328  languages_.clear();
329  for (const config &l : cfg.child_range("language")) {
330  languages_.push_back(l);
331  }
332  return load_strings(true);
333 }
334 
335 /* vim:set encoding=utf-8: */
bool & time_locale_correct()
Definition: language.cpp:65
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 or an empty string if the directory i...
#define WRN_G
Definition: language.cpp:39
std::map< std::string, t_string > string_map
#define a
child_itors child_range(config_key_type key)
Definition: config.cpp:362
attribute_map::value_type attribute
Definition: config.hpp:256
const language_def & get_locale()
Definition: language.cpp:263
void set_language(const std::string &language, const std::vector< std::string > *)
Definition: gettext.cpp:449
void set_min_translation_percent(int percent)
Definition: language.cpp:132
bool operator==(const language_def &) const
Definition: language.cpp:58
static language_list known_languages
Definition: language.cpp:49
#define LOG_G
Definition: language.cpp:38
static const char * name(const std::vector< SDL_Joystick *> &joysticks, const std::size_t index)
Definition: joystick.cpp:48
bool current_language_rtl()
Definition: language.cpp:53
static void add_textdomain(const std::string &name, const std::string &path)
Definition: tstring.cpp:646
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:625
std::string get_intl_dir()
std::string localename
Definition: language.hpp:52
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:39
std::string path
Definition: game_config.cpp:39
static void wesnoth_setlocale(int category, const std::string &slocale, std::vector< std::string > const *alternates)
Definition: language.cpp:136
void init_textdomains(const config &cfg)
Initializes the list of textdomains from a configuration object.
Definition: language.cpp:303
std::size_t i
Definition: function.cpp:933
std::string get_wml_location(const std::string &filename, const std::string &current_dir)
Returns a complete path to the actual WML file or directory or an empty string if the file isn&#39;t pres...
const language_def & get_language()
Definition: language.cpp:261
bool load_language_list()
Definition: language.cpp:92
std::vector< language_def > language_list
Definition: language.hpp:64
language_list get_languages()
Definition: language.cpp:115
std::string language()
Definition: general.cpp:475
Declarations for File-IO.
#define N_(String)
Definition: gettext.hpp:99
static int sort(lua_State *L)
Definition: ltablib.cpp:411
t_string language
Definition: language.hpp:54
int percent
% of translated text in core po-s
Definition: language.hpp:58
void set_language(const language_def &locale)
Definition: language.cpp:221
#define DBG_G
Definition: language.cpp:37
double t
Definition: astarsearch.cpp:64
symbol_table string_table
Definition: language.cpp:63
const t_string & operator[](const std::string &key) const
Definition: language.cpp:71
Standard logging facilities (interface).
bool init_strings(const config &cfg)
Initializes certain English strings.
Definition: language.cpp:326
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map *defines)
bool load_strings(bool complain)
Definition: language.cpp:238
std::vector< std::string > alternates
Definition: language.hpp:53