1 /*
2 * Copyright (C) 2006-2020 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20 #include "base/i18n.h"
21
22 #ifdef __FreeBSD__
23 #include <clocale>
24 #endif
25
26 #include <map>
27
28 #include <boost/format.hpp>
29
30 #include "base/log.h"
31 #include "config.h"
32
33 #ifdef __APPLE__
34 #if LIBINTL_VERSION >= 0x001201
35 // for older libintl versions, setlocale is just fine
36 #define SETLOCALE libintl_setlocale
37 #endif // LIBINTL_VERSION
38 #else // __APPLE__
39 #if defined _WIN32
40 #define SETLOCALE setlocale
41 #else
42 #define SETLOCALE std::setlocale
43 #endif
44 #endif
45
46 extern int _nl_msg_cat_cntr;
47
48 namespace i18n {
49
50 /// A stack of textdomains. On entering a new textdomain, the old one gets
51 /// pushed on the stack. On leaving the domain again it is popped back.
52 /// \see grab_texdomain()
53 namespace {
54
55 std::vector<std::pair<std::string, std::string>> textdomains;
56
57 std::string env_locale;
58 std::string locale;
59 std::string localedir;
60
61 } // namespace
62
63 /**
64 * Translate a string with gettext
65 */
66 // TODO(unknown): Implement a workaround if gettext was not found
translate(char const * const str)67 char const* translate(char const* const str) {
68 return gettext(str);
69 }
translate(const std::string & str)70 char const* translate(const std::string& str) {
71 return gettext(str.c_str());
72 }
73
74 /**
75 * Set the localedir. This should usually only be done once
76 */
set_localedir(const std::string & dname)77 void set_localedir(const std::string& dname) {
78 localedir = dname;
79 }
80
get_localedir()81 const std::string& get_localedir() {
82 return localedir;
83 }
84
85 /**
86 * Grab a given TextDomain. If a new one is grabbed, it is pushed on the stack.
87 * On release, it is dropped and the previous one is re-grabbed instead.
88 *
89 * So when a tribe loads, it grabs its textdomain, loads all data and releases
90 * it -> we're back in widelands domain. Negative: We can't translate error
91 * messages. Who cares?
92 */
grab_textdomain(const std::string & domain)93 void grab_textdomain(const std::string& domain) {
94 char const* const dom = domain.c_str();
95 char const* const ldir = localedir.c_str();
96
97 bindtextdomain(dom, ldir);
98 bind_textdomain_codeset(dom, "UTF-8");
99 // log("textdomain %s @ %s\n", dom, ldir);
100 textdomain(dom);
101 textdomains.push_back(std::make_pair(dom, ldir));
102 }
103
104 /**
105 * See grab_textdomain()
106 */
release_textdomain()107 void release_textdomain() {
108 if (textdomains.empty()) {
109 log("ERROR: trying to pop textdomain from empty stack");
110 return;
111 }
112 textdomains.pop_back();
113
114 // Don't try to get the previous TD when the very first one ('widelands')
115 // just got dropped
116 if (!textdomains.empty()) {
117 char const* const domain = textdomains.back().first.c_str();
118
119 bind_textdomain_codeset(domain, "UTF-8");
120 bindtextdomain(domain, textdomains.back().second.c_str());
121 textdomain(domain);
122 }
123 }
124
125 /**
126 * Initialize locale to English.
127 * Save system language (for later selection).
128 * Code inspired by wesnoth.org
129 */
init_locale()130 void init_locale() {
131 env_locale = std::string();
132 #ifdef _WIN32
133 locale = "English";
134 SETLOCALE(LC_ALL, "English");
135 #else
136 // first, save environment variable
137 char* lang;
138 lang = getenv("LANG");
139 if (lang != nullptr) {
140 env_locale = lang;
141 }
142 if (env_locale.empty()) {
143 lang = getenv("LANGUAGE");
144 if (lang != nullptr) {
145 env_locale = lang;
146 } else { // Finall fallback in case we cannot find out anything (#1784495)
147 env_locale = "en";
148 }
149 }
150 locale = "C";
151 SETLOCALE(LC_ALL, "C");
152 SETLOCALE(LC_MESSAGES, "");
153 #endif
154 }
155
156 /**
157 * Set the locale to the given string.
158 * Code inspired by wesnoth.org
159 */
set_locale(const std::string & name)160 void set_locale(const std::string& name) {
161 const std::map<std::string, std::string> kAlternatives = {
162 {"ar", "ar,ar_AR,ar_AE,ar_BH,ar_DZ,ar_EG,ar_IN,ar_IQ,ar_JO,ar_KW,ar_LB,ar_LY,ar_MA,ar_OM,ar_"
163 "QA,ar_SA,ar_SD,ar_SY,ar_TN,ar_YE"},
164 {"ast", "ast,ast_ES"},
165 {"ca", "ca,ca_ES,ca_ES@valencia,ca_FR,ca_IT"},
166 {"cs", "cs,cs_CZ"},
167 {"da", "da,da_DK"},
168 {"de", "de,de_DE,de_AT,de_CH,de_LI,de_LU,de_BE"},
169 {"el", "el,el_GR,el_CY"},
170 {"en", "en,en_US,en_GB,en_AU,en_CA,en_AG,en_BW,en_DK,en_HK,en_IE,en_IN,en_NG,en_NZ,en_PH,en_"
171 "SG,en_ZA,en_ZW"},
172 {"en_AU", "en_AU,en,en_US,en_GB"},
173 {"en_CA", "en_CA,en,en_US,en_GB"},
174 {"en_GB", "en_GB,en,en_US"},
175 {"eo", "eo,eo_XX"},
176 {"es", "es,es_ES,es_MX,es_US"},
177 {"et", "et,et_EE"},
178 {"eu", "eu,eu_ES,eu_FR"},
179 {"fa", "fa,fa_IR"},
180 {"fi", "fi,fi_FI"},
181 {"fr", "fr,fr_FR,fr_CH,fr_BE,fr_CA,fr_LU"},
182 {"gd", "gd,gd_GB,gd_CA"},
183 {"gl", "ga,gl_ES"},
184 {"he", "he,he_IL"},
185 {"hr", "hr,hr_HR,hr_RS,hr_BA,hr_ME,hr_HU"},
186 {"hu", "hu,hu_HU"},
187 {"ia", "ia"},
188 {"id", "id,id_ID"},
189 {"it", "it,it_IT,it_CH"},
190 {"ja", "ja,ja_JP"},
191 {"jv", "jv,jv_ID,jv_MY,jv_SR,jv_NC"},
192 {"ka", "ka,ka_GE"},
193 {"ko", "ko,ko_KR"},
194 {"la", "la,la_AU,la_VA"},
195 {"mr", "mr,mr_IN"},
196 {"ms", "ms,ms_MY"},
197 {"my", "my,my_MM"},
198 {"nb", "nb,nb_NO"},
199 {"nl", "nl,nl_NL,nl_BE,nl_AW"},
200 {"nn", "nn,nn_NO"},
201 {"oc", "oc,oc_FR"},
202 {"pl", "pl,pl_PL"},
203 {"pt", "pt,pt_PT,pt_BR"},
204 {"pt_BR", "pt_BR,pt,pt_PT"},
205 {"ru", "ru,ru_RU,ru_UA"},
206 {"si", "si,si_LK"},
207 {"sk", "sk,sk_SK"},
208 {"sl", "sl,sl_SI"},
209 {"sr", "sr,sr_RS,sr_ME"},
210 {"sv", "sv,sv_FI,sv_SE"},
211 {"tr", "tr,tr_TR,tr_CY"},
212 {"uk", "uk,uk_UA"},
213 {"vi", "vi,vi_VN"},
214 {"zh", "zh,zh_CN,zh_TW,zh_HK,zh_MO"},
215 {"zh_CN", "zh_CN,zh,zh_TW,zh_HK,zh_MO"},
216 {"zh_TW", "zh_TW,zh_HK,zh_MO,zh,zh_CN"},
217 };
218
219 std::string lang(name);
220
221 log("selected language: %s\n", lang.empty() ? "(system language)" : lang.c_str());
222
223 #ifndef _WIN32
224 #ifndef __AMIGAOS4__
225 #ifndef __APPLE__
226 unsetenv("LANGUAGE"); // avoid problems with this variable
227 #endif
228 #endif
229 #endif
230
231 std::string alt_str;
232 if (lang.empty()) {
233 // reload system language, if selected
234 lang = env_locale;
235 alt_str = env_locale;
236 } else {
237 alt_str = lang;
238 // otherwise, try alternatives.
239 if (kAlternatives.count(lang)) {
240 alt_str = kAlternatives.at(lang);
241 }
242 }
243 alt_str += ",";
244
245 // Somehow setlocale doesn't behave same on
246 // some systems.
247 #ifdef __BEOS__
248 setenv("LANG", lang.c_str(), 1);
249 setenv("LC_ALL", lang.c_str(), 1);
250 locale = lang;
251 #endif
252 #ifdef __APPLE__
253 setenv("LANGUAGE", lang.c_str(), 1);
254 setenv("LANG", lang.c_str(), 1);
255 setenv("LC_ALL", lang.c_str(), 1);
256 locale = lang;
257 #endif
258 #ifdef _WIN32
259 _putenv_s("LANG", lang.c_str());
260 locale = lang;
261 #endif
262
263 #ifdef __linux__
264 char* res = nullptr;
265 char const* encoding[] = {"", ".utf-8", "@euro", ".UTF-8"};
266 std::size_t found = alt_str.find(',', 0);
267 bool leave_while = false;
268 // try every possible combination of alternative and encoding
269 while (found != std::string::npos) {
270 std::string base_locale = alt_str.substr(0, int(found));
271 alt_str = alt_str.erase(0, int(found) + 1);
272
273 for (int j = 0; j < 4; ++j) {
274 std::string try_locale = base_locale + encoding[j];
275 res = SETLOCALE(LC_MESSAGES, try_locale.c_str());
276 if (res) {
277 locale = try_locale;
278 log("using locale %s\n", try_locale.c_str());
279 leave_while = true;
280 break;
281 } else {
282 // log("locale is not working: %s\n", try_locale.c_str());
283 }
284 }
285 if (leave_while) {
286 break;
287 }
288
289 found = alt_str.find(',', 0);
290 }
291 if (leave_while) {
292 setenv("LC_ALL", locale.c_str(), 1);
293 setenv("LANG", locale.c_str(), 1);
294 setenv("LANGUAGE", locale.c_str(), 1);
295 } else {
296 log("No corresponding locale found\n");
297 log(" - Set LANGUAGE, LANG and LC_ALL to '%s'\n", lang.c_str());
298
299 setenv("LANGUAGE", lang.c_str(), 1);
300 setenv("LANG", lang.c_str(), 1);
301 setenv("LC_ALL", lang.c_str(), 1);
302
303 try {
304 SETLOCALE(LC_MESSAGES, "en_US.utf8"); // set locale according to the env. variables
305 // --> see $ man 3 setlocale
306 log(" - Set system locale to 'en_US.utf8' to make '%s' accessible to libintl\n",
307 lang.c_str());
308 } catch (std::exception&) {
309 SETLOCALE(LC_MESSAGES, ""); // set locale according to the env. variables
310 // --> see $ man 3 setlocale
311 }
312 // assume that it worked
313 // maybe, do another check with the return value (?)
314 locale = lang;
315 }
316
317 /* Finally make changes known. */
318 ++_nl_msg_cat_cntr;
319 #endif
320
321 SETLOCALE(LC_ALL, ""); // call to libintl
322
323 if (!textdomains.empty()) {
324 char const* const domain = textdomains.back().first.c_str();
325
326 bind_textdomain_codeset(domain, "UTF-8");
327 bindtextdomain(domain, textdomains.back().second.c_str());
328 textdomain(domain);
329 }
330 }
331
get_locale()332 const std::string& get_locale() {
333 return locale;
334 }
335
localize_list(const std::vector<std::string> & items,ConcatenateWith listtype)336 std::string localize_list(const std::vector<std::string>& items, ConcatenateWith listtype) {
337 i18n::Textdomain td("widelands");
338 std::string result;
339 for (std::vector<std::string>::const_iterator it = items.begin(); it != items.end(); ++it) {
340 if (it == items.begin()) {
341 result = *it;
342 } else if (it == --items.end()) {
343 if (listtype == ConcatenateWith::AMPERSAND) {
344 /** TRANSLATORS: Concatenate the last 2 items on a list. */
345 /** TRANSLATORS: RTL languages might want to change the word order here. */
346 result = (boost::format(_("%1$s & %2$s")) % result % (*it)).str();
347 } else if (listtype == ConcatenateWith::OR) {
348 /** TRANSLATORS: Join the last 2 items on a list with "or". */
349 /** TRANSLATORS: RTL languages might want to change the word order here. */
350 result = (boost::format(_("%1$s or %2$s")) % result % (*it)).str();
351 } else if (listtype == ConcatenateWith::COMMA) {
352 /** TRANSLATORS: Join the last 2 items on a list with a comma. */
353 /** TRANSLATORS: RTL languages might want to change the word order here. */
354 result = (boost::format(_("%1$s, %2$s")) % result % (*it)).str();
355 } else {
356 /** TRANSLATORS: Concatenate the last 2 items on a list. */
357 /** TRANSLATORS: RTL languages might want to change the word order here. */
358 result = (boost::format(_("%1$s and %2$s")) % result % (*it)).str();
359 }
360 } else {
361 /** TRANSLATORS: Concatenate 2 items at in the middle of a list. */
362 /** TRANSLATORS: RTL languages might want to change the word order here. */
363 result = (boost::format(_("%1$s, %2$s")) % result % (*it)).str();
364 }
365 }
366 return result;
367 }
368
join_sentences(const std::string & sentence1,const std::string & sentence2)369 std::string join_sentences(const std::string& sentence1, const std::string& sentence2) {
370 i18n::Textdomain td("widelands");
371 /** TRANSLATORS: Put 2 sentences one after the other. Languages using Chinese script probably
372 * want to lose the blank space here. */
373 return (boost::format(pgettext("sentence_separator", "%1% %2%")) % sentence1 % sentence2).str();
374 }
375
376 } // namespace i18n
377