FlightGear next
locale.cxx
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2012 Thorsten Brehm - brehmt (at) gmail com
2// SPDX-License-Identifier: GPL-2.0-or-later
3
8
9#include <config.h>
10
11#ifdef HAVE_WINDOWS_H
12#include <windows.h>
13#endif
14
15#include <cstdio>
16#include <cstring> // std::strlen()
17#include <cstddef> // std::size_t
18#include <cassert>
19#include <memory>
20#include <string>
21#include <vector>
22
23#include <simgear/debug/logstream.hxx>
24#include <simgear/misc/strutils.hxx>
25#include <simgear/misc/sg_dir.hxx>
26#include <simgear/misc/sg_path.hxx>
27#include <simgear/props/props.hxx>
28#include <simgear/props/props_io.hxx>
29#include <simgear/structure/exception.hxx>
30
31#include "fg_props.hxx"
32#include "locale.hxx"
33
39
40using std::string;
41using std::vector;
42namespace strutils = simgear::strutils;
43
47
48FGLocale::FGLocale(SGPropertyNode* root) :
49 _intl(root->getNode("/sim/intl", 0, true)),
50 _fallbackLocale(_intl->getChild("locale", 0, true))
51{
52}
53
57
58// Static method
59string FGLocale::removeEncodingPart(const string& locale)
60{
61 string res;
62 std::size_t pos = locale.find('.');
63
64 if (pos != string::npos)
65 {
66 assert(pos > 0);
67 res = locale.substr(0, pos);
68 } else {
69 res = locale;
70 }
71
72 return res;
73}
74
75string removeLocalePart(const string& locale)
76{
77 std::size_t pos = locale.find('_');
78 if (pos == string::npos) {
79 pos = locale.find('-');
80 }
81
82 if (pos == string::npos)
83 return {};
84
85 return locale.substr(0, pos);
86}
87
88#ifdef _WIN32
89
90
93{
94 unsigned long bufSize = 128;
95 wchar_t* localeNameBuf = reinterpret_cast<wchar_t*>(alloca(bufSize));
96 unsigned long numLanguages = 0;
97
98 bool ok = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
99 localeNameBuf, &bufSize);
100 if (!ok) {
101 // if we have a lot of languages, can fail, allocate a bigger
102 // buffer
103 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
104 bufSize = 0;
105 GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
106 nullptr, &bufSize);
107 localeNameBuf = reinterpret_cast<wchar_t*>(alloca(bufSize));
108 ok = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
109 localeNameBuf, &bufSize);
110 }
111 }
112
113 if (!ok) {
114 SG_LOG(SG_GENERAL, SG_WARN, "Failed to detected user locale via GetUserPreferredUILanguages");
115 return{};
116 }
117
118 string_list result;
119 result.reserve(numLanguages);
120 for (unsigned int l = 0; l < numLanguages; ++l) {
121 std::wstring ws(localeNameBuf);
122 if (ws.empty())
123 break;
124
125 // skip to next string, past this string and trailing NULL
126 localeNameBuf += (ws.size() + 1);
127 result.push_back(simgear::strutils::convertWStringToUtf8(ws));
128 SG_LOG(SG_GENERAL, SG_INFO, "User langauge " << l << ":" << result.back());
129 }
130
131 return result;
132}
133#elif __APPLE__
134// implemented in CocoaHelpers.mm
135#else
141{
142 string_list result;
143 const char* langEnv = ::getenv("LANG");
144
145 if (langEnv) {
146 // Remove character encoding from the locale spec, i.e. "de_DE.UTF-8"
147 // becomes "de_DE". This is for consistency with the Windows and MacOS
148 // implementations of this method.
149 result.push_back(removeEncodingPart(langEnv));
150 }
151
152 return result;
153}
154#endif
155
156// Search property tree for matching locale description
157SGPropertyNode*
158FGLocale::findLocaleNode(const string& localeSpec)
159{
160 SGPropertyNode* node = nullptr;
161 // Remove the character encoding part of the locale spec, i.e.,
162 // "de_DE.utf8" => "de_DE"
163 string language = removeEncodingPart(localeSpec);
164
165 SG_LOG(SG_GENERAL, SG_DEBUG,
166 "Searching language resource for locale: '" << language << "'");
167 // search locale using full string
168 vector<SGPropertyNode_ptr> localeList = _intl->getChildren("locale");
169
170 for (size_t i = 0; i < localeList.size(); i++)
171 {
172 vector<SGPropertyNode_ptr> langList = localeList[i]->getChildren("lang");
173
174 for (size_t j = 0; j < langList.size(); j++)
175 {
176 if (!language.compare(langList[j]->getStringValue()))
177 {
178 SG_LOG(SG_GENERAL, SG_INFO, "Found language resource for: " << language);
179 return localeList[i];
180 }
181 }
182 }
183
184 // try country's default resource, i.e. "de_DE" => "de"
185 const auto justTheLanguage = removeLocalePart(language);
186 if (!justTheLanguage.empty()) {
187 node = findLocaleNode(justTheLanguage);
188 if (node)
189 return node;
190 }
191
192 return nullptr;
193}
194
195// Select the language. When no language is given (nullptr),
196// a default is determined matching the system locale.
197bool FGLocale::selectLanguage(const std::string& language)
198{
199 bool result = true;
200 // Remove all loaded translations, including the default translation
201 _domains.clear();
202 // Default translation for 'atc', 'menu', 'options', etc.
204
205 _languages = getUserLanguages();
206 if (_languages.empty()) {
207 // Use plain C locale if nothing is available.
208 SG_LOG(SG_GENERAL, SG_WARN, "Unable to detect system language" );
209 _languages.push_back("C");
210 }
211
212 // if we were passed a language option, try it first
213 if (!language.empty()) {
214 const auto normalizedLang = strutils::replace(language, "-", "_");
215 _languages.insert(_languages.begin(), normalizedLang);
216 }
217
218 _currentLocaleString = removeEncodingPart(_languages.front());
219 if (_currentLocaleString == "C") {
220 _currentLocaleString.clear();
221 }
222 // Record the current locale at /sim/intl/current-locale
223 _intl->getChild("current-locale", 0, true)
224 ->setStringValue(_currentLocaleString);
225
226 _currentLocale.reset();
227
228 if (_currentLocaleString != "default") {
229 for (const string& lang : _languages) {
230 SG_LOG(SG_GENERAL, SG_DEBUG,
231 "Trying to find locale for '" << lang << "'");
233
234 if (_currentLocale) {
235 SG_LOG(SG_GENERAL, SG_DEBUG,
236 "Found locale for '" << lang << "' at "
237 << _currentLocale->getPath());
238 break;
239 }
240 }
241 }
242
243 if (!_currentLocale) {
244 if (_currentLocaleString == "default") {
245 SG_LOG(SG_GENERAL, SG_INFO,
246 "Using the default translation (“engineering English”).");
247 } else {
248 SG_LOG(SG_GENERAL, SG_WARN,
249 "System locale not found or no internationalization "
250 "settings specified in defaults.xml. Using the fallback "
251 "translation (English).");
253 assert(_currentLocale != nullptr);
254 result = false;
255 }
256 }
257
258 // If the _currentLocale shared pointer is non-empty, it points to some
259 // /sim/intl/locale[n] node and _languageId is set to the value of
260 // /sim/intl/locale[n]/language-id. Otherwise (default translation),
261 // _languageId is set to "default".
263 // Record it in /sim/intl/current-language-id
264 _intl->getChild("current-language-id", 0, true)->setStringValue(_languageId);
265
266 if (_currentLocale &&
267 _currentLocale->getNode("core", 0, true)->hasChild("xliff")) {
268 // Load translations for the selected locale
269 loadXLIFF(globals->get_fg_root(), _currentLocale, "core");
270 }
271
272 // From this point on, if (_currentLocale == nullptr), it means
273 // --language=default was passed: the user wants “engineering English”,
274 // so we won't load any XLIFF file (including from aircraft or add-ons).
275 _inited = true;
276 return result;
277}
278
279std::string FGLocale::findLanguageId() const
280{
281 std::string result = "default";
282
283 if (_currentLocale) {
284 SGPropertyNode* n = _currentLocale->getChild("language-id");
285
286 if (n) {
287 result = n->getStringValue();
288
289 if (result.empty()) {
290 SG_LOG(SG_GENERAL, SG_ALERT, "Unexpected empty string value of "
291 << n->getPath() << "; will use 'default' but "
292 "please fix this!");
293 result = "default";
294 }
295 } else {
296 SG_LOG(SG_GENERAL, SG_ALERT, "No 'language-id' child node of " <<
297 _currentLocale->getPath() << "; will use 'default' but "
298 "please fix this!");
299 }
300 }
301
302 return result;
303}
304
305std::string FGLocale::getLanguageId() const
306{
307 return _languageId;
308}
309
311{
312 const simgear::Dir d = simgear::Dir(
313 globals->get_fg_root() / "Translations" / "default");
314
315 loadDefaultTranslation(d, "core");
316}
317
319{
321 "current-aircraft");
322}
323
325{
326 const auto& addonManager = flightgear::addons::AddonManager::instance();
327 if (addonManager) {
328 for (const Addon* addon : addonManager->registeredAddons()) {
329 const string domain = "addons/" + addon->getId();
330 loadResourcesFromAircraftOrAddonDir(addon->getBasePath(), domain);
331 }
332 } else {
333 SG_LOG(SG_GENERAL, SG_WARN,
334 "FGLocale: not loading add-on translations: AddonManager "
335 "instance not found");
336 }
337}
338
340 const string& domain)
341{
342 const simgear::Dir d = simgear::Dir(basePath / "Translations" / "default");
343
344 if (d.exists()) {
345 loadDefaultTranslation(d, domain);
346 }
347
348 if (_currentLocale != nullptr) { // if not “engineering English”
349 loadXLIFFFromAircraftOrAddonDir(basePath, domain);
350 }
351}
352
353void FGLocale::loadDefaultTranslation(const simgear::Dir& defaultTranslationDir,
354 const string& domain)
355{
356 // Files from Translations/default
357 const vector<SGPath> baseXmlFiles = defaultTranslationDir.children(
358 simgear::Dir::TYPE_FILE | simgear::Dir::NO_DOT_OR_DOTDOT, ".xml");
359 // Files from Translations/default/auto-extracted, if this exists
360 vector<SGPath> generatedXmlFiles;
361
362 const SGPath subDirPath = defaultTranslationDir.path() / "auto-extracted";
363 const simgear::Dir subDir = simgear::Dir(subDirPath);
364
365 if (subDir.exists()) {
366 generatedXmlFiles = subDir.children(
367 simgear::Dir::TYPE_FILE | simgear::Dir::NO_DOT_OR_DOTDOT, ".xml");
368 }
369
370 for (const SGPath& file : baseXmlFiles) {
371 // Because file.file_base() stops at the first dot, atc.no_translate.xml
372 // is loaded as the 'atc' resource.
373 loadResourceForDefaultTranslation(file, domain, file.file_base());
374 }
375
376 for (const SGPath& file : generatedXmlFiles) {
377 loadResourceForDefaultTranslation(file, domain, file.file_base());
378 }
379}
380
382 const string& domain)
383{
384 const simgear::Dir translDir = simgear::Dir(basePath / "Translations");
385 if (!translDir.exists()) {
386 return;
387 }
388
389 const auto subdirs = translDir.children(
390 simgear::Dir::TYPE_DIR | simgear::Dir::NO_DOT_OR_DOTDOT);
391
392 assert(_currentLocale != nullptr);
393 const auto langNodes = _currentLocale->getChildren("lang");
394 vector<string> foundSubdirs;
395
396 for (const SGPath& subdir : subdirs) {
397 const string name = subdir.file(); // name of subdir of 'Translations'
398 if (name == "default") {
399 continue;
400 }
401
402 for (const auto& n : langNodes) {
403 if (n->getStringValue() != name) {
404 continue;
405 }
406
407 // Subdir 'name' matches the current locale; check if we didn't
408 // already find one before.
409 foundSubdirs.push_back(name);
410
411 if (foundSubdirs.size() > 1) {
412 SG_LOG(SG_GENERAL, SG_WARN,
413 "Found several matching subdirectories of '"
414 << translDir.path().utf8Str() <<
415 "' for the current locale ("
416 << foundSubdirs[0] << ", " << foundSubdirs[1] <<
417 "). Incorrect Translations/locale.xml setup?");
418 return;
419 }
420
421 // Load the XLIFF file
422 SGPropertyNode* xliffNode = _currentLocale->getNode(
423 domain + "/xliff", 0, true);
424 xliffNode->setStringValue(
425 "Translations/" + name + "/FlightGear-nonQt.xlf");
426 loadXLIFF(basePath, _currentLocale, domain);
427 }
428 }
429}
430
432{
433 _inited = false;
434 _currentLocaleString.clear();
435 _languages.clear();
436 _domains.clear();
437
438 if (_currentLocale) {
439 _currentLocale->removeChild("current-aircraft");
440 _currentLocale->removeChild("addons");
441 }
442
443 _currentLocale.clear();
444 _languageId.clear();
445}
446
447// Return the preferred language according to user choice and/or settings
448// (e.g., 'fr_FR', or the empty string if nothing could be found).
449std::string
454
455void FGLocale::loadXLIFF(const SGPath& basePath, SGPropertyNode* localeNode,
456 const string& domain)
457{
458 SGPropertyNode* domainNode = localeNode->getNode(domain, 0, true);
459 const string relPath = domainNode->getStringValue("xliff");
460 const SGPath xliffPath = basePath / relPath;
461
462 if (!xliffPath.exists()) {
463 SG_LOG(SG_GENERAL, SG_ALERT, "No XLIFF file at " << xliffPath);
464 } else {
465 SG_LOG(SG_GENERAL, SG_INFO, "Loading XLIFF file at " << xliffPath);
466 try {
468 _domains[domain].get());
469 readXML(xliffPath, visitor);
470 } catch (sg_io_exception& ex) {
471 SG_LOG(SG_GENERAL, SG_WARN, "failure parsing XLIFF: " << xliffPath
472 << "\n\t" << ex.getMessage() << "\n\tat: "
473 << ex.getLocation().asString());
474 } catch (sg_exception& ex) {
475 SG_LOG(SG_GENERAL, SG_WARN, "failure parsing XLIFF: " << xliffPath
476 << "\n\t" << ex.getMessage());
477 }
478 }
479}
480
482 const SGPath& xmlFile, const std::string& domain,
483 const std::string& resource)
484{
485 auto& domainPtr = _domains[domain];
486 if (!domainPtr) { // domain not initialized yet
487 domainPtr = std::make_shared<flightgear::TranslationDomain>();
488 }
489
490 // Automatically create the resource if necessary
491 auto resourcePtr = domainPtr->getOrCreateResource(resource);
492 DefaultTranslationParser visitor(resourcePtr.get());
493
494 SG_LOG(SG_GENERAL, SG_INFO, "Reading the default translation for " <<
495 domain << "/" << resource << " from '" << xmlFile.utf8Str() << "'");
496
497 try {
498 readXML(xmlFile, visitor);
499 } catch (const sg_io_exception& ex) {
500 SG_LOG(SG_GENERAL, SG_WARN, "error parsing default translation from '"
501 << xmlFile.utf8Str() << "':\n\t" << ex.getMessage()
502 << "\n\tat: " << ex.getLocation().asString());
503 } catch (const sg_exception& ex) {
504 SG_LOG(SG_GENERAL, SG_WARN, "error parsing default translation from '"
505 << xmlFile.utf8Str() << "':\n\t" << ex.getMessage());
506 }
507}
508
509std::shared_ptr<const flightgear::TranslationDomain>
510FGLocale::getDomain(const std::string& domain) const
511{
512 auto it = _domains.find(domain);
513
514 if (it == _domains.end()) {
515 SG_LOG(SG_GENERAL, SG_ALERT,
516 "FGLocale::getDomain(): unable to find requested domain '"
517 << domain << "'.");
518 return {};
519 }
520
521 return it->second;
522}
523
524std::string
525FGLocale::getLocalizedStringWithIndex(const string& id, const string& resource,
526 int index) const
527{
528 assert(_inited);
529 return FGTranslate().get(resource, id, index);
530}
531
532std::string
533FGLocale::getLocalizedString(const string& id, const string& resource,
534 const std::string& defaultValue)
535{
536 assert(_inited);
537 return FGTranslate().getWithDefault(resource, id, defaultValue);
538}
539
540vector<string>
541FGLocale::getLocalizedStrings(const string& id, const string& resource)
542{
543 assert(_inited);
544 return FGTranslate().getAll(resource, id);
545}
546
547std::size_t FGLocale::getLocalizedStringCount(const string& id,
548 const string& resource) const
549{
550 assert(_inited);
551 return FGTranslate().getCount(resource, id);
552}
553
554// Check for localized font
555std::string
556FGLocale::getDefaultFont(const char* fallbackFont)
557{
558 assert(_inited);
559 std::string font;
560 if (_currentLocale)
561 {
562 font = _currentLocale->getStringValue("font", "");
563 if (!font.empty())
564 return font;
565 }
566
567 if (_fallbackLocale)
568 {
569 font = _fallbackLocale->getStringValue("font", "");
570 if (!font.empty())
571 return font;
572 }
573
574 return fallbackFont;
575}
576
577std::string FGLocale::localizedPrintf(const char* id, const char* resource, ... )
578{
579 va_list args;
580 va_start(args, resource);
581 string r = vlocalizedPrintf(id, resource, args);
582 va_end(args);
583 return r;
584}
585
586std::string FGLocale::vlocalizedPrintf(const char* id, const char* resource, va_list args)
587{
588 assert(_inited);
589 std::string format = getLocalizedString(id, resource);
590 int len = ::vsnprintf(nullptr, 0, format.c_str(), args);
591 char* buf = (char*) alloca(len);
592 ::vsnprintf(buf, len, format.c_str(), args);
593 return std::string(buf);
594}
595
596// Simple UTF8 to Latin1 encoder.
598{
599 size_t pos = 0;
600
601 // map '0xc3..' utf8 characters to Latin1
602 while ((string::npos != (pos = s.find('\xc3',pos)))&&
603 (pos+1 < s.size()))
604 {
605 char c='*';
606 unsigned char p = s[pos+1];
607 if ((p>=0x80)&&(p<0xc0))
608 c = 0x40 + p;
609 char v[2];
610 v[0]=c;
611 v[1]=0;
612 s.replace(pos, 2, v);
613 pos++;
614 }
615
616#ifdef DEBUG_ENCODING
617 printf("'%s': ", s.c_str());
618 for (pos = 0;pos<s.size();pos++)
619 printf("%02x ", (unsigned char) s[pos]);
620 printf("\n");
621#endif
622
623 // hack: also map some Latin2 characters to plain-text ASCII
624 pos = 0;
625 while ((string::npos != (pos = s.find('\xc5',pos)))&&
626 (pos+1 < s.size()))
627 {
628 char c='*';
629 unsigned char p = s[pos+1];
630 switch(p)
631 {
632 case 0x82:c='l';break;
633 case 0x9a:c='S';break;
634 case 0x9b:c='s';break;
635 case 0xba:c='z';break;
636 case 0xbc:c='z';break;
637 }
638 char v[2];
639 v[0]=c;
640 v[1]=0;
641 s.replace(pos, 2, v);
642 pos++;
643 }
644}
645
646std::string fgTrMsg(const char* key)
647{
648 return globals->get_locale()->getLocalizedString(key, "message");
649}
650
651std::string fgTrPrintfMsg(const char* key, ...)
652{
653 va_list args;
654 va_start(args, key);
655 string r = globals->get_locale()->vlocalizedPrintf(key, "message", args);
656 va_end(args);
657 return r;
658}
659
660SGPropertyNode_ptr FGLocale::selectLanguageNode(SGPropertyNode* langs) const
661{
662 if (!langs)
663 return {};
664
665 for (auto l : _languages) {
666 // Only accept the hyphen separator in PropertyList node names between
667 // language and territory
668 const auto langNoEncoding = strutils::replace(removeEncodingPart(l),
669 "_", "-");
670 if (langs->hasChild(langNoEncoding)) {
671 return langs->getChild(langNoEncoding);
672 }
673
674 const auto justLang = removeLocalePart(langNoEncoding);
675 if (langs->hasChild(justLang)) {
676 return langs->getChild(justLang);
677 }
678 }
679
680 return {};
681}
Parse a FlightGear default translation file (e.g., menu.xml)
#define p(x)
Class for retrieving translated strings.
#define i(x)
Parse an XLIFF 1.2 XML file.
void loadAircraftTranslations()
Definition locale.cxx:318
void loadDefaultTranslation(const simgear::Dir &defaultTranslationDir, const std::string &domain)
Load all default translation files from the specified directory.
Definition locale.cxx:353
std::string getLocalizedStringWithIndex(const std::string &id, const std::string &context, int index) const
Obtain a single translation with the given identifier, context and index.
Definition locale.cxx:525
std::size_t getLocalizedStringCount(const std::string &id, const std::string &context) const
Return the number of strings with a given id in the specified context.
Definition locale.cxx:547
bool selectLanguage(const std::string &language={})
Select the locale's primary language according to user-level, system-level language settings and the ...
Definition locale.cxx:197
FGLocale(SGPropertyNode *root)
Definition locale.cxx:48
friend class FGTranslate
Definition locale.hxx:358
void loadCoreResourcesForDefaultTranslation()
Load the core default translation ('atc', 'menu', 'options', 'sys', etc.).
Definition locale.cxx:310
std::string getLanguageId() const
Return the value of _languageId, which uniquely identifies the language for the LanguageInfo class (h...
Definition locale.cxx:305
std::string vlocalizedPrintf(const char *id, const char *resource, va_list args)
Definition locale.cxx:586
std::string _languageId
This is used to fetch linguistic data such as the number of plural forms for the selected locale.
Definition locale.hxx:310
static void utf8toLatin1(std::string &s)
Simple UTF8 to Latin1 encoder.
Definition locale.cxx:597
void loadAddonTranslations()
Definition locale.cxx:324
SGPropertyNode_ptr _intl
Definition locale.hxx:301
void loadResourcesFromAircraftOrAddonDir(const SGPath &basePath, const std::string &domain)
From an add-on or aircraft directory, load the default translation and, if available,...
Definition locale.cxx:339
virtual ~FGLocale()
Definition locale.cxx:54
std::string _currentLocaleString
Corresponds to user's language settings, possibly overridden by the –language value.
Definition locale.hxx:324
string_list getUserLanguages() const
Obtain user's default language settings.
Definition locale.cxx:140
std::vector< std::string > getLocalizedStrings(const std::string &id, const std::string &resource)
Obtain a list of translations that share the same tag name (id stem).
Definition locale.cxx:541
std::string localizedPrintf(const char *id, const char *resource,...)
Obtain a message string, from a localized resource ID, and use it as a printf format string.
Definition locale.cxx:577
void loadXLIFF(const SGPath &basePath, SGPropertyNode *localeNode, const std::string &domain)
Load an XLIFF 1.2 file.
Definition locale.cxx:455
void loadXLIFFFromAircraftOrAddonDir(const SGPath &basePath, const std::string &domain)
Definition locale.cxx:381
SGPropertyNode_ptr _currentLocale
Definition locale.hxx:302
SGPropertyNode * findLocaleNode(const std::string &language)
Find a property node matching the given language.
Definition locale.cxx:158
void loadResourceForDefaultTranslation(const SGPath &xmlFile, const std::string &domain, const std::string &resource)
Load default strings for the requested resource ("atc", "menu", etc.).
Definition locale.cxx:481
std::string getDefaultFont(const char *fallbackFont)
Obtain default font for current locale.
Definition locale.cxx:556
void clear()
reset all data in the locale.
Definition locale.cxx:431
SGPropertyNode_ptr selectLanguageNode(SGPropertyNode *langs) const
Given a node with children corresponding to different language / locale codes, select one based on th...
Definition locale.cxx:660
std::string findLanguageId() const
Return the appropriate value for _languageId according to _currentLocale.
Definition locale.cxx:279
SGPropertyNode_ptr _fallbackLocale
Proper locale (corresponding to a /sim/intl/locale[n] node, as opposed to the default translation) us...
Definition locale.hxx:318
std::string getLocalizedString(const std::string &id, const std::string &resource, const std::string &defaultValue={})
Obtain a single string matching the given id, with fallback.
Definition locale.cxx:533
std::string getPreferredLanguage() const
Return the preferred language according to user choice and/or settings.
Definition locale.cxx:450
Class for parsing a FlightGear default translation file (e.g., menu.xml)
Class that holds translation resources within a domain.
static const std::unique_ptr< AddonManager > & instance()
const char * name
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
FGGlobals * globals
Definition globals.cxx:142
std::vector< std::string > string_list
Definition globals.hxx:36
string removeLocalePart(const string &locale)
Definition locale.cxx:75
std::string fgTrPrintfMsg(const char *key,...)
Definition locale.cxx:651
std::string fgTrMsg(const char *key)
Definition locale.cxx:646
FlightGear Localization Support.