The Battle for Wesnoth  1.19.5+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").value());
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  config cfg;
311 
312  if (complain && languages_.empty()) {
313  PLAIN_LOG << "No [language] block found";
314  return false;
315  }
316  for (const config &lang : languages_) {
317  for(const auto& [key, value] : lang.attribute_range()) {
318  strings_[key] = value;
319  }
320  }
321 
322  return true;
323 }
324 
325 const language_def& get_language() { return current_language; }
326 
328 {
329  //TODO: Add in support for querying the locale on Windows
330 
331  assert(!known_languages.empty());
332 
333  const std::string& prefs_locale = prefs::get().locale();
334  if(prefs_locale.empty() == false) {
335  translation::set_language(prefs_locale, nullptr);
336  for(language_list::const_iterator i = known_languages.begin();
337  i != known_languages.end(); ++i) {
338  if (prefs_locale == i->localename)
339  return *i;
340  }
341  LOG_G << "'" << prefs_locale << "' locale not found in known array; defaulting to system locale";
342  return known_languages[0];
343  }
344 
345 #if 0
346  const char* const locale = getenv("LANG");
347  #ifdef _WIN32
348  return posix_locale_to_win32(locale);
349  #endif
350  if(locale != nullptr && strlen(locale) >= 2) {
351  //we can't pass pointers into the string to the std::string
352  //constructor because some STL implementations don't support
353  //it (*cough* MSVC++6)
354  std::string res(2,'z');
355  res[0] = tolower(locale[0]);
356  res[1] = tolower(locale[1]);
357  return res;
358  }
359 #endif
360 
361  LOG_G << "locale could not be determined; defaulting to system locale";
362  return known_languages[0];
363 }
364 
366 {
367  for (const config &t : cfg.child_range("textdomain"))
368  {
369  const std::string &name = t["name"];
370  const std::string &path = t["path"];
371 
372  if(path.empty()) {
374  } else if(auto location = filesystem::get_binary_dir_location("", path)) {
375  t_string::add_textdomain(name, location.value());
376  } else {
377  // If location is empty, this causes a crash on Windows, so we disallow adding empty domains
378  WRN_G << "no location found for '" << path << "', skipping textdomain";
379  }
380  }
381 }
382 
384 {
385  languages_.clear();
386  for (const config &l : cfg.child_range("language")) {
387  languages_.push_back(l);
388  }
389  return load_strings(true);
390 }
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:172
child_itors child_range(config_key_type key)
Definition: config.cpp:272
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 prefs & get()
static void add_textdomain(const std::string &name, const std::string &path)
Definition: tstring.cpp:657
Declarations for File-IO.
std::size_t i
Definition: function.cpp:1028
#define N_(String)
Definition: gettext.hpp:101
const language_def & get_locale()
Definition: language.cpp:327
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:365
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:325
bool load_language_list()
Definition: language.cpp:103
bool init_strings(const game_config_view &cfg)
Initializes certain English strings.
Definition: language.cpp:383
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:299
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir)
Returns a translated path to the actual file or directory, if it exists.
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:53
utils::optional< 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, if it exists.
std::string get_intl_dir()
std::string path
Definition: filesystem.cpp:90
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:622
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