Add-ons localization support
Add-on meta-data can be localized now, in the same manner as aircraft metadata.
This commit is contained in:
parent
f0d3663102
commit
0bb9600795
6 changed files with 102 additions and 29 deletions
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {};
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue