The Battle for Wesnoth  1.15.13+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 #include "game_config_manager.hpp"
23 
24 #include <stdexcept>
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  utils::string_map strings_;
47  int min_translation_percent = 80;
48 }
49 
51 
52 bool load_strings(bool complain);
53 
55 {
56  return get_language().rtl;
57 }
58 
60 {
61  return ((language == a.language) /* && (localename == a.localename) */ );
62 }
63 
65 
67 {
68  static bool result = true;
69  return result;
70 }
71 
72 const t_string& symbol_table::operator[](const std::string& key) const
73 {
74  const utils::string_map::const_iterator i = strings_.find(key);
75  if(i != strings_.end()) {
76  return i->second;
77  } else {
78  static t_string empty_string;
79  // Let's do it the painful way (untlb means untranslatABLE).
80  // It will cause problem if somebody stores more than one reference at once
81  // but I don't really care since this path is an error path and it should
82  // not have been taken in the first place. -- silene
83  empty_string = "UNTLB " + key;
84  return empty_string;
85  }
86 }
87 
88 const t_string& symbol_table::operator[](const char* key) const
89 {
90  return (*this)[std::string(key)];
91 }
92 
93 utils::string_map::const_iterator symbol_table::find(const std::string& key) const
94 {
95  return strings_.find(key);
96 }
97 
98 utils::string_map::const_iterator symbol_table::end() const
99 {
100  return strings_.end();
101 }
102 
104 {
105  config cfg;
106  try {
107  filesystem::scoped_istream stream = preprocess_file(filesystem::get_wml_location("hardwired/language.cfg"));
108  read(cfg, *stream);
109  } catch(const config::error &) {
110  return false;
111  }
112 
113  known_languages.clear();
114  known_languages.emplace_back("", t_string(N_("System default language"), "wesnoth"), "ltr", "", "A", "100");
115 
116  for (const config &lang : cfg.child_range("locale"))
117  {
118  known_languages.emplace_back(
119  lang["locale"], lang["name"], lang["dir"],
120  lang["alternates"], lang["sort_name"], lang["percent"]);
121  }
122 
123  return true;
124 }
125 
127 {
128  // We sort every time, the local might have changed which can modify the
129  // sort order.
130  std::sort(known_languages.begin(), known_languages.end());
131 
132  if(all || min_translation_percent == 0) {
133  return known_languages;
134  }
135 
136  language_list result;
137  std::copy_if(known_languages.begin(), known_languages.end(), std::back_inserter(result),
138  [](const language_def& lang) { return lang.percent >= min_translation_percent; });
139 
140  return result;
141 }
142 
144  min_translation_percent = percent;
145 }
146 
147 #ifdef _WIN32
148 // Simplified translation table from unix locale symbols to win32 locale strings
149 static const std::map<std::string, std::string> win32_locales_map = {
150  { "af", "Afrikaans" },
151  { "ang", "C" },
152  { "ar", "Arabic" },
153  { "bg", "Bulgarian" },
154  { "ca", "Catalan" },
155  { "cs", "Czech" },
156  { "da", "Danish" },
157  { "de", "German" },
158  { "el", "Greek" },
159  { "en", "English" },
160  { "eo", "C" },
161  { "es", "Spanish" },
162  { "et", "Estonian" },
163  { "eu", "Basque" },
164  { "fi", "Finnish" },
165  { "fr", "French" },
166  { "fur", "C" },
167  { "ga", "Irish_Ireland" }, // Yes, "Irish" alone does not work
168  { "gl", "Galician" },
169  { "he", "Hebrew" },
170  { "hr", "Croatian" },
171  { "hu", "Hungarian" },
172  { "id", "Indonesian" },
173  { "is", "Icelandic" },
174  { "it", "Italian" },
175  { "ja", "Japanese" },
176  { "ko", "Korean" },
177  { "la", "C" },
178  { "lt", "Lithuanian" },
179  { "lv", "Latvian" },
180  { "mk", "Macedonian" },
181  { "mr", "C" },
182  { "nb", "Norwegian" },
183  { "nl", "Dutch" },
184  { "pl", "Polish" },
185  { "pt", "Portuguese" },
186  { "racv", "C" },
187  { "ro", "Romanian" },
188  { "ru", "Russian" },
189  { "sk", "Slovak" },
190  { "sl", "Slovenian" },
191  { "sr", "Serbian" },
192  { "sv", "Swedish" },
193  { "tl", "Filipino" },
194  { "tr", "Turkish" },
195  { "uk", "Ukrainian" },
196  { "vi", "Vietnamese" },
197  { "zh", "Chinese" },
198 };
199 
200 static const std::string& posix_locale_to_win32(const std::string& posix)
201 {
202  auto it = win32_locales_map.find(posix);
203  return it != win32_locales_map.end() ? it->second : posix;
204 }
205 
206 #endif
207 
208 static void wesnoth_setlocale(int category, const std::string& slocale,
209  std::vector<std::string> const *alternates)
210 {
211  std::string locale = slocale;
212  // FIXME: ideally we should check LANGUAGE and on first invocation
213  // use that value, so someone with es would get the game in Spanish
214  // instead of en_US the first time round
215  // LANGUAGE overrides other settings, so for now just get rid of it
216  // FIXME: add configure check for unsetenv
217 
218  //category is never LC_MESSAGES since that case was moved to gettext.cpp to remove the dependency to libintl.h in this file
219  //that's why code like if (category == LC_MESSAGES) is outcommented here.
220 #ifndef _WIN32
221  unsetenv ("LANGUAGE"); // void so no return value to check
222 #endif
223 #ifdef __APPLE__
224  //if (category == LC_MESSAGES && setenv("LANG", locale.c_str(), 1) == -1) {
225  // ERR_G << "setenv LANG failed: " << strerror(errno);
226  //}
227 #endif
228 
229 #ifdef _WIN32
230  std::string lang_code{locale, 0, locale.find_first_of("_@.")};
231  locale = posix_locale_to_win32(lang_code);
232 #endif
233 
234  char *res = nullptr;
235  std::vector<std::string>::const_iterator i;
236  if (alternates) i = alternates->begin();
237 
238  for (;;)
239  {
240  std::string lang = locale, extra;
241  std::string::size_type pos = locale.find('@');
242  if (pos != std::string::npos) {
243  lang.erase(pos);
244  extra = locale.substr(pos);
245  }
246 
247  /*
248  * The "" is the last item to work-around a problem in glibc picking
249  * the non utf8 locale instead an utf8 version if available.
250  */
251  char const *encoding[] { ".utf-8", ".UTF-8", "" };
252  for (int j = 0; j != 3; ++j)
253  {
254  locale = lang + encoding[j] + extra;
255  res = std::setlocale(category, locale.c_str());
256  if (res) {
257  LOG_G << "Set locale to '" << locale << "' result: '" << res << "'.\n";
258  goto done;
259  }
260  }
261 
262  if (!alternates || i == alternates->end()) break;
263  locale = *i;
264  ++i;
265  }
266 
267  WRN_G << "setlocale() failed for '" << slocale << "'." << std::endl;
268 
269  if (category == LC_TIME) {
270  time_locale_correct() = false;
271  }
272 
273 #ifndef _WIN32
274  //if(category == LC_MESSAGES) {
275  // WRN_G << "Setting LANGUAGE to '" << slocale << "'." << std::endl;
276  // setenv("LANGUAGE", slocale.c_str(), 1);
277  // std::setlocale(LC_MESSAGES, "");
278  //}
279 #endif
280 
281  done:
282  DBG_G << "Numeric locale: " << std::setlocale(LC_NUMERIC, nullptr) << '\n';
283  DBG_G << "Full locale: " << std::setlocale(LC_ALL, nullptr) << '\n';
284 }
285 
286 void set_language(const language_def& locale)
287 {
288  strings_.clear();
289 
290  std::string locale_lc;
291  locale_lc.resize(locale.localename.size());
292  std::transform(locale.localename.begin(),locale.localename.end(),locale_lc.begin(),tolower);
293 
294  current_language = locale;
295  time_locale_correct() = true;
296 
297  wesnoth_setlocale(LC_COLLATE, locale.localename, &locale.alternates);
298  wesnoth_setlocale(LC_TIME, locale.localename, &locale.alternates);
300  load_strings(false);
301 }
302 
303 bool load_strings(bool complain)
304 {
305  DBG_G << "Loading strings\n";
306  config cfg;
307 
308  LOG_G << "There are " << languages_.size() << " [language] blocks\n";
309  if (complain && languages_.empty()) {
310  std::cerr << "No [language] block found\n";
311  return false;
312  }
313  for (const config &lang : languages_) {
314  DBG_G << "[language]\n";
315  for (const config::attribute &j : lang.attribute_range()) {
316  DBG_G << j.first << "=\"" << j.second << "\"\n";
317  strings_[j.first] = j.second;
318  }
319  DBG_G << "[/language]\n";
320  }
321  DBG_G << "done\n";
322 
323  return true;
324 }
325 
326 const language_def& get_language() { return current_language; }
327 
329 {
330  //TODO: Add in support for querying the locale on Windows
331 
332  assert(!known_languages.empty());
333 
334  const std::string& prefs_locale = preferences::language();
335  if(prefs_locale.empty() == false) {
336  translation::set_language(prefs_locale, nullptr);
337  for(language_list::const_iterator i = known_languages.begin();
338  i != known_languages.end(); ++i) {
339  if (prefs_locale == i->localename)
340  return *i;
341  }
342  LOG_G << "'" << prefs_locale << "' locale not found in known array; defaulting to system locale\n";
343  return known_languages[0];
344  }
345 
346 #if 0
347  const char* const locale = getenv("LANG");
348  #ifdef _WIN32
349  return posix_locale_to_win32(locale);
350  #endif
351  if(locale != nullptr && strlen(locale) >= 2) {
352  //we can't pass pointers into the string to the std::string
353  //constructor because some STL implementations don't support
354  //it (*cough* MSVC++6)
355  std::string res(2,'z');
356  res[0] = tolower(locale[0]);
357  res[1] = tolower(locale[1]);
358  return res;
359  }
360 #endif
361 
362  LOG_G << "locale could not be determined; defaulting to system locale\n";
363  return known_languages[0];
364 }
365 
367 {
368  for (const config &t : cfg.child_range("textdomain"))
369  {
370  const std::string &name = t["name"];
371  const std::string &path = t["path"];
372 
373  if(path.empty()) {
375  } else {
376  std::string location = filesystem::get_binary_dir_location("", path);
377 
378  if (location.empty()) {
379  //if location is empty, this causes a crash on Windows, so we
380  //disallow adding empty domains
381  WRN_G << "no location found for '" << path << "', skipping textdomain" << std::endl;
382  } else {
383  t_string::add_textdomain(name, location);
384  }
385  }
386  }
387 }
388 
390 {
391  languages_.clear();
392  for (const config &l : cfg.child_range("language")) {
393  languages_.push_back(l);
394  }
395  return load_strings(true);
396 }
397 
398 /* vim:set encoding=utf-8: */
bool & time_locale_correct()
Definition: language.cpp:66
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:40
std::map< std::string, t_string > string_map
#define a
config_array_view child_range(config_key_type key) const
utils::string_map::const_iterator find(const std::string &key) const
Look up the string mappings given in [language] tags.
Definition: language.cpp:93
child_itors child_range(config_key_type key)
Definition: config.cpp:356
attribute_map::value_type attribute
Definition: config.hpp:220
const language_def & get_locale()
Definition: language.cpp:328
void set_language(const std::string &language, const std::vector< std::string > *)
Definition: gettext.cpp:447
void set_min_translation_percent(int percent)
Definition: language.cpp:143
void init_textdomains(const game_config_view &cfg)
Initializes the list of textdomains from a configuration object.
Definition: language.cpp:366
bool operator==(const language_def &) const
Definition: language.cpp:59
static language_list known_languages
Definition: language.cpp:50
#define LOG_G
Definition: language.cpp:39
bool current_language_rtl()
Definition: language.cpp:54
static void add_textdomain(const std::string &name, const std::string &path)
Definition: tstring.cpp:643
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:626
std::string get_intl_dir()
std::string localename
Definition: language.hpp:54
language_list get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:126
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:37
std::string path
Definition: game_config.cpp:38
static void wesnoth_setlocale(int category, const std::string &slocale, std::vector< std::string > const *alternates)
Definition: language.cpp:208
bool init_strings(const game_config_view &cfg)
Initializes certain English strings.
Definition: language.cpp:389
utils::string_map::const_iterator end() const
Definition: language.cpp:98
std::size_t i
Definition: function.cpp:940
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:326
bool load_language_list()
Definition: language.cpp:103
std::vector< language_def > language_list
Definition: language.hpp:66
std::string language()
Definition: general.cpp:489
Declarations for File-IO.
#define N_(String)
Definition: gettext.hpp:100
static int sort(lua_State *L)
Definition: ltablib.cpp:397
t_string language
Definition: language.hpp:56
int percent
% of translated text in core po-s
Definition: language.hpp:60
void set_language(const language_def &locale)
Definition: language.cpp:286
#define DBG_G
Definition: language.cpp:38
double t
Definition: astarsearch.cpp:64
symbol_table string_table
Definition: language.cpp:64
const t_string & operator[](const std::string &key) const
Look up the string mappings given in [language] tags.
Definition: language.cpp:72
Standard logging facilities (interface).
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:59
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map *defines)
Function to use the WML preprocessor on a file.
bool load_strings(bool complain)
Definition: language.cpp:303
std::vector< std::string > alternates
Definition: language.hpp:55