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