12#include <QJsonDocument>
15#include <QStandardPaths>
22 QVector<ExerciseTourResourceEntry> entries;
26 qWarning() <<
"ExerciseTourResources::scan: directory not found:" << directory;
30 const QStringList jsonFiles = dir.entryList({QStringLiteral(
"*.json")}, QDir::Files);
31 for (
const QString &fileName : jsonFiles) {
32 const QString path = directory + QLatin1Char(
'/') + fileName;
35 if (!file.open(QIODevice::ReadOnly)) {
36 qWarning() <<
"ExerciseTourResources::scan: failed to open" << path;
40 const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
41 if (doc.isNull() || !doc.isObject()) {
42 qWarning() <<
"ExerciseTourResources::scan: malformed JSON in" << path;
46 const QJsonObject root = doc.object();
47 const QString
id = root.value(QStringLiteral(
"id")).toString();
48 const QString title = root.value(QStringLiteral(
"title")).toString();
49 if (
id.isEmpty() || title.isEmpty()) {
50 qWarning() <<
"ExerciseTourResources::scan: missing id/title in" << path;
54 entries.append({path, id, title, root.value(QStringLiteral(
"description")).toString()});
65 const QVector<ExerciseTourResourceEntry> &additional)
69 return existing.id == entry.id;
72 qWarning() <<
"ExerciseTourResources::mergeUnique: skipping duplicate id" << entry.id <<
"at" << entry.path;
87 const QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char(
'/') + category;
95QString ExerciseTourResources::documentsFallbackDir(
const QString &category)
97 return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + QStringLiteral(
"/wiRedPanda/") + category;
102 for (
const QString &candidate : candidates) {
103 if (candidate.isEmpty()) {
107 if ((dir.exists() || dir.mkpath(candidate)) && QFileInfo(candidate).isWritable()) {
112 QDir dir(documentsFallback);
113 if ((dir.exists() || dir.mkpath(documentsFallback)) && QFileInfo(documentsFallback).isWritable()) {
114 return documentsFallback;
127 QVector<ExerciseTourResourceEntry> entries =
scan(QStringLiteral(
":/") + category);
132 if (!installDir.isEmpty()) {
136 const QString docsDir = documentsFallbackDir(category);
137 if (QDir(docsDir).exists()) {
145 const QString &fallbackEnglish)
147 QFile file(catalogPath);
148 if (!file.open(QIODevice::ReadOnly)) {
149 return fallbackEnglish;
152 const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
153 if (!doc.isObject()) {
154 return fallbackEnglish;
157 QJsonValue node = doc.object();
158 const QStringList parts = key.split(QLatin1Char(
'.'));
159 for (
const QString &part : parts) {
160 if (!node.isObject()) {
161 return fallbackEnglish;
163 node = node.toObject().value(part);
166 return (node.isString() && !node.toString().isEmpty()) ? node.toString() : fallbackEnglish;
172 if (lang.isEmpty() || lang == QLatin1String(
"en")) {
173 return fallbackEnglish;
176 return translateFromCatalog(QStringLiteral(
":/i18n/ExerciseTour/%1.json").arg(lang), key, fallbackEnglish);
Discovery and translation lookup for Exercise/Tour step content.
Per-platform resolution of install-relative content directories.
Typed wrappers around QSettings for all application preferences.
static QVector< ExerciseTourResourceEntry > discover(const QString &category)
static QVector< ExerciseTourResourceEntry > mergeUnique(QVector< ExerciseTourResourceEntry > base, const QVector< ExerciseTourResourceEntry > &additional)
static QString preferredContentDir(const QString &category)
static QVector< ExerciseTourResourceEntry > scan(const QString &directory)
static QString translateFromCatalog(const QString &catalogPath, const QString &key, const QString &fallbackEnglish)
static QString managedContentDir(const QString &category)
static QString resolveWritableDir(const QStringList &candidates, const QString &documentsFallback)
static QString translate(const QString &key, const QString &fallbackEnglish)
static QString resolve(const QString &category)
static QStringList candidates(const QString &category)
static QString language()