The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
24
25#include <clocale>
26
27#ifdef _WIN32
28#include <windows.h>
29#if !defined(_MSC_VER) && !defined(__MINGW32__)
30extern "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
48namespace {
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
59bool load_strings(bool complain);
60
62{
63 return ((language == a.language) /* && (localename == a.localename) */ );
64}
65
66std::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
83const 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
99const t_string& symbol_table::operator[](const char* key) const
100{
101 return (*this)[std::string(key)];
102}
103
104utils::string_map::const_iterator symbol_table::find(const std::string& key) const
105{
106 return strings_.find(key);
107}
108
109utils::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
159std::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
178std::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
190 min_translation_percent = percent;
191}
192
193
194static 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
259void 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
302bool 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
317const 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
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.
#define N_(String)
Definition gettext.hpp:108
static void wesnoth_setlocale(int category, const std::string &slocale, std::vector< std::string > const *alternates)
Definition language.cpp:194
std::vector< language_def > get_languages(bool all)
Return a list of available translations.
Definition language.cpp:159
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
#define WRN_G
Definition language.cpp:45
const language_def & get_language()
Definition language.cpp:317
int get_min_translation_percent()
Definition language.cpp:184
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
const language_def & get_locale()
Definition language.cpp:319
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
bool & time_locale_correct()
Definition language.cpp:77
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=utils::nullopt)
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()
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::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.
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
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
#define i