From 0bb9600795a92a3289f74faa934ccbfd20224d7b Mon Sep 17 00:00:00 2001 From: James Turner Date: Thu, 9 Jul 2020 18:14:56 +0100 Subject: [PATCH] Add-ons localization support Add-on meta-data can be localized now, in the same manner as aircraft metadata. --- src/Add-ons/Addon.cxx | 8 ++++ src/Add-ons/Addon.hxx | 7 +++- src/Add-ons/AddonMetadataParser.cxx | 41 +++++++++++++------- src/GUI/CocoaHelpers.mm | 2 +- src/Main/locale.cxx | 60 +++++++++++++++++++++++------ src/Main/locale.hxx | 13 ++++++- 6 files changed, 102 insertions(+), 29 deletions(-) diff --git a/src/Add-ons/Addon.cxx b/src/Add-ons/Addon.cxx index 8631c9e2d..4342f1ae4 100644 --- a/src/Add-ons/Addon.cxx +++ b/src/Add-ons/Addon.cxx @@ -469,6 +469,14 @@ Addon::readMenubarItems(const SGPath& menuFile) return res; } +void Addon::retranslate() +{ + Addon::Metadata metadata = MetadataParser::parseMetadataFile(_basePath); + setName(std::move(metadata.name)); + setShortDescription(std::move(metadata.shortDescription)); + setLongDescription(std::move(metadata.longDescription)); +} + // Static method void Addon::setupGhost(nasal::Hash& addonsModule) { diff --git a/src/Add-ons/Addon.hxx b/src/Add-ons/Addon.hxx index 8983283a1..b472a0737 100644 --- a/src/Add-ons/Addon.hxx +++ b/src/Add-ons/Addon.hxx @@ -195,7 +195,12 @@ public: static void setupGhost(nasal::Hash& addonsModule); -private: + /** + * @brief update string values (description, etc) based on the active locale + */ + void retranslate(); + + private: class Metadata; class MetadataParser; diff --git a/src/Add-ons/AddonMetadataParser.cxx b/src/Add-ons/AddonMetadataParser.cxx index d17ec402e..5675b3965 100644 --- a/src/Add-ons/AddonMetadataParser.cxx +++ b/src/Add-ons/AddonMetadataParser.cxx @@ -35,6 +35,9 @@ #include "exceptions.hxx" #include "pointer_traits.hxx" +#include
+#include
+ namespace strutils = simgear::strutils; using std::string; @@ -53,6 +56,23 @@ Addon::MetadataParser::getMetadataFile(const SGPath& addonPath) return addonPath / "addon-metadata.xml"; } +static string getMaybeLocalized(const string& tag, SGPropertyNode* base, SGPropertyNode* lang) +{ + if (lang) { + auto n = lang->getChild(tag); + if (n) { + return strutils::strip(n->getStringValue()); + } + } + + auto n = base->getChild(tag); + if (n) { + return strutils::strip(n->getStringValue()); + } + + return {}; +} + // Static method Addon::Metadata Addon::MetadataParser::parseMetadataFile(const SGPath& addonPath) @@ -121,6 +141,9 @@ Addon::MetadataParser::parseMetadataFile(const SGPath& addonPath) metadataFile.utf8Str() + "'"); } + SGPropertyNode* localizedNode = addonNode->getChild("localized"); + SGPropertyNode* langStringsNode = globals->get_locale()->selectLanguageNode(localizedNode); + SGPropertyNode *idNode = addonNode->getChild("identifier"); if (idNode == nullptr) { throw errors::error_loading_metadata_file( @@ -147,7 +170,8 @@ Addon::MetadataParser::parseMetadataFile(const SGPath& addonPath) "no /addon/name node found in add-on metadata file '" + metadataFile.utf8Str() + "'"); } - metadata.name = strutils::strip(nameNode->getStringValue()); + + metadata.name = getMaybeLocalized("name", addonNode, langStringsNode); // Require a non-empty name for the add-on if (metadata.name.empty()) { @@ -170,19 +194,8 @@ Addon::MetadataParser::parseMetadataFile(const SGPath& addonPath) metadata.maintainers = parseContactsNode( metadataFile, addonNode->getChild("maintainers")); - SGPropertyNode *shortDescNode = addonNode->getChild("short-description"); - if (shortDescNode != nullptr) { - metadata.shortDescription = strutils::strip(shortDescNode->getStringValue()); - } else { - metadata.shortDescription = string(); - } - - SGPropertyNode *longDescNode = addonNode->getChild("long-description"); - if (longDescNode != nullptr) { - metadata.longDescription = strutils::strip(longDescNode->getStringValue()); - } else { - metadata.longDescription = string(); - } + metadata.shortDescription = getMaybeLocalized("short-description", addonNode, langStringsNode); + metadata.longDescription = getMaybeLocalized("long-description", addonNode, langStringsNode); std::tie(metadata.licenseDesignation, metadata.licenseFile, metadata.licenseUrl) = parseLicenseNode(addonPath, addonNode); diff --git a/src/GUI/CocoaHelpers.mm b/src/GUI/CocoaHelpers.mm index eb03b2a53..a5219b25c 100644 --- a/src/GUI/CocoaHelpers.mm +++ b/src/GUI/CocoaHelpers.mm @@ -194,7 +194,7 @@ QWindow* qtWindowFromOSG(osgViewer::GraphicsWindow* graphicsWindow) } // of namespace flightgear -string_list FGLocale::getUserLanguage() +string_list FGLocale::getUserLanguages() { CocoaAutoreleasePool ap; string_list result; diff --git a/src/Main/locale.cxx b/src/Main/locale.cxx index 8b5aa905b..3c7c04b00 100644 --- a/src/Main/locale.cxx +++ b/src/Main/locale.cxx @@ -37,6 +37,8 @@ #include "locale.hxx" #include "XLIFFParser.hxx" +#include + using std::vector; using std::string; @@ -67,11 +69,24 @@ string FGLocale::removeEncodingPart(const string& locale) return res; } +string removeLocalePart(const string& locale) +{ + std::size_t pos = locale.find('_'); + if (pos == string::npos) { + pos = locale.find('-'); + } + + if (pos == string::npos) + return {}; + + return locale.substr(0, pos); +} + #ifdef _WIN32 string_list -FGLocale::getUserLanguage() +FGLocale::getUserLanguages() { unsigned long bufSize = 128; wchar_t* localeNameBuf = reinterpret_cast(alloca(bufSize)); @@ -119,7 +134,7 @@ FGLocale::getUserLanguage() * Determine locale/language settings on Linux/Unix. */ string_list -FGLocale::getUserLanguage() +FGLocale::getUserLanguages() { string_list result; const char* langEnv = ::getenv("LANG"); @@ -171,9 +186,9 @@ FGLocale::findLocaleNode(const string& localeSpec) pos = language.find('-'); } - if ((pos != string::npos) && (pos > 0)) - { - node = findLocaleNode(language.substr(0, pos)); + const auto justTheLanguage = removeLocalePart(language); + if (!justTheLanguage.empty()) { + node = findLocaleNode(justTheLanguage); if (node) return node; } @@ -185,26 +200,26 @@ FGLocale::findLocaleNode(const string& localeSpec) // a default is determined matching the system locale. bool FGLocale::selectLanguage(const std::string& language) { - string_list languages = getUserLanguage(); - if (languages.empty()) { + _languages = getUserLanguages(); + if (_languages.empty()) { // Use plain C locale if nothing is available. SG_LOG(SG_GENERAL, SG_WARN, "Unable to detect system language" ); - languages.push_back("C"); + _languages.push_back("C"); } // if we were passed a language option, try it first if (!language.empty()) { - languages.insert(languages.begin(), language); + _languages.insert(_languages.begin(), language); } - _currentLocaleString = removeEncodingPart(languages[0]); + _currentLocaleString = removeEncodingPart(_languages.front()); if (_currentLocaleString == "C") { _currentLocaleString.clear(); } _currentLocale = nullptr; - for (const string& lang: languages) { + for (const string& lang : _languages) { SG_LOG(SG_GENERAL, SG_DEBUG, "Trying to find locale for '" << lang << "'"); _currentLocale = findLocaleNode(lang); @@ -216,7 +231,7 @@ bool FGLocale::selectLanguage(const std::string& language) break; } } - + if (_currentLocale && _currentLocale->hasChild("xliff")) { parseXLIFF(_currentLocale); } @@ -242,6 +257,7 @@ void FGLocale::clear() { _inited = false; _currentLocaleString.clear(); + _languages.clear(); if (_currentLocale && (_currentLocale != _defaultLocale)) { // remove loaded strings, so we don't duplicate @@ -585,3 +601,23 @@ std::string fgTrPrintfMsg(const char* key, ...) va_end(args); return r; } + +SGPropertyNode_ptr FGLocale::selectLanguageNode(SGPropertyNode* langs) const +{ + if (!langs) + return {}; + + for (auto l : _languages) { + const auto langNoEncoding = removeEncodingPart(l); + if (langs->hasChild(langNoEncoding)) { + return langs->getChild(langNoEncoding); + } + + const auto justLang = removeLocalePart(langNoEncoding); + if (langs->hasChild(justLang)) { + return langs->getChild(justLang); + } + } + + return {}; +} diff --git a/src/Main/locale.hxx b/src/Main/locale.hxx index 5490e5c46..96903caa6 100644 --- a/src/Main/locale.hxx +++ b/src/Main/locale.hxx @@ -118,6 +118,12 @@ public: */ void clear(); + /** + @ brief given a node with children corresponding to different language / locale codes, + select one based on the user preferred langauge + */ + SGPropertyNode_ptr selectLanguageNode(SGPropertyNode* langs) const; + protected: /** * Find property node matching given language. @@ -142,7 +148,7 @@ protected: /** * Obtain user's default language setting. */ - string_list getUserLanguage(); + string_list getUserLanguages(); SGPropertyNode_ptr _intl; SGPropertyNode_ptr _currentLocale; @@ -161,6 +167,11 @@ private: */ static std::string removeEncodingPart(const std::string& locale); + // this is the ordered list of languages to try. It's the same as + // returned by getUserLanguages except if the user has used --langauge to + // override, that will be the first item. + + string_list _languages; bool _inited = false; };