The Battle for Wesnoth  1.19.0+dev
language.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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  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 {
145  return min_translation_percent;
146 }
147 
148 void set_min_translation_percent(int percent) {
149  min_translation_percent = percent;
150 }
151 
152 #ifdef _WIN32
153 // Simplified translation table from unix locale symbols to win32 locale strings
154 static const std::map<std::string, std::string> win32_locales_map = {
155  { "af", "Afrikaans" },
156  { "ang", "C" },
157  { "ar", "Arabic" },
158  { "bg", "Bulgarian" },
159  { "ca", "Catalan" },
160  { "cs", "Czech" },
161  { "da", "Danish" },
162  { "de", "German" },
163  { "el", "Greek" },
164  { "en", "English" },
165  { "eo", "C" },
166  { "es", "Spanish" },
167  { "et", "Estonian" },
168  { "eu", "Basque" },
169  { "fi", "Finnish" },
170  { "fr", "French" },
171  { "fur", "C" },
172  { "ga", "Irish_Ireland" }, // Yes, "Irish" alone does not work
173  { "gl", "Galician" },
174  { "he", "Hebrew" },
175  { "hr", "Croatian" },
176  { "hu", "Hungarian" },
177  { "id", "Indonesian" },
178  { "is", "Icelandic" },
179  { "it", "Italian" },
180  { "ja", "Japanese" },
181  { "ko", "Korean" },
182  { "la", "C" },
183  { "lt", "Lithuanian" },
184  { "lv", "Latvian" },
185  { "mk", "Macedonian" },
186  { "mr", "C" },
187  { "nb", "Norwegian" },
188  { "nl", "Dutch" },
189  { "pl", "Polish" },
190  { "pt", "Portuguese" },
191  { "racv", "C" },
192  { "ro", "Romanian" },
193  { "ru", "Russian" },
194  { "sk", "Slovak" },
195  { "sl", "Slovenian" },
196  { "sr", "Serbian" },
197  { "sv", "Swedish" },
198  { "tl", "Filipino" },
199  { "tr", "Turkish" },
200  { "uk", "Ukrainian" },
201  { "vi", "Vietnamese" },
202  { "zh", "Chinese" },
203 };
204 
205 static const std::string& posix_locale_to_win32(const std::string& posix)
206 {
207  auto it = win32_locales_map.find(posix);
208  return it != win32_locales_map.end() ? it->second : posix;
209 }
210 
211 #endif
212 
213 static void wesnoth_setlocale(int category, const std::string& slocale,
214  std::vector<std::string> const *alternates)
215 {
216  std::string locale = slocale;
217  // FIXME: ideally we should check LANGUAGE and on first invocation
218  // use that value, so someone with es would get the game in Spanish
219  // instead of en_US the first time round
220  // LANGUAGE overrides other settings, so for now just get rid of it
221  // FIXME: add configure check for unsetenv
222 
223  //category is never LC_MESSAGES since that case was moved to gettext.cpp to remove the dependency to libintl.h in this file
224  //that's why code like if (category == LC_MESSAGES) is outcommented here.
225 #ifndef _WIN32
226  unsetenv ("LANGUAGE"); // void so no return value to check
227 #endif
228 #ifdef __APPLE__
229  //if (category == LC_MESSAGES && setenv("LANG", locale.c_str(), 1) == -1) {
230  // ERR_G << "setenv LANG failed: " << strerror(errno);
231  //}
232 #endif
233 
234 #ifdef _WIN32
235  std::string lang_code{locale, 0, locale.find_first_of("_@.")};
236  locale = posix_locale_to_win32(lang_code);
237 #endif
238 
239  char *res = nullptr;
240  std::vector<std::string>::const_iterator i;
241  if (alternates) i = alternates->begin();
242 
243  for (;;)
244  {
245  std::string lang = locale, extra;
246  std::string::size_type pos = locale.find('@');
247  if (pos != std::string::npos) {
248  lang.erase(pos);
249  extra = locale.substr(pos);
250  }
251 
252  /*
253  * The "" is the last item to work-around a problem in glibc picking
254  * the non utf8 locale instead an utf8 version if available.
255  */
256  char const *encoding[] { ".utf-8", ".UTF-8", "" };
257  for (int j = 0; j != 3; ++j)
258  {
259  locale = lang + encoding[j] + extra;
260  res = std::setlocale(category, locale.c_str());
261  if (res) {
262  LOG_G << "Set locale to '" << locale << "' result: '" << res << "'.";
263  goto done;
264  }
265  }
266 
267  if (!alternates || i == alternates->end()) break;
268  locale = *i;
269  ++i;
270  }
271 
272  WRN_G << "setlocale() failed for '" << slocale << "'.";
273 
274  if (category == LC_TIME) {
275  time_locale_correct() = false;
276  }
277 
278 #ifndef _WIN32
279  //if(category == LC_MESSAGES) {
280  // WRN_G << "Setting LANGUAGE to '" << slocale << "'.";
281  // setenv("LANGUAGE", slocale.c_str(), 1);
282  // std::setlocale(LC_MESSAGES, "");
283  //}
284 #endif
285 
286  done:
287  DBG_G << "Numeric locale: " << std::setlocale(LC_NUMERIC, nullptr);
288  DBG_G << "Full locale: " << std::setlocale(LC_ALL, nullptr);
289 }
290 
291 void set_language(const language_def& locale)
292 {
293  strings_.clear();
294 
295  std::string locale_lc;
296  locale_lc.resize(locale.localename.size());
297  std::transform(locale.localename.begin(),locale.localename.end(),locale_lc.begin(),tolower);
298 
299  current_language = locale;
300  time_locale_correct() = true;
301 
302  wesnoth_setlocale(LC_COLLATE, locale.localename, &locale.alternates);
303  wesnoth_setlocale(LC_TIME, locale.localename, &locale.alternates);
305  load_strings(false);
306 }
307 
308 bool load_strings(bool complain)
309 {
310  DBG_G << "Loading strings";
311  config cfg;
312 
313  LOG_G << "There are " << languages_.size() << " [language] blocks";
314  if (complain && languages_.empty()) {
315  PLAIN_LOG << "No [language] block found";
316  return false;
317  }
318  for (const config &lang : languages_) {
319  DBG_G << "[language]";
320  for (const config::attribute &j : lang.attribute_range()) {
321  DBG_G << j.first << "=\"" << j.second << "\"";
322  strings_[j.first] = j.second;
323  }
324  DBG_G << "[/language]";
325  }
326  DBG_G << "done";
327 
328  return true;
329 }
330 
331 const language_def& get_language() { return current_language; }
332 
334 {
335  //TODO: Add in support for querying the locale on Windows
336 
337  assert(!known_languages.empty());
338 
339  const std::string& prefs_locale = prefs::get().language();
340  if(prefs_locale.empty() == false) {
341  translation::set_language(prefs_locale, nullptr);
342  for(language_list::const_iterator i = known_languages.begin();
343  i != known_languages.end(); ++i) {
344  if (prefs_locale == i->localename)
345  return *i;
346  }
347  LOG_G << "'" << prefs_locale << "' locale not found in known array; defaulting to system locale";
348  return known_languages[0];
349  }
350 
351 #if 0
352  const char* const locale = getenv("LANG");
353  #ifdef _WIN32
354  return posix_locale_to_win32(locale);
355  #endif
356  if(locale != nullptr && strlen(locale) >= 2) {
357  //we can't pass pointers into the string to the std::string
358  //constructor because some STL implementations don't support
359  //it (*cough* MSVC++6)
360  std::string res(2,'z');
361  res[0] = tolower(locale[0]);
362  res[1] = tolower(locale[1]);
363  return res;
364  }
365 #endif
366 
367  LOG_G << "locale could not be determined; defaulting to system locale";
368  return known_languages[0];
369 }
370 
372 {
373  for (const config &t : cfg.child_range("textdomain"))
374  {
375  const std::string &name = t["name"];
376  const std::string &path = t["path"];
377 
378  if(path.empty()) {
380  } else {
381  std::string location = filesystem::get_binary_dir_location("", path);
382 
383  if (location.empty()) {
384  //if location is empty, this causes a crash on Windows, so we
385  //disallow adding empty domains
386  WRN_G << "no location found for '" << path << "', skipping textdomain";
387  } else {
388  t_string::add_textdomain(name, location);
389  }
390  }
391  }
392 }
393 
395 {
396  languages_.clear();
397  for (const config &l : cfg.child_range("language")) {
398  languages_.push_back(l);
399  }
400  return load_strings(true);
401 }
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:159
child_itors child_range(config_key_type key)
Definition: config.cpp:273
attribute_map::value_type attribute
Definition: config.hpp:299
A class grating read only view to a vector of config objects, viewed as one config with all children ...
config_array_view child_range(config_key_type key) const
std::string language()
static prefs & get()
static void add_textdomain(const std::string &name, const std::string &path)
Definition: tstring.cpp:643
Declarations for File-IO.
std::size_t i
Definition: function.cpp:968
#define N_(String)
Definition: gettext.hpp:101
const language_def & get_locale()
Definition: language.cpp:333
static void wesnoth_setlocale(int category, const std::string &slocale, std::vector< std::string > const *alternates)
Definition: language.cpp:213
bool current_language_rtl()
Definition: language.cpp:54
void init_textdomains(const game_config_view &cfg)
Initializes the list of textdomains from a configuration object.
Definition: language.cpp:371
bool load_strings(bool complain)
Definition: language.cpp:308
bool & time_locale_correct()
Definition: language.cpp:66
#define WRN_G
Definition: language.cpp:40
int get_min_translation_percent()
Definition: language.cpp:143
const language_def & get_language()
Definition: language.cpp:331
bool load_language_list()
Definition: language.cpp:103
bool init_strings(const game_config_view &cfg)
Initializes certain English strings.
Definition: language.cpp:394
void set_language(const language_def &locale)
Definition: language.cpp:291
static language_list known_languages
Definition: language.cpp:50
language_list get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:126
symbol_table string_table
Definition: language.cpp:64
#define LOG_G
Definition: language.cpp:39
#define DBG_G
Definition: language.cpp:38
void set_min_translation_percent(int percent)
Definition: language.cpp:148
std::vector< language_def > language_list
Definition: language.hpp:65
Standard logging facilities (interface).
#define PLAIN_LOG
Definition: log.hpp:295
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't pres...
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...
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:50
std::string get_intl_dir()
std::string path
Definition: filesystem.cpp:84
void set_language(const std::string &language, const std::vector< std::string > *)
Definition: gettext.cpp:494
std::map< std::string, t_string > string_map
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map *defines)
Function to use the WML preprocessor on a file.
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:627
bool operator==(const language_def &) const
Definition: language.cpp:59
t_string language
Definition: language.hpp:55
std::vector< std::string > alternates
Definition: language.hpp:54
std::string localename
Definition: language.hpp:53
utils::string_map::const_iterator end() const
Definition: language.cpp:98
const t_string & operator[](const std::string &key) const
Look up the string mappings given in [language] tags.
Definition: language.cpp:72
utils::string_map::const_iterator find(const std::string &key) const
Look up the string mappings given in [language] tags.
Definition: language.cpp:93
#define a