From 5a11e57d0abe141a3c5f240dc6d06a00037b21f5 Mon Sep 17 00:00:00 2001 From: James Turner Date: Fri, 19 Jun 2020 10:45:47 +0100 Subject: [PATCH] I18N: support localised local aircraft strings Allows us to localize aircraft names/descriptions, especially for the UFO and C172 --- src/GUI/AircraftModel.cxx | 14 ++--- src/GUI/LocalAircraftCache.cxx | 94 ++++++++++++++++++++++++++++++---- src/GUI/LocalAircraftCache.hxx | 35 +++++++++++-- src/GUI/QmlAircraftInfo.cxx | 10 ++-- src/GUI/QtLauncher.cxx | 2 +- 5 files changed, 129 insertions(+), 26 deletions(-) diff --git a/src/GUI/AircraftModel.cxx b/src/GUI/AircraftModel.cxx index ec474602a..bcfaa38fe 100644 --- a/src/GUI/AircraftModel.cxx +++ b/src/GUI/AircraftModel.cxx @@ -277,11 +277,11 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta if (role >= AircraftVariantDescriptionRole) { int variantIndex = role - AircraftVariantDescriptionRole; if (variantIndex == 0) { - return item->description; + return item->name(); } Q_ASSERT(variantIndex < item->variants.size()); - return item->variants.at(variantIndex)->description; + return item->variants.at(variantIndex)->name(); } if (state.variant) { @@ -292,11 +292,11 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta } if (role == Qt::DisplayRole) { - if (item->description.isEmpty()) { + if (item->name().isEmpty()) { return tr("Missing description for: %1").arg(item->baseName()); } - return item->description; + return item->name(); } else if (role == AircraftPathRole) { return item->path; } else if (role == AircraftAuthorsRole) { @@ -318,7 +318,7 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta } return have; } else if (role == AircraftLongDescriptionRole) { - return item->longDescription; + return item->description(); } else if (role == AircraftIsHelicopterRole) { return item->usesHeliports; } else if (role == AircraftIsSeaplaneRole) { @@ -564,14 +564,14 @@ QString AircraftItemModel::nameForAircraftURI(QUrl uri) const const QString path = uri.toLocalFile(); if (item->path == path) { - return item->description; + return item->name(); } // check variants too for (int vr=0; vr < item->variants.size(); ++vr) { auto variant = item->variants.at(vr); if (variant->path == path) { - return variant->description; + return variant->name(); } } } else if (uri.scheme() == "package") { diff --git a/src/GUI/LocalAircraftCache.cxx b/src/GUI/LocalAircraftCache.cxx index e538690cf..db6dd40ab 100644 --- a/src/GUI/LocalAircraftCache.cxx +++ b/src/GUI/LocalAircraftCache.cxx @@ -31,17 +31,28 @@ #include #include -#include
#include +#include
+#include
#include #include #include -static quint32 CACHE_VERSION = 12; +static quint32 CACHE_VERSION = 13; -AircraftItem::AircraftItem() +const std::vector static_localizedStringTags = {"name", "desc"}; + +QDataStream& operator<<(QDataStream& ds, const AircraftItem::LocalizedStrings& ls) { + ds << ls.locale << ls.strings; + return ds; +} + +QDataStream& operator>>(QDataStream& ds, AircraftItem::LocalizedStrings& ls) +{ + ds >> ls.locale >> ls.strings; + return ds; } AircraftItem::AircraftItem(QDir dir, QString filePath) @@ -62,10 +73,9 @@ AircraftItem::AircraftItem(QDir dir, QString filePath) return; } - description = sim->getStringValue("description"); - // trim the description to ensure the alphabetical sort - // doesn't get thrown off by leading whitespace - description = description.trimmed(); + LocalizedStrings ls; + ls.locale = "en"; + ls.strings["name"] = QString::fromStdString(sim->getStringValue("description")).trimmed(); authors = sim->getStringValue("author"); if (sim->hasChild("rating")) { @@ -77,7 +87,7 @@ AircraftItem::AircraftItem(QDir dir, QString filePath) if (sim->hasChild("long-description")) { // clean up any XML whitspace in the text. - longDescription = QString(sim->getStringValue("long-description")).simplified(); + ls.strings["desc"] = QString(sim->getStringValue("long-description")).simplified(); } if (sim->hasChild("variant-of")) { @@ -132,6 +142,65 @@ AircraftItem::AircraftItem(QDir dir, QString filePath) homepageUrl = QUrl(QString::fromStdString(sim->getStringValue("urls/home-page"))); supportUrl = QUrl(QString::fromStdString(sim->getStringValue("urls/support"))); wikipediaUrl = QUrl(QString::fromStdString(sim->getStringValue("urls/wikipedia"))); + + _localized.push_front(ls); + readLocalizedStrings(sim); + doLocalizeStrings(); +} + +void AircraftItem::readLocalizedStrings(SGPropertyNode_ptr simNode) +{ + if (!simNode->hasChild("localized")) + return; + + auto localeNode = simNode->getChild("localized"); + const auto num = localeNode->nChildren(); + for (int i = 0; i < num; i++) { + const SGPropertyNode* c = localeNode->getChild(i); + + LocalizedStrings ls; + ls.locale = QString::fromStdString(c->getNameString()); + if (c->hasChild("description")) { + ls.strings["name"] = QString::fromStdString(c->getStringValue("description")); + } + if (c->hasChild("long-description")) { + ls.strings["desc"] = QString::fromStdString(c->getStringValue("long-description")).simplified(); + } + + _localized.push_back(ls); + } +} + +void AircraftItem::doLocalizeStrings() +{ + // default strings are always at the front + _currentStrings = _localized.front().strings; + + const auto lang = QString::fromStdString(globals->get_locale()->getPreferredLanguage()); + // find the matching locale + auto it = std::find_if(_localized.begin(), _localized.end(), [lang](const LocalizedStrings& ls) { + return ls.locale == lang; + }); + + if (it == _localized.end()) + return; // nothing else to do + + for (auto t : static_localizedStringTags) { + if (it->strings.contains(t)) { + // copy the value we found + _currentStrings[t] = it->strings.value(t); + } + } // of strings iteration +} + +QString AircraftItem::name() const +{ + return _currentStrings.value("name"); +} + +QString AircraftItem::description() const +{ + return _currentStrings.value("desc"); } QString AircraftItem::baseName() const @@ -148,7 +217,7 @@ void AircraftItem::fromDataStream(QDataStream& ds) return; } - ds >> description >> longDescription >> authors >> variantOf >> isPrimary; + ds >> authors >> variantOf >> isPrimary; for (int i=0; i<4; ++i) ds >> ratings[i]; ds >> previews; ds >> thumbnailPath; @@ -156,6 +225,9 @@ void AircraftItem::fromDataStream(QDataStream& ds) ds >> needsMaintenance >> usesHeliports >> usesSeaports; ds >> homepageUrl >> supportUrl >> wikipediaUrl; ds >> tags; + ds >> _localized; + + doLocalizeStrings(); } void AircraftItem::toDataStream(QDataStream& ds) const @@ -165,7 +237,7 @@ void AircraftItem::toDataStream(QDataStream& ds) const return; } - ds << description << longDescription << authors << variantOf << isPrimary; + ds << authors << variantOf << isPrimary; for (int i=0; i<4; ++i) ds << ratings[i]; ds << previews; ds << thumbnailPath; @@ -173,6 +245,7 @@ void AircraftItem::toDataStream(QDataStream& ds) const ds << needsMaintenance << usesHeliports << usesSeaports; ds << homepageUrl << supportUrl << wikipediaUrl; ds << tags; + ds << _localized; } int AircraftItem::indexOfVariant(QUrl uri) const @@ -216,6 +289,7 @@ public: SGPath resolve(const std::string& aResource, SGPath& aContext) const override { + Q_UNUSED(aContext) string_list pieces(sgPathBranchSplit(aResource)); if ((pieces.size() < 3) || (pieces.front() != "Aircraft")) { return SGPath{}; // not an Aircraft path diff --git a/src/GUI/LocalAircraftCache.hxx b/src/GUI/LocalAircraftCache.hxx index 3efb1f978..9e3ad9af6 100644 --- a/src/GUI/LocalAircraftCache.hxx +++ b/src/GUI/LocalAircraftCache.hxx @@ -29,6 +29,8 @@ #include #include +#include + class QDataStream; struct AircraftItem; class AircraftScanThread; @@ -38,13 +40,17 @@ typedef QSharedPointer AircraftItemPtr; struct AircraftItem { - AircraftItem(); + AircraftItem() = default; AircraftItem(QDir dir, QString filePath); // the file-name without -set.xml suffix QString baseName() const; + QString name() const; + + QString description() const; + void fromDataStream(QDataStream& ds); void toDataStream(QDataStream& ds) const; @@ -53,8 +59,7 @@ struct AircraftItem bool excluded = false; QString path; - QString description; - QString longDescription; + QString authors; // legacy authors data only int ratings[4] = {0, 0, 0, 0}; QString variantOf; @@ -72,7 +77,31 @@ struct AircraftItem QUrl wikipediaUrl; QUrl supportUrl; QVariant status(int variant); + + private: + struct LocalizedStrings { + QString locale; + QMap strings; + }; + + friend QDataStream& operator<<(QDataStream&, const LocalizedStrings&); + friend QDataStream& operator>>(QDataStream&, LocalizedStrings&); + + using LocalizedStringsVec = QVector; + + // store all localized strings. We need this to avoid rebuilding + // the cache when switching languages. + LocalizedStringsVec _localized; + + // the resolved values for our strings, based on QLocale + // if we support dynamic switching of language, this would need to + // be flushed and re-computed + QMap _currentStrings; + + void doLocalizeStrings(); + + void readLocalizedStrings(SGPropertyNode_ptr simNode); }; class LocalAircraftCache : public QObject diff --git a/src/GUI/QmlAircraftInfo.cxx b/src/GUI/QmlAircraftInfo.cxx index 7c07baa14..5a9ab703c 100644 --- a/src/GUI/QmlAircraftInfo.cxx +++ b/src/GUI/QmlAircraftInfo.cxx @@ -342,7 +342,7 @@ quint32 QmlAircraftInfo::numVariants() const QString QmlAircraftInfo::name() const { if (_item) { - return resolveItem()->description; + return resolveItem()->name(); } else if (_package) { return QString::fromStdString(_package->nameForVariant(_variant)); } @@ -353,7 +353,7 @@ QString QmlAircraftInfo::name() const QString QmlAircraftInfo::description() const { if (_item) { - return resolveItem()->longDescription; + return resolveItem()->description(); } else if (_package) { std::string longDesc = _package->getLocalisedProp("description", _variant); return QString::fromStdString(longDesc).simplified(); @@ -773,12 +773,12 @@ QStringList QmlAircraftInfo::variantNames() const { QStringList result; if (_item) { - result.append(_item->description); + result.append(_item->name()); Q_FOREACH(auto v, _item->variants) { - if (v->description.isEmpty()) { + if (v->name().isEmpty()) { qWarning() << Q_FUNC_INFO << "missing description for " << v->path; } - result.append(v->description); + result.append(v->name()); } } else if (_package) { for (quint32 vindex = 0; vindex < _package->variants().size(); ++vindex) { diff --git a/src/GUI/QtLauncher.cxx b/src/GUI/QtLauncher.cxx index d8a29a240..733108832 100644 --- a/src/GUI/QtLauncher.cxx +++ b/src/GUI/QtLauncher.cxx @@ -462,7 +462,7 @@ bool runLauncherDialog() // we will re-do this later, but we want to access translated strings // from within the launcher globals->get_locale()->selectLanguage(lang); - globals->packageRoot()->setLocale(lang); + globals->packageRoot()->setLocale(globals->get_locale()->getPreferredLanguage()); // startup the HTTP system now since packages needs it FGHTTPClient* http = globals->add_new_subsystem();