The Battle for Wesnoth  1.19.27+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 <SDL3/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  : localename(cfg["locale"])
122  , alternates(utils::split(cfg["alternates"]))
123  , windows_locale(cfg["windows_locale"].str("C"))
124  , windows_alternates(utils::split(cfg["windows_alternates"]))
125  , language(cfg["name"].t_str())
126  , sort_name(cfg["sort_name"].str(language))
127  , rtl(cfg["dir"] == "rtl")
128  , percent(cfg["percent"].to_int())
129 {
130  // entry for main language in the locale id and name map
131  translation_names.emplace(localename, language);
132 
133  for(const auto& alternate : alternates) {
134  translation_names.emplace(alternate, language);
135  }
136 }
137 
139 {
140  try {
141  config cfg = io::read(*preprocess_file(filesystem::get_wml_location("hardwired/language.cfg").value()));
142 
143  known_languages.clear();
144  known_languages.emplace_back(); // System default language
145 
146  for(const config& lang : cfg.child_range("locale")) {
147  known_languages.emplace_back(lang);
148  }
149 
150  return true;
151 
152  } catch(const utils::bad_optional_access&) {
153  return false;
154  } catch(const config::error&) {
155  return false;
156  }
157 }
158 
159 std::vector<language_def> get_languages(bool all)
160 {
161  // We sort every time, the local might have changed which can modify the
162  // sort order.
163  std::sort(known_languages.begin(), known_languages.end());
164 
165  if(all || min_translation_percent == 0) {
166  LOG_G << "Found " << known_languages.size() << " known languages";
167  return known_languages;
168  }
169 
170  std::vector<language_def> result;
171  std::copy_if(known_languages.begin(), known_languages.end(), std::back_inserter(result),
172  [](const language_def& lang) { return lang.percent >= min_translation_percent; });
173 
174  LOG_G << "Found " << result.size() << " sufficiently translated languages";
175  return result;
176 }
177 
178 std::string get_translation_name(const std::string& locale_id)
179 {
180  auto itor = translation_names.find(locale_id);
181  return itor != translation_names.end() ? itor->second : "";
182 }
183 
185 {
186  return min_translation_percent;
187 }
188 
189 void set_min_translation_percent(int percent) {
190  min_translation_percent = percent;
191 }
192 
193 
194 static void wesnoth_setlocale(int category, const std::string& slocale,
195  std::vector<std::string> const *alternates)
196 {
197  std::string locale = slocale;
198 
199  //category is never LC_MESSAGES since that case was moved to gettext.cpp to remove the dependency to libintl.h in this file
200  //that's why code like if (category == LC_MESSAGES) is outcommented here.
201 #ifdef __APPLE__
202  //if (category == LC_MESSAGES && setenv("LANG", locale.c_str(), 1) == -1) {
203  // ERR_G << "setenv LANG failed: " << strerror(errno);
204  //}
205 #endif
206 
207  char *res = nullptr;
208  std::vector<std::string>::const_iterator i;
209  if (alternates) i = alternates->begin();
210 
211  for (;;)
212  {
213  std::string lang = locale, extra;
214  std::string::size_type pos = locale.find('@');
215  if (pos != std::string::npos) {
216  lang.erase(pos);
217  extra = locale.substr(pos);
218  }
219 
220  /*
221  * The "" is the last item to work-around a problem in glibc picking
222  * the non utf8 locale instead an utf8 version if available.
223  */
224  char const *encoding[] { ".utf-8", ".UTF-8", "" };
225  for (int j = 0; j != 3; ++j)
226  {
227  locale = lang + encoding[j] + extra;
228  res = std::setlocale(category, locale.c_str());
229  if (res) {
230  LOG_G << "Set locale to '" << locale << "' result: '" << res << "'.";
231  goto done;
232  }
233  }
234 
235  if (!alternates || i == alternates->end()) break;
236  locale = *i;
237  ++i;
238  }
239 
240  WRN_G << "setlocale() failed for '" << slocale << "'.";
241 
242  if (category == LC_TIME) {
243  time_locale_correct() = false;
244  }
245 
246 #ifndef _WIN32
247  //if(category == LC_MESSAGES) {
248  // WRN_G << "Setting LANGUAGE to '" << slocale << "'.";
249  // setenv("LANGUAGE", slocale.c_str(), 1);
250  // std::setlocale(LC_MESSAGES, "");
251  //}
252 #endif
253 
254  done:
255  DBG_G << "Numeric locale: " << std::setlocale(LC_NUMERIC, nullptr);
256  DBG_G << "Full locale: " << std::setlocale(LC_ALL, nullptr);
257 }
258 
259 void set_language(const language_def& locale)
260 {
261  strings_.clear();
262 
263  current_language = locale;
264  time_locale_correct() = true;
265 
266  std::string localename = locale.localename;
267 
268 #ifdef __ANDROID__
269  if (locale.localename.empty()) {
270  JNIEnv* env = reinterpret_cast<JNIEnv*>(SDL_GetAndroidJNIEnv());
271  jobject wesnoth_instance = reinterpret_cast<jobject>(SDL_GetAndroidActivity());
272  jclass wesnoth_activity(env->GetObjectClass(wesnoth_instance));
273  jmethodID locale = env->GetMethodID(wesnoth_activity, "getLocaleCode", "()Ljava/lang/String;");
274  jstring lcode = reinterpret_cast<jstring>(env->CallObjectMethod(wesnoth_instance, locale));
275  localename = env->GetStringUTFChars(lcode, nullptr);
276 
277  if(env->ExceptionCheck() == JNI_TRUE) {
278  env->ExceptionDescribe();
279  env->ExceptionClear();
280  }
281 
282  env->DeleteLocalRef(wesnoth_instance);
283  env->DeleteLocalRef(wesnoth_activity);
284  }
285 #endif
286 
287 #ifdef _WIN32
288  // Windows has no knowledge of most POSIX locale IDs, so the OS-level setlocale() call
289  // (date/time formatting and collation only) uses the windows_locale override instead.
290  // Catalogue look-up below still uses the portable POSIX locale name, since gettext-style
291  // catalogue matching doesn't go through the OS locale database.
292  wesnoth_setlocale(LC_COLLATE, locale.windows_locale, &locale.windows_alternates);
293  wesnoth_setlocale(LC_TIME, locale.windows_locale, &locale.windows_alternates);
294 #else
295  wesnoth_setlocale(LC_COLLATE, localename, &locale.alternates);
296  wesnoth_setlocale(LC_TIME, localename, &locale.alternates);
297 #endif
298  translation::set_language(localename, &locale.alternates);
299  load_strings(false);
300 }
301 
302 bool load_strings(bool complain)
303 {
304  if(complain && languages.empty()) {
305  PLAIN_LOG << "No [language] block found";
306  return false;
307  }
308  for(const config& lang : languages) {
309  for(const auto& [key, value] : lang.attribute_range()) {
310  strings_[key] = value.t_str();
311  }
312  }
313 
314  return true;
315 }
316 
317 const language_def& get_language() { return current_language; }
318 
320 {
321  assert(!known_languages.empty());
322 
323  const std::string& prefs_locale = prefs::get().locale();
324  if(prefs_locale.empty() == false) {
325  translation::set_language(prefs_locale, nullptr);
326  for(const language_def& def : known_languages) {
327  if(prefs_locale == def.localename) {
328  return def;
329  }
330  }
331  LOG_G << "'" << prefs_locale << "' locale not found in known array; defaulting to system locale";
332  return known_languages[0];
333  }
334 
335  LOG_G << "locale could not be determined; defaulting to system locale";
336  return known_languages[0];
337 }
338 
340 {
341  for (const config &t : cfg.child_range("textdomain"))
342  {
343  const std::string &name = t["name"];
344  const std::string &path = t["path"];
345 
346  if(path.empty()) {
348  } else if(auto location = filesystem::get_binary_dir_location("", path)) {
349  t_string::add_textdomain(name, location.value());
350  } else {
351  // If location is empty, this causes a crash on Windows, so we disallow adding empty domains
352  WRN_G << "no location found for '" << path << "', skipping textdomain";
353  }
354  }
355 }
356 
358 {
359  languages.clear();
360  for (const config &l : cfg.child_range("language")) {
361  languages.push_back(l);
362  }
363  return load_strings(true);
364 }
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:108
const language_def & get_locale()
Definition: language.cpp:319
std::vector< language_def > get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:159
static void wesnoth_setlocale(int category, const std::string &slocale, std::vector< std::string > const *alternates)
Definition: language.cpp:194
void init_textdomains(const game_config_view &cfg)
Initializes the list of textdomains from a configuration object.
Definition: language.cpp:339
bool load_strings(bool complain)
Definition: language.cpp:302
bool & time_locale_correct()
Definition: language.cpp:77
#define WRN_G
Definition: language.cpp:45
int get_min_translation_percent()
Definition: language.cpp:184
const language_def & get_language()
Definition: language.cpp:317
bool load_language_list()
Definition: language.cpp:138
std::string get_translation_name(const std::string &locale_id)
Definition: language.cpp:178
bool init_strings(const game_config_view &cfg)
Initializes certain English strings.
Definition: language.cpp:357
void set_language(const language_def &locale)
Definition: language.cpp:259
#define LOG_G
Definition: language.cpp:44
#define DBG_G
Definition: language.cpp:43
void set_min_translation_percent(int percent)
Definition: language.cpp:189
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.
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition: sound.cpp:138
std::vector< std::string > windows_alternates
Definition: language.hpp:43
bool operator==(const language_def &) const
Definition: language.cpp:61
std::string windows_locale
Windows has no knowledge of most POSIX locale IDs, so on that platform the OS-level setlocale() call ...
Definition: language.hpp:42
t_string language
Definition: language.hpp:45
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