1
0
Fork 0

Add-ons localization support

Add-on meta-data can be localized now, in the same manner as aircraft
metadata.
This commit is contained in:
James Turner 2020-07-09 18:14:56 +01:00
parent f0d3663102
commit 0bb9600795
6 changed files with 102 additions and 29 deletions

View file

@ -469,6 +469,14 @@ Addon::readMenubarItems(const SGPath& menuFile)
return res; 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 // Static method
void Addon::setupGhost(nasal::Hash& addonsModule) void Addon::setupGhost(nasal::Hash& addonsModule)
{ {

View file

@ -195,7 +195,12 @@ public:
static void setupGhost(nasal::Hash& addonsModule); static void setupGhost(nasal::Hash& addonsModule);
private: /**
* @brief update string values (description, etc) based on the active locale
*/
void retranslate();
private:
class Metadata; class Metadata;
class MetadataParser; class MetadataParser;

View file

@ -35,6 +35,9 @@
#include "exceptions.hxx" #include "exceptions.hxx"
#include "pointer_traits.hxx" #include "pointer_traits.hxx"
#include <Main/globals.hxx>
#include <Main/locale.hxx>
namespace strutils = simgear::strutils; namespace strutils = simgear::strutils;
using std::string; using std::string;
@ -53,6 +56,23 @@ Addon::MetadataParser::getMetadataFile(const SGPath& addonPath)
return addonPath / "addon-metadata.xml"; 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 // Static method
Addon::Metadata Addon::Metadata
Addon::MetadataParser::parseMetadataFile(const SGPath& addonPath) Addon::MetadataParser::parseMetadataFile(const SGPath& addonPath)
@ -121,6 +141,9 @@ Addon::MetadataParser::parseMetadataFile(const SGPath& addonPath)
metadataFile.utf8Str() + "'"); metadataFile.utf8Str() + "'");
} }
SGPropertyNode* localizedNode = addonNode->getChild("localized");
SGPropertyNode* langStringsNode = globals->get_locale()->selectLanguageNode(localizedNode);
SGPropertyNode *idNode = addonNode->getChild("identifier"); SGPropertyNode *idNode = addonNode->getChild("identifier");
if (idNode == nullptr) { if (idNode == nullptr) {
throw errors::error_loading_metadata_file( 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 '" + "no /addon/name node found in add-on metadata file '" +
metadataFile.utf8Str() + "'"); metadataFile.utf8Str() + "'");
} }
metadata.name = strutils::strip(nameNode->getStringValue());
metadata.name = getMaybeLocalized("name", addonNode, langStringsNode);
// Require a non-empty name for the add-on // Require a non-empty name for the add-on
if (metadata.name.empty()) { if (metadata.name.empty()) {
@ -170,19 +194,8 @@ Addon::MetadataParser::parseMetadataFile(const SGPath& addonPath)
metadata.maintainers = parseContactsNode<Maintainer>( metadata.maintainers = parseContactsNode<Maintainer>(
metadataFile, addonNode->getChild("maintainers")); metadataFile, addonNode->getChild("maintainers"));
SGPropertyNode *shortDescNode = addonNode->getChild("short-description"); metadata.shortDescription = getMaybeLocalized("short-description", addonNode, langStringsNode);
if (shortDescNode != nullptr) { metadata.longDescription = getMaybeLocalized("long-description", addonNode, langStringsNode);
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();
}
std::tie(metadata.licenseDesignation, metadata.licenseFile, std::tie(metadata.licenseDesignation, metadata.licenseFile,
metadata.licenseUrl) = parseLicenseNode(addonPath, addonNode); metadata.licenseUrl) = parseLicenseNode(addonPath, addonNode);

View file

@ -194,7 +194,7 @@ QWindow* qtWindowFromOSG(osgViewer::GraphicsWindow* graphicsWindow)
} // of namespace flightgear } // of namespace flightgear
string_list FGLocale::getUserLanguage() string_list FGLocale::getUserLanguages()
{ {
CocoaAutoreleasePool ap; CocoaAutoreleasePool ap;
string_list result; string_list result;

View file

@ -37,6 +37,8 @@
#include "locale.hxx" #include "locale.hxx"
#include "XLIFFParser.hxx" #include "XLIFFParser.hxx"
#include <Add-ons/AddonMetadataParser.hxx>
using std::vector; using std::vector;
using std::string; using std::string;
@ -67,11 +69,24 @@ string FGLocale::removeEncodingPart(const string& locale)
return res; 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 #ifdef _WIN32
string_list string_list
FGLocale::getUserLanguage() FGLocale::getUserLanguages()
{ {
unsigned long bufSize = 128; unsigned long bufSize = 128;
wchar_t* localeNameBuf = reinterpret_cast<wchar_t*>(alloca(bufSize)); wchar_t* localeNameBuf = reinterpret_cast<wchar_t*>(alloca(bufSize));
@ -119,7 +134,7 @@ FGLocale::getUserLanguage()
* Determine locale/language settings on Linux/Unix. * Determine locale/language settings on Linux/Unix.
*/ */
string_list string_list
FGLocale::getUserLanguage() FGLocale::getUserLanguages()
{ {
string_list result; string_list result;
const char* langEnv = ::getenv("LANG"); const char* langEnv = ::getenv("LANG");
@ -171,9 +186,9 @@ FGLocale::findLocaleNode(const string& localeSpec)
pos = language.find('-'); pos = language.find('-');
} }
if ((pos != string::npos) && (pos > 0)) const auto justTheLanguage = removeLocalePart(language);
{ if (!justTheLanguage.empty()) {
node = findLocaleNode(language.substr(0, pos)); node = findLocaleNode(justTheLanguage);
if (node) if (node)
return node; return node;
} }
@ -185,26 +200,26 @@ FGLocale::findLocaleNode(const string& localeSpec)
// a default is determined matching the system locale. // a default is determined matching the system locale.
bool FGLocale::selectLanguage(const std::string& language) bool FGLocale::selectLanguage(const std::string& language)
{ {
string_list languages = getUserLanguage(); _languages = getUserLanguages();
if (languages.empty()) { if (_languages.empty()) {
// Use plain C locale if nothing is available. // Use plain C locale if nothing is available.
SG_LOG(SG_GENERAL, SG_WARN, "Unable to detect system language" ); 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 we were passed a language option, try it first
if (!language.empty()) { if (!language.empty()) {
languages.insert(languages.begin(), language); _languages.insert(_languages.begin(), language);
} }
_currentLocaleString = removeEncodingPart(languages[0]); _currentLocaleString = removeEncodingPart(_languages.front());
if (_currentLocaleString == "C") { if (_currentLocaleString == "C") {
_currentLocaleString.clear(); _currentLocaleString.clear();
} }
_currentLocale = nullptr; _currentLocale = nullptr;
for (const string& lang: languages) { for (const string& lang : _languages) {
SG_LOG(SG_GENERAL, SG_DEBUG, SG_LOG(SG_GENERAL, SG_DEBUG,
"Trying to find locale for '" << lang << "'"); "Trying to find locale for '" << lang << "'");
_currentLocale = findLocaleNode(lang); _currentLocale = findLocaleNode(lang);
@ -216,7 +231,7 @@ bool FGLocale::selectLanguage(const std::string& language)
break; break;
} }
} }
if (_currentLocale && _currentLocale->hasChild("xliff")) { if (_currentLocale && _currentLocale->hasChild("xliff")) {
parseXLIFF(_currentLocale); parseXLIFF(_currentLocale);
} }
@ -242,6 +257,7 @@ void FGLocale::clear()
{ {
_inited = false; _inited = false;
_currentLocaleString.clear(); _currentLocaleString.clear();
_languages.clear();
if (_currentLocale && (_currentLocale != _defaultLocale)) { if (_currentLocale && (_currentLocale != _defaultLocale)) {
// remove loaded strings, so we don't duplicate // remove loaded strings, so we don't duplicate
@ -585,3 +601,23 @@ std::string fgTrPrintfMsg(const char* key, ...)
va_end(args); va_end(args);
return r; 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 {};
}

View file

@ -118,6 +118,12 @@ public:
*/ */
void clear(); 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: protected:
/** /**
* Find property node matching given language. * Find property node matching given language.
@ -142,7 +148,7 @@ protected:
/** /**
* Obtain user's default language setting. * Obtain user's default language setting.
*/ */
string_list getUserLanguage(); string_list getUserLanguages();
SGPropertyNode_ptr _intl; SGPropertyNode_ptr _intl;
SGPropertyNode_ptr _currentLocale; SGPropertyNode_ptr _currentLocale;
@ -161,6 +167,11 @@ private:
*/ */
static std::string removeEncodingPart(const std::string& locale); 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; bool _inited = false;
}; };