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