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>
42namespace strutils = simgear::strutils;
49 _intl(root->getNode(
"/sim/intl", 0, true)),
59string FGLocale::removeEncodingPart(
const string& locale)
62 std::size_t pos = locale.find(
'.');
64 if (pos != string::npos)
67 res = locale.substr(0, pos);
77 std::size_t pos = locale.find(
'_');
78 if (pos == string::npos) {
79 pos = locale.find(
'-');
82 if (pos == string::npos)
85 return locale.substr(0, pos);
94 unsigned long bufSize = 128;
95 wchar_t* localeNameBuf =
reinterpret_cast<wchar_t*
>(alloca(bufSize));
96 unsigned long numLanguages = 0;
98 bool ok = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
99 localeNameBuf, &bufSize);
103 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
105 GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
107 localeNameBuf =
reinterpret_cast<wchar_t*
>(alloca(bufSize));
108 ok = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
109 localeNameBuf, &bufSize);
114 SG_LOG(SG_GENERAL, SG_WARN,
"Failed to detected user locale via GetUserPreferredUILanguages");
119 result.reserve(numLanguages);
120 for (
unsigned int l = 0; l < numLanguages; ++l) {
121 std::wstring ws(localeNameBuf);
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());
143 const char* langEnv = ::getenv(
"LANG");
149 result.push_back(removeEncodingPart(langEnv));
160 SGPropertyNode* node =
nullptr;
163 string language = removeEncodingPart(localeSpec);
165 SG_LOG(SG_GENERAL, SG_DEBUG,
166 "Searching language resource for locale: '" << language <<
"'");
168 vector<SGPropertyNode_ptr> localeList =
_intl->getChildren(
"locale");
170 for (
size_t i = 0;
i < localeList.size();
i++)
172 vector<SGPropertyNode_ptr> langList = localeList[
i]->getChildren(
"lang");
174 for (
size_t j = 0; j < langList.size(); j++)
176 if (!language.compare(langList[j]->getStringValue()))
178 SG_LOG(SG_GENERAL, SG_INFO,
"Found language resource for: " << language);
179 return localeList[
i];
186 if (!justTheLanguage.empty()) {
206 if (_languages.empty()) {
208 SG_LOG(SG_GENERAL, SG_WARN,
"Unable to detect system language" );
209 _languages.push_back(
"C");
213 if (!language.empty()) {
214 const auto normalizedLang = strutils::replace(language,
"-",
"_");
215 _languages.insert(_languages.begin(), normalizedLang);
223 _intl->getChild(
"current-locale", 0,
true)
229 for (
const string& lang : _languages) {
230 SG_LOG(SG_GENERAL, SG_DEBUG,
231 "Trying to find locale for '" << lang <<
"'");
235 SG_LOG(SG_GENERAL, SG_DEBUG,
236 "Found locale for '" << lang <<
"' at "
245 SG_LOG(SG_GENERAL, SG_INFO,
246 "Using the default translation (“engineering English”).");
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).");
264 _intl->getChild(
"current-language-id", 0,
true)->setStringValue(
_languageId);
281 std::string result =
"default";
287 result = n->getStringValue();
289 if (result.empty()) {
290 SG_LOG(SG_GENERAL, SG_ALERT,
"Unexpected empty string value of "
291 << n->getPath() <<
"; will use 'default' but "
296 SG_LOG(SG_GENERAL, SG_ALERT,
"No 'language-id' child node of " <<
312 const simgear::Dir d = simgear::Dir(
313 globals->get_fg_root() /
"Translations" /
"default");
328 for (
const Addon* addon : addonManager->registeredAddons()) {
329 const string domain =
"addons/" + addon->getId();
333 SG_LOG(SG_GENERAL, SG_WARN,
334 "FGLocale: not loading add-on translations: AddonManager "
335 "instance not found");
340 const string& domain)
342 const simgear::Dir d = simgear::Dir(basePath /
"Translations" /
"default");
354 const string& domain)
357 const vector<SGPath> baseXmlFiles = defaultTranslationDir.children(
358 simgear::Dir::TYPE_FILE | simgear::Dir::NO_DOT_OR_DOTDOT,
".xml");
360 vector<SGPath> generatedXmlFiles;
362 const SGPath subDirPath = defaultTranslationDir.path() /
"auto-extracted";
363 const simgear::Dir subDir = simgear::Dir(subDirPath);
365 if (subDir.exists()) {
366 generatedXmlFiles = subDir.children(
367 simgear::Dir::TYPE_FILE | simgear::Dir::NO_DOT_OR_DOTDOT,
".xml");
370 for (
const SGPath& file : baseXmlFiles) {
376 for (
const SGPath& file : generatedXmlFiles) {
382 const string& domain)
384 const simgear::Dir translDir = simgear::Dir(basePath /
"Translations");
385 if (!translDir.exists()) {
389 const auto subdirs = translDir.children(
390 simgear::Dir::TYPE_DIR | simgear::Dir::NO_DOT_OR_DOTDOT);
394 vector<string> foundSubdirs;
396 for (
const SGPath& subdir : subdirs) {
397 const string name = subdir.file();
398 if (
name ==
"default") {
402 for (
const auto& n : langNodes) {
403 if (n->getStringValue() !=
name) {
409 foundSubdirs.push_back(
name);
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?");
423 domain +
"/xliff", 0,
true);
424 xliffNode->setStringValue(
425 "Translations/" +
name +
"/FlightGear-nonQt.xlf");
456 const string& domain)
458 SGPropertyNode* domainNode = localeNode->getNode(domain, 0,
true);
459 const string relPath = domainNode->getStringValue(
"xliff");
460 const SGPath xliffPath = basePath / relPath;
462 if (!xliffPath.exists()) {
463 SG_LOG(SG_GENERAL, SG_ALERT,
"No XLIFF file at " << xliffPath);
465 SG_LOG(SG_GENERAL, SG_INFO,
"Loading XLIFF file at " << xliffPath);
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());
482 const SGPath& xmlFile,
const std::string& domain,
483 const std::string& resource)
485 auto& domainPtr = _domains[domain];
487 domainPtr = std::make_shared<flightgear::TranslationDomain>();
491 auto resourcePtr = domainPtr->getOrCreateResource(resource);
494 SG_LOG(SG_GENERAL, SG_INFO,
"Reading the default translation for " <<
495 domain <<
"/" << resource <<
" from '" << xmlFile.utf8Str() <<
"'");
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());
509std::shared_ptr<const flightgear::TranslationDomain>
510FGLocale::getDomain(
const std::string& domain)
const
512 auto it = _domains.find(domain);
514 if (it == _domains.end()) {
515 SG_LOG(SG_GENERAL, SG_ALERT,
516 "FGLocale::getDomain(): unable to find requested domain '"
534 const std::string& defaultValue)
537 return FGTranslate().getWithDefault(resource,
id, defaultValue);
548 const string& resource)
const
580 va_start(args, 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);
602 while ((string::npos != (pos = s.find(
'\xc3',pos)))&&
606 unsigned char p = s[pos+1];
607 if ((
p>=0x80)&&(
p<0xc0))
612 s.replace(pos, 2, v);
617 printf(
"'%s': ", s.c_str());
618 for (pos = 0;pos<s.size();pos++)
619 printf(
"%02x ", (
unsigned char) s[pos]);
625 while ((string::npos != (pos = s.find(
'\xc5',pos)))&&
629 unsigned char p = s[pos+1];
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;
641 s.replace(pos, 2, v);
648 return globals->get_locale()->getLocalizedString(key,
"message");
655 string r =
globals->get_locale()->vlocalizedPrintf(key,
"message", args);
665 for (
auto l : _languages) {
668 const auto langNoEncoding = strutils::replace(removeEncodingPart(l),
670 if (langs->hasChild(langNoEncoding)) {
671 return langs->getChild(langNoEncoding);
675 if (langs->hasChild(justLang)) {
676 return langs->getChild(justLang);
Parse a FlightGear default translation file (e.g., menu.xml)
Class for retrieving translated strings.
Parse an XLIFF 1.2 XML file.
void loadAircraftTranslations()
void loadDefaultTranslation(const simgear::Dir &defaultTranslationDir, const std::string &domain)
Load all default translation files from the specified directory.
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.
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.
bool selectLanguage(const std::string &language={})
Select the locale's primary language according to user-level, system-level language settings and the ...
FGLocale(SGPropertyNode *root)
void loadCoreResourcesForDefaultTranslation()
Load the core default translation ('atc', 'menu', 'options', 'sys', etc.).
std::string getLanguageId() const
Return the value of _languageId, which uniquely identifies the language for the LanguageInfo class (h...
std::string vlocalizedPrintf(const char *id, const char *resource, va_list args)
std::string _languageId
This is used to fetch linguistic data such as the number of plural forms for the selected locale.
static void utf8toLatin1(std::string &s)
Simple UTF8 to Latin1 encoder.
void loadAddonTranslations()
void loadResourcesFromAircraftOrAddonDir(const SGPath &basePath, const std::string &domain)
From an add-on or aircraft directory, load the default translation and, if available,...
std::string _currentLocaleString
Corresponds to user's language settings, possibly overridden by the –language value.
string_list getUserLanguages() const
Obtain user's default language settings.
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).
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.
void loadXLIFF(const SGPath &basePath, SGPropertyNode *localeNode, const std::string &domain)
Load an XLIFF 1.2 file.
void loadXLIFFFromAircraftOrAddonDir(const SGPath &basePath, const std::string &domain)
SGPropertyNode_ptr _currentLocale
SGPropertyNode * findLocaleNode(const std::string &language)
Find a property node matching the given language.
void loadResourceForDefaultTranslation(const SGPath &xmlFile, const std::string &domain, const std::string &resource)
Load default strings for the requested resource ("atc", "menu", etc.).
std::string getDefaultFont(const char *fallbackFont)
Obtain default font for current locale.
void clear()
reset all data in the locale.
SGPropertyNode_ptr selectLanguageNode(SGPropertyNode *langs) const
Given a node with children corresponding to different language / locale codes, select one based on th...
std::string findLanguageId() const
Return the appropriate value for _languageId according to _currentLocale.
SGPropertyNode_ptr _fallbackLocale
Proper locale (corresponding to a /sim/intl/locale[n] node, as opposed to the default translation) us...
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.
std::string getPreferredLanguage() const
Return the preferred language according to user choice and/or settings.
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()
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
std::vector< std::string > string_list
string removeLocalePart(const string &locale)
std::string fgTrPrintfMsg(const char *key,...)
std::string fgTrMsg(const char *key)
FlightGear Localization Support.