8#include <QtCore/QStandardPaths>
10#include <QtCore/QJsonArray>
11#include <QtCore/QJsonDocument>
12#include <QtCore/QXmlStreamReader>
14QGC_LOGGING_CATEGORY(ComponentInformationTranslationLog,
"ComponentInformation.ComponentInformationTranslation")
18 : QObject(parent), _cachedFileDownload(cachedFileDownload)
23 const QString& toTranslateJsonFile,
int maxCacheAgeSec,
const QString& componentName)
26 const QString locale = QLocale::system().name();
27 if (locale.startsWith(QLatin1String(
"en"))) {
28 qCDebug(ComponentInformationTranslationLog) <<
"Skipping translation for English locale" << locale <<
"for" << componentName;
33 _toTranslateJsonFile = toTranslateJsonFile;
34 QString url = getUrlFromSummaryJson(summaryJsonFile, locale, componentName);
41 if (!_cachedFileDownload->
download(url, maxCacheAgeSec)) {
42 qCWarning(ComponentInformationTranslationLog) <<
"Metadata translation download failed";
49QString ComponentInformationTranslation::getUrlFromSummaryJson(
const QString &summaryJsonFile,
const QString &locale,
const QString &componentName)
52 QJsonDocument jsonDoc;
55 qCWarning(ComponentInformationTranslationLog) <<
"Metadata translation summary json file open failed for" << componentName <<
":" <<
errorString;
58 QJsonObject jsonObj = jsonDoc.object();
60 QJsonObject localeObj = jsonObj[locale].toObject();
61 if (localeObj.isEmpty()) {
62 qCWarning(ComponentInformationTranslationLog) <<
"Locale" << locale <<
"not found in translation json for" << componentName;
66 QString url = localeObj[
"url"].toString();
68 qCWarning(ComponentInformationTranslationLog) <<
"Locale" << locale <<
"has no url in translation json for" << componentName;
73void ComponentInformationTranslation::onDownloadCompleted(
bool success,
const QString &localFile, QString errorMsg, [[maybe_unused]]
bool fromCache)
77 QString tsFileName = localFile;
78 bool deleteFile =
false;
80 const QString tempPath = QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).absoluteFilePath(
"qgc_translation_file_decompressed.ts");
82 if (tsFileName.isEmpty()) {
83 const QString remoteFile = _cachedFileDownload->
url().toString();
84 errorMsg =
"Decompression of translation file failed: " + remoteFile;
85 }
else if (tsFileName != localFile) {
91 QString translatedJsonFilename;
92 if (errorMsg.isEmpty()) {
94 if (translatedJsonFilename.isEmpty()) {
95 errorMsg =
"Failed to translate json file";
100 QFile(localFile).remove();
108 qCDebug(ComponentInformationTranslationLog) <<
"Translating" << toTranslateJsonFile <<
"using" << tsFile;
112 QJsonDocument jsonDoc;
115 qCWarning(ComponentInformationTranslationLog) <<
"Metadata json file to translate open failed:" <<
errorString;
121 qCWarning(ComponentInformationTranslationLog) <<
"Translation json schema validation failed:" << schemaError;
124 QJsonObject jsonObj = jsonDoc.object();
126 QJsonObject translationObj = jsonObj[
"translation"].toObject();
127 if (translationObj.isEmpty()) {
128 qCWarning(ComponentInformationTranslationLog) <<
"json file does not contain 'translation' object";
134 QHash<QString, QString> translations;
135 QFile xmlFile(tsFile);
136 if (!xmlFile.open(QIODevice::ReadOnly)) {
137 qCWarning(ComponentInformationTranslationLog) <<
"Failed opening TS file";
141 QXmlStreamReader xml(xmlFile.readAll());
143 if (xml.hasError()) {
144 qCWarning(ComponentInformationTranslationLog) <<
"Badly formed TS (XML)" << xml.errorString();
148 bool insideTS =
false;
150 while (!xml.atEnd()) {
151 if (xml.isStartElement()) {
152 QString elementName = xml.name().toString();
154 if (elementName ==
"TS") {
156 }
else if (insideTS && elementName ==
"context") {
160 bool insideMessage =
false;
161 while (!xml.atEnd()) {
163 if (xml.isStartElement()) {
164 if (xml.name().toString() ==
"message") {
165 insideMessage =
true;
166 }
else if (xml.name().toString() ==
"name" && !insideMessage) {
167 name = xml.readElementText();
168 }
else if (xml.name().toString() ==
"translation" && insideMessage) {
169 translation = xml.readElementText();
171 }
else if (xml.isEndElement()) {
172 if (xml.name().toString() ==
"context") {
174 }
else if (xml.name().toString() ==
"message") {
175 insideMessage =
false;
182 if (name !=
"" && translation !=
"") {
183 translations[name] = translation;
187 }
else if (xml.isEndElement()) {
188 QString elementName = xml.name().toString();
190 if (elementName ==
"TS") {
197 if (translations.isEmpty()) {
198 qCWarning(ComponentInformationTranslationLog) <<
"No translations found in TS file";
203 jsonDoc.setObject(translate(translationObj, translations, jsonDoc.object()));
206 QString translatedFileName = QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).absoluteFilePath(
"qgc_translated_metadata.json");
208 QFile translatedFile(translatedFileName);
209 if (!translatedFile.open(QFile::WriteOnly|QFile::Truncate)) {
210 errorString = tr(
"File open failed: file:error %1 %2").arg(translatedFile.fileName()).arg(translatedFile.errorString());
213 translatedFile.write(jsonDoc.toJson());
214 translatedFile.close();
216 qCDebug(ComponentInformationTranslationLog) <<
"JSON file" << toTranslateJsonFile <<
"successfully translated to" << translatedFileName;
217 return translatedFileName;
220QJsonObject ComponentInformationTranslation::translate(
const QJsonObject& translationObj,
221 const QHash<QString, QString>& translations, QJsonObject doc)
223 QJsonObject defs = translationObj[
"$defs"].toObject();
224 if (translationObj.contains(
"items")) {
225 doc = translateItems(
"", defs, translationObj[
"items"].toObject(), translations, doc);
227 if (translationObj.contains(
"$ref")) {
228 doc = translateItems(
"", defs, defs[getRefName(translationObj[
"$ref"].toString())].toObject(), translations, doc);
233QJsonObject ComponentInformationTranslation::translateItems(
const QString& prefix,
const QJsonObject& defs,
234 const QJsonObject& translationObj,
235 const QHash<QString, QString>& translations,
236 QJsonObject jsonData)
238 for (
auto translationItemIter = translationObj.begin(); translationItemIter != translationObj.end(); ++translationItemIter) {
239 QStringList translationKeys;
240 if (translationItemIter.key() ==
"*") {
241 translationKeys = jsonData.keys();
243 translationKeys.append(translationItemIter.key());
245 for (
const auto& jsonItem : translationKeys) {
246 QString nextPrefix = prefix +
'/' + jsonItem;
247 QJsonObject nextTranslationObj = translationItemIter.value().toObject();
248 if (jsonData.contains(jsonItem)) {
249 jsonData[jsonItem] = translateTranslationItems(nextPrefix, defs, nextTranslationObj, translations, jsonData[jsonItem]);
256QString ComponentInformationTranslation::getRefName(
const QString& ref)
262QJsonValue ComponentInformationTranslation::translateTranslationItems(
const QString& prefix,
const QJsonObject& defs,
263 const QJsonObject& translationObj,
264 const QHash<QString, QString>& translations,
267 if (translationObj.contains(
"list")) {
268 QJsonObject translationList = translationObj[
"list"].toObject();
269 QString key = translationList[
"key"].toString();
271 QJsonArray array = jsonData.toArray();
272 for (
const auto& listEntry : array) {
274 if (!key.isEmpty() && listEntry.toObject().contains(key)) {
275 value = listEntry.toObject()[key].toString();
277 value = QString::number(idx);
279 array[idx] = translateTranslationItems(prefix +
'/' + value, defs, translationList, translations, listEntry);
284 if (translationObj.contains(
"translate")) {
285 for (
const auto& translateName : translationObj[
"translate"].toArray()) {
286 QString translateNameStr = translateName.toString();
287 if (jsonData.toObject().contains(translateNameStr)) {
288 if (jsonData[translateNameStr].isString()) {
289 auto lookupIter = translations.find(prefix +
'/' + translateNameStr);
290 if (lookupIter != translations.end()) {
292 QJsonObject obj = jsonData.toObject();
293 obj.insert(translateNameStr, lookupIter.value());
296 }
else if (jsonData[translateNameStr].isArray()) {
297 QJsonArray jsonArray = jsonData[translateNameStr].toArray();
298 for (
int i=0; i < jsonArray.count(); ++i) {
299 auto lookupIter = translations.find(prefix +
'/' + translateNameStr +
'/' + QString::number(i));
300 if (lookupIter != translations.end()) {
301 jsonArray.replace(i, lookupIter.value());
304 QJsonObject obj = jsonData.toObject();
305 obj[translateNameStr] = jsonArray;
312 if (translationObj.contains(
"translate-global")) {
313 for (
const auto& translateName : translationObj[
"translate-global"].toArray()) {
314 QString translateNameStr = translateName.toString();
315 if (jsonData.toObject().contains(translateNameStr)) {
316 if (jsonData[translateNameStr].isString()) {
317 auto lookupIter = translations.find(
"$globals/" + translateNameStr +
"/" + jsonData[translateNameStr].toString());
318 if (lookupIter != translations.end()) {
319 QJsonObject obj = jsonData.toObject();
320 obj.insert(translateNameStr, lookupIter.value());
323 }
else if (jsonData[translateNameStr].isArray()) {
324 QJsonArray jsonArray = jsonData[translateNameStr].toArray();
325 for (
int i=0; i < jsonArray.count(); ++i) {
326 auto lookupIter = translations.find(
"$globals/" + translateNameStr +
'/' + jsonArray[i].toString());
327 if (lookupIter != translations.end()) {
328 jsonArray.replace(i, lookupIter.value());
331 QJsonObject obj = jsonData.toObject();
332 obj[translateNameStr] = jsonArray;
338 if (translationObj.contains(
"items")) {
339 jsonData = translateItems(prefix, defs, translationObj[
"items"].toObject(), translations, jsonData.toObject());
341 if (translationObj.contains(
"$ref")) {
342 jsonData = translateTranslationItems(prefix, defs, defs[getRefName(translationObj[
"$ref"].toString())].toObject(), translations, jsonData);
Cached file download with time-based expiration and fallback support.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Cached file download with time-based expiration.
bool download(const QString &url, int maxCacheAgeSec)
void finished(bool success, const QString &localPath, const QString &errorMessage, bool fromCache)
bool isJsonFile(const QByteArray &bytes, QJsonDocument &jsonDoc, QString &errorString)
Determines whether an in-memory byte buffer contains parseable JSON content.
bool validate(const QJsonDocument &doc, const QString &schemaResourcePath, QString &errorString)
QString decompressIfNeeded(const QString &filePath, const QString &outputPath, bool removeOriginal)