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"
20 #include "preferences/general.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 
143 void set_min_translation_percent(int percent) {
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 << "'.";
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 << "'.";
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 << "'.";
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);
283  DBG_G << "Full locale: " << std::setlocale(LC_ALL, nullptr);
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";
306  config cfg;
307 
308  LOG_G << "There are " << languages_.size() << " [language] blocks";
309  if (complain && languages_.empty()) {
310  PLAIN_LOG << "No [language] block found";
311  return false;
312  }
313  for (const config &lang : languages_) {
314  DBG_G << "[language]";
315  for (const config::attribute &j : lang.attribute_range()) {
316  DBG_G << j.first << "=\"" << j.second << "\"";
317  strings_[j.first] = j.second;
318  }
319  DBG_G << "[/language]";
320  }
321  DBG_G << "done";
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";
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";
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";
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 }
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
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:328
static void wesnoth_setlocale(int category, const std::string &slocale, std::vector< std::string > const *alternates)
Definition: language.cpp:208
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:366
bool load_strings(bool complain)
Definition: language.cpp:303
bool & time_locale_correct()
Definition: language.cpp:66
#define WRN_G
Definition: language.cpp:40
const language_def & get_language()
Definition: language.cpp:326
bool load_language_list()
Definition: language.cpp:103
bool init_strings(const game_config_view &cfg)
Initializes certain English strings.
Definition: language.cpp:389
void set_language(const language_def &locale)
Definition: language.cpp:286
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:143
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:83
std::string language()
Definition: general.cpp:535
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