The Battle for Wesnoth  1.19.24+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 #ifdef __ANDROID__
39 #include <SDL2/SDL_system.h> // For SDL Android functions
40 #include <jni.h>
41 #endif
42 
43 #define DBG_G LOG_STREAM(debug, lg::general())
44 #define LOG_G LOG_STREAM(info, lg::general())
45 #define WRN_G LOG_STREAM(warn, lg::general())
46 #define ERR_G LOG_STREAM(err, lg::general())
47 
48 namespace {
49  language_def current_language;
50  std::vector<config> languages;
51  std::vector<language_def> known_languages;
52  utils::string_map strings_;
53  int min_translation_percent = 80;
54  // a storage for looking up active translation name
55  // corresponding to a locale id for locale ids that Wesnoth supports.
56  std::map<std::string, std::string> translation_names;
57 }
58 
59 bool load_strings(bool complain);
60 
62 {
63  return ((language == a.language) /* && (localename == a.localename) */ );
64 }
65 
66 std::string language_def::short_localename() const {
67  std::string::size_type index = localename.find(
68 #ifdef _WIN32
69  '-'
70 #else
71  '_'
72 #endif
73  );
74  return index == std::string::npos ? localename : localename.substr(0, index);
75 }
76 
78 {
79  static bool result = true;
80  return result;
81 }
82 
83 const t_string& symbol_table::operator[](const std::string& key) const
84 {
85  const utils::string_map::const_iterator i = strings_.find(key);
86  if(i != strings_.end()) {
87  return i->second;
88  } else {
89  static t_string empty_string;
90  // Let's do it the painful way (untlb means untranslatABLE).
91  // It will cause problem if somebody stores more than one reference at once
92  // but I don't really care since this path is an error path and it should
93  // not have been taken in the first place. -- silene
94  empty_string = "UNTLB " + key;
95  return empty_string;
96  }
97 }
98 
99 const t_string& symbol_table::operator[](const char* key) const
100 {
101  return (*this)[std::string(key)];
102 }
103 
104 utils::string_map::const_iterator symbol_table::find(const std::string& key) const
105 {
106  return strings_.find(key);
107 }
108 
109 utils::string_map::const_iterator symbol_table::end() const
110 {
111  return strings_.end();
112 }
113 
115  : language(t_string(N_("System default language"), "wesnoth"))
116  , sort_name("A")
117 {
118 }
119 
121 #ifndef _WIN32
122  : localename(cfg["locale"])
123  , alternates(utils::split(cfg["alternates"]))
124 #else
125  : localename(cfg["windows_locale"].str("C"))
126  , alternates(utils::split(cfg["windows_alternates"]))
127 #endif
128  , language(cfg["name"].t_str())
129  , sort_name(cfg["sort_name"].str(language))
130  , rtl(cfg["dir"] == "rtl")
131  , percent(cfg["percent"].to_int())
132 {
133  // entry for main language in the locale id and name map
134  translation_names.emplace(localename, language);
135 
136  for(const auto& alternate : alternates) {
137  translation_names.emplace(alternate, language);
138  }
139 }
140 
142 {
143  try {
144  config cfg = io::read(*preprocess_file(filesystem::get_wml_location("hardwired/language.cfg").value()));
145 
146  known_languages.clear();
147  known_languages.emplace_back(); // System default language
148 
149  for(const config& lang : cfg.child_range("locale")) {
150  known_languages.emplace_back(lang);
151  }
152 
153  return true;
154 
155  } catch(const utils::bad_optional_access&) {
156  return false;
157  } catch(const config::error&) {
158  return false;
159  }
160 }
161 
162 std::vector<language_def> get_languages(bool all)
163 {
164  // We sort every time, the local might have changed which can modify the
165  // sort order.
166  std::sort(known_languages.begin(), known_languages.end());
167 
168  if(all || min_translation_percent == 0) {
169  LOG_G << "Found " << known_languages.size() << " known languages";
170  return known_languages;
171  }
172 
173  std::vector<language_def> result;
174  std::copy_if(known_languages.begin(), known_languages.end(), std::back_inserter(result),
175  [](const language_def& lang) { return lang.percent >= min_translation_percent; });
176 
177  LOG_G << "Found " << result.size() << " sufficiently translated languages";
178  return result;
179 }
180 
181 std::string get_translation_name(const std::string& locale_id)
182 {
183  auto itor = translation_names.find(locale_id);
184  return itor != translation_names.end() ? itor->second : "";
185 }
186 
188 {
189  return min_translation_percent;
190 }
191 
192 void set_min_translation_percent(int percent) {
193  min_translation_percent = percent;
194 }
195 
196 
197 static void wesnoth_setlocale(int category, const std::string& slocale,
198  std::vector<std::string> const *alternates)
199 {
200  std::string locale = slocale;
201 
202  //category is never LC_MESSAGES since that case was moved to gettext.cpp to remove the dependency to libintl.h in this file
203  //that's why code like if (category == LC_MESSAGES) is outcommented here.
204 #ifdef __APPLE__
205  //if (category == LC_MESSAGES && setenv("LANG", locale.c_str(), 1) == -1) {
206  // ERR_G << "setenv LANG failed: " << strerror(errno);
207  //}
208 #endif
209 
210  char *res = nullptr;
211  std::vector<std::string>::const_iterator i;
212  if (alternates) i = alternates->begin();
213 
214  for (;;)
215  {
216  std::string lang = locale, extra;
217  std::string::size_type pos = locale.find('@');
218  if (pos != std::string::npos) {
219  lang.erase(pos);
220  extra = locale.substr(pos);
221  }
222 
223  /*
224  * The "" is the last item to work-around a problem in glibc picking
225  * the non utf8 locale instead an utf8 version if available.
226  */
227  char const *encoding[] { ".utf-8", ".UTF-8", "" };
228  for (int j = 0; j != 3; ++j)
229  {
230  locale = lang + encoding[j] + extra;
231  res = std::setlocale(category, locale.c_str());
232  if (res) {
233  LOG_G << "Set locale to '" << locale << "' result: '" << res << "'.";
234  goto done;
235  }
236  }
237 
238  if (!alternates || i == alternates->end()) break;
239  locale = *i;
240  ++i;
241  }
242 
243  WRN_G << "setlocale() failed for '" << slocale << "'.";
244 
245  if (category == LC_TIME) {
246  time_locale_correct() = false;
247  }
248 
249 #ifndef _WIN32
250  //if(category == LC_MESSAGES) {
251  // WRN_G << "Setting LANGUAGE to '" << slocale << "'.";
252  // setenv("LANGUAGE", slocale.c_str(), 1);
253  // std::setlocale(LC_MESSAGES, "");
254  //}
255 #endif
256 
257  done:
258  DBG_G << "Numeric locale: " << std::setlocale(LC_NUMERIC, nullptr);
259  DBG_G << "Full locale: " << std::setlocale(LC_ALL, nullptr);
260 }
261 
262 void set_language(const language_def& locale)
263 {
264  strings_.clear();
265 
266  current_language = locale;
267  time_locale_correct() = true;
268 
269  std::string localename = locale.localename;
270 
271 #ifdef __ANDROID__
272  if (locale.localename.empty()) {
273  JNIEnv* env = reinterpret_cast<JNIEnv*>(SDL_AndroidGetJNIEnv());
274  jobject wesnoth_instance = reinterpret_cast<jobject>(SDL_AndroidGetActivity());
275  jclass wesnoth_activity(env->GetObjectClass(wesnoth_instance));
276  jmethodID locale = env->GetMethodID(wesnoth_activity, "getLocaleCode", "()Ljava/lang/String;");
277  jstring lcode = reinterpret_cast<jstring>(env->CallObjectMethod(wesnoth_instance, locale));
278  localename = env->GetStringUTFChars(lcode, nullptr);
279 
280  if(env->ExceptionCheck() == JNI_TRUE) {
281  env->ExceptionDescribe();
282  env->ExceptionClear();
283  }
284 
285  env->DeleteLocalRef(wesnoth_instance);
286  env->DeleteLocalRef(wesnoth_activity);
287  }
288 #endif
289 
290  wesnoth_setlocale(LC_COLLATE, localename, &locale.alternates);
291  wesnoth_setlocale(LC_TIME, localename, &locale.alternates);
292  translation::set_language(localename, &locale.alternates);
293  load_strings(false);
294 }
295 
296 bool load_strings(bool complain)
297 {
298  if(complain && languages.empty()) {
299  PLAIN_LOG << "No [language] block found";
300  return false;
301  }
302  for(const config& lang : languages) {
303  for(const auto& [key, value] : lang.attribute_range()) {
304  strings_[key] = value.t_str();
305  }
306  }
307 
308  return true;
309 }
310 
311 const language_def& get_language() { return current_language; }
312 
314 {
315  assert(!known_languages.empty());
316 
317  const std::string& prefs_locale = prefs::get().locale();
318  if(prefs_locale.empty() == false) {
319  translation::set_language(prefs_locale, nullptr);
320  for(const language_def& def : known_languages) {
321  if(prefs_locale == def.localename) {
322  return def;
323  }
324  }
325  LOG_G << "'" << prefs_locale << "' locale not found in known array; defaulting to system locale";
326  return known_languages[0];
327  }
328 
329  LOG_G << "locale could not be determined; defaulting to system locale";
330  return known_languages[0];
331 }
332 
334 {
335  for (const config &t : cfg.child_range("textdomain"))
336  {
337  const std::string &name = t["name"];
338  const std::string &path = t["path"];
339 
340  if(path.empty()) {
342  } else if(auto location = filesystem::get_binary_dir_location("", path)) {
343  t_string::add_textdomain(name, location.value());
344  } else {
345  // If location is empty, this causes a crash on Windows, so we disallow adding empty domains
346  WRN_G << "no location found for '" << path << "', skipping textdomain";
347  }
348  }
349 }
350 
352 {
353  languages.clear();
354  for (const config &l : cfg.child_range("language")) {
355  languages.push_back(l);
356  }
357  return load_strings(true);
358 }
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:157
child_itors child_range(std::string_view key)
Definition: config.cpp:268
A class grating read only view to a vector of config objects, viewed as one config with all children ...
static prefs & get()
static void add_textdomain(const std::string &name, const std::string &path)
Definition: tstring.cpp:657
const config * cfg
Declarations for File-IO.
std::size_t i
Definition: function.cpp:1031
#define N_(String)
Definition: gettext.hpp:105
const language_def & get_locale()
Definition: language.cpp:313
std::vector< language_def > get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:162
static void wesnoth_setlocale(int category, const std::string &slocale, std::vector< std::string > const *alternates)
Definition: language.cpp:197
void init_textdomains(const game_config_view &cfg)
Initializes the list of textdomains from a configuration object.
Definition: language.cpp:333
bool load_strings(bool complain)
Definition: language.cpp:296
bool & time_locale_correct()
Definition: language.cpp:77
#define WRN_G
Definition: language.cpp:45
int get_min_translation_percent()
Definition: language.cpp:187
const language_def & get_language()
Definition: language.cpp:311
bool load_language_list()
Definition: language.cpp:141
std::string get_translation_name(const std::string &locale_id)
Definition: language.cpp:181
bool init_strings(const game_config_view &cfg)
Initializes certain English strings.
Definition: language.cpp:351
void set_language(const language_def &locale)
Definition: language.cpp:262
#define LOG_G
Definition: language.cpp:44
#define DBG_G
Definition: language.cpp:43
void set_min_translation_percent(int percent)
Definition: language.cpp:192
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:106
config read(std::istream &in, abstract_validator *validator)
Definition: parser.cpp:610
void set_language(const std::string &language, const std::vector< std::string > *)
Definition: gettext.cpp:494
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
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:61
t_string language
Definition: language.hpp:34
std::string short_localename() const
Definition: language.cpp:66
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:114
utils::string_map::const_iterator end() const
Definition: language.cpp:109
const t_string & operator[](const std::string &key) const
Look up the string mappings given in [language] tags.
Definition: language.cpp:83
utils::string_map::const_iterator find(const std::string &key) const
Look up the string mappings given in [language] tags.
Definition: language.cpp:104