7#include <QtCore/QStandardPaths>
9#include <QtCore/QJsonArray>
10#include <QtCore/QJsonDocument>
11#include <QtCore/QXmlStreamReader>
13QGC_LOGGING_CATEGORY(ComponentInformationTranslationLog,
"ComponentInformation.ComponentInformationTranslation")
17 : QObject(parent), _cachedFileDownload(cachedFileDownload)
22 const QString& toTranslateJsonFile,
int maxCacheAgeSec,
const QString& componentName)
25 const QString locale = QLocale::system().name();
26 if (locale.startsWith(QLatin1String(
"en"))) {
27 qCDebug(ComponentInformationTranslationLog) <<
"Skipping translation for English locale" << locale <<
"for" << componentName;
32 _toTranslateJsonFile = toTranslateJsonFile;
33 QString url = getUrlFromSummaryJson(summaryJsonFile, locale, componentName);
40 if (!_cachedFileDownload->
download(url, maxCacheAgeSec)) {
41 qCWarning(ComponentInformationTranslationLog) <<
"Metadata translation download failed";
48QString ComponentInformationTranslation::getUrlFromSummaryJson(
const QString &summaryJsonFile,
const QString &locale,
const QString &componentName)
51 QJsonDocument jsonDoc;
54 qCWarning(ComponentInformationTranslationLog) <<
"Metadata translation summary json file open failed for" << componentName <<
":" <<
errorString;
57 QJsonObject jsonObj = jsonDoc.object();
59 QJsonObject localeObj = jsonObj[locale].toObject();
60 if (localeObj.isEmpty()) {
61 qCWarning(ComponentInformationTranslationLog) <<
"Locale" << locale <<
"not found in translation json for" << componentName;
65 QString url = localeObj[
"url"].toString();
67 qCWarning(ComponentInformationTranslationLog) <<
"Locale" << locale <<
"has no url in translation json for" << componentName;
72void ComponentInformationTranslation::onDownloadCompleted(
bool success,
const QString &localFile, QString errorMsg, [[maybe_unused]]
bool fromCache)
76 QString tsFileName = localFile;
77 bool deleteFile =
false;
79 const QString tempPath = QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).absoluteFilePath(
"qgc_translation_file_decompressed.ts");
81 if (tsFileName.isEmpty()) {
82 const QString remoteFile = _cachedFileDownload->url().toString();
83 errorMsg =
"Decompression of translation file failed: " + remoteFile;
84 }
else if (tsFileName != localFile) {
90 QString translatedJsonFilename;
91 if (errorMsg.isEmpty()) {
93 if (translatedJsonFilename.isEmpty()) {
94 errorMsg =
"Failed to translate json file";
99 QFile(localFile).remove();
107 qCDebug(ComponentInformationTranslationLog) <<
"Translating" << toTranslateJsonFile <<
"using" << tsFile;
111 QJsonDocument jsonDoc;
114 qCWarning(ComponentInformationTranslationLog) <<
"Metadata json file to translate open failed:" <<
errorString;
117 QJsonObject jsonObj = jsonDoc.object();
119 QJsonObject translationObj = jsonObj[
"translation"].toObject();
120 if (translationObj.isEmpty()) {
121 qCWarning(ComponentInformationTranslationLog) <<
"json file does not contain 'translation' object";
127 QHash<QString, QString> translations;
128 QFile xmlFile(tsFile);
129 if (!xmlFile.open(QIODevice::ReadOnly)) {
130 qCWarning(ComponentInformationTranslationLog) <<
"Failed opening TS file";
134 QXmlStreamReader xml(xmlFile.readAll());
136 if (xml.hasError()) {
137 qCWarning(ComponentInformationTranslationLog) <<
"Badly formed TS (XML)" << xml.errorString();
141 bool insideTS =
false;
143 while (!xml.atEnd()) {
144 if (xml.isStartElement()) {
145 QString elementName = xml.name().toString();
147 if (elementName ==
"TS") {
149 }
else if (insideTS && elementName ==
"context") {
153 bool insideMessage =
false;
154 while (!xml.atEnd()) {
156 if (xml.isStartElement()) {
157 if (xml.name().toString() ==
"message") {
158 insideMessage =
true;
159 }
else if (xml.name().toString() ==
"name" && !insideMessage) {
160 name = xml.readElementText();
161 }
else if (xml.name().toString() ==
"translation" && insideMessage) {
162 translation = xml.readElementText();
164 }
else if (xml.isEndElement()) {
165 if (xml.name().toString() ==
"context") {
167 }
else if (xml.name().toString() ==
"message") {
168 insideMessage =
false;
175 if (name !=
"" && translation !=
"") {
176 translations[name] = translation;
180 }
else if (xml.isEndElement()) {
181 QString elementName = xml.name().toString();
183 if (elementName ==
"TS") {
190 if (translations.isEmpty()) {
191 qCWarning(ComponentInformationTranslationLog) <<
"No translations found in TS file";
196 jsonDoc.setObject(translate(translationObj, translations, jsonDoc.object()));
199 QString translatedFileName = QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).absoluteFilePath(
"qgc_translated_metadata.json");
201 QFile translatedFile(translatedFileName);
202 if (!translatedFile.open(QFile::WriteOnly|QFile::Truncate)) {
203 errorString = tr(
"File open failed: file:error %1 %2").arg(translatedFile.fileName()).arg(translatedFile.errorString());
206 translatedFile.write(jsonDoc.toJson());
207 translatedFile.close();
209 qCDebug(ComponentInformationTranslationLog) <<
"JSON file" << toTranslateJsonFile <<
"successfully translated to" << translatedFileName;
210 return translatedFileName;
213QJsonObject ComponentInformationTranslation::translate(
const QJsonObject& translationObj,
214 const QHash<QString, QString>& translations, QJsonObject doc)
216 QJsonObject defs = translationObj[
"$defs"].toObject();
217 if (translationObj.contains(
"items")) {
218 doc = translateItems(
"", defs, translationObj[
"items"].toObject(), translations, doc);
220 if (translationObj.contains(
"$ref")) {
221 doc = translateItems(
"", defs, defs[getRefName(translationObj[
"$ref"].toString())].toObject(), translations, doc);
226QJsonObject ComponentInformationTranslation::translateItems(
const QString& prefix,
const QJsonObject& defs,
227 const QJsonObject& translationObj,
228 const QHash<QString, QString>& translations,
229 QJsonObject jsonData)
231 for (
auto translationItemIter = translationObj.begin(); translationItemIter != translationObj.end(); ++translationItemIter) {
232 QStringList translationKeys;
233 if (translationItemIter.key() ==
"*") {
234 translationKeys = jsonData.keys();
236 translationKeys.append(translationItemIter.key());
238 for (
const auto& jsonItem : translationKeys) {
239 QString nextPrefix = prefix +
'/' + jsonItem;
240 QJsonObject nextTranslationObj = translationItemIter.value().toObject();
241 if (jsonData.contains(jsonItem)) {
242 jsonData[jsonItem] = translateTranslationItems(nextPrefix, defs, nextTranslationObj, translations, jsonData[jsonItem]);
249QString ComponentInformationTranslation::getRefName(
const QString& ref)
255QJsonValue ComponentInformationTranslation::translateTranslationItems(
const QString& prefix,
const QJsonObject& defs,
256 const QJsonObject& translationObj,
257 const QHash<QString, QString>& translations,
260 if (translationObj.contains(
"list")) {
261 QJsonObject translationList = translationObj[
"list"].toObject();
262 QString key = translationList[
"key"].toString();
264 QJsonArray array = jsonData.toArray();
265 for (
const auto& listEntry : array) {
267 if (!key.isEmpty() && listEntry.toObject().contains(key)) {
268 value = listEntry.toObject()[key].toString();
270 value = QString::number(idx);
272 array[idx] = translateTranslationItems(prefix +
'/' + value, defs, translationList, translations, listEntry);
277 if (translationObj.contains(
"translate")) {
278 for (
const auto& translateName : translationObj[
"translate"].toArray()) {
279 QString translateNameStr = translateName.toString();
280 if (jsonData.toObject().contains(translateNameStr)) {
281 if (jsonData[translateNameStr].isString()) {
282 auto lookupIter = translations.find(prefix +
'/' + translateNameStr);
283 if (lookupIter != translations.end()) {
285 QJsonObject obj = jsonData.toObject();
286 obj.insert(translateNameStr, lookupIter.value());
289 }
else if (jsonData[translateNameStr].isArray()) {
290 QJsonArray jsonArray = jsonData[translateNameStr].toArray();
291 for (
int i=0; i < jsonArray.count(); ++i) {
292 auto lookupIter = translations.find(prefix +
'/' + translateNameStr +
'/' + QString::number(i));
293 if (lookupIter != translations.end()) {
294 jsonArray.replace(i, lookupIter.value());
297 QJsonObject obj = jsonData.toObject();
298 obj[translateNameStr] = jsonArray;
305 if (translationObj.contains(
"translate-global")) {
306 for (
const auto& translateName : translationObj[
"translate-global"].toArray()) {
307 QString translateNameStr = translateName.toString();
308 if (jsonData.toObject().contains(translateNameStr)) {
309 if (jsonData[translateNameStr].isString()) {
310 auto lookupIter = translations.find(
"$globals/" + translateNameStr +
"/" + jsonData[translateNameStr].toString());
311 if (lookupIter != translations.end()) {
312 QJsonObject obj = jsonData.toObject();
313 obj.insert(translateNameStr, lookupIter.value());
316 }
else if (jsonData[translateNameStr].isArray()) {
317 QJsonArray jsonArray = jsonData[translateNameStr].toArray();
318 for (
int i=0; i < jsonArray.count(); ++i) {
319 auto lookupIter = translations.find(
"$globals/" + translateNameStr +
'/' + jsonArray[i].toString());
320 if (lookupIter != translations.end()) {
321 jsonArray.replace(i, lookupIter.value());
324 QJsonObject obj = jsonData.toObject();
325 obj[translateNameStr] = jsonArray;
331 if (translationObj.contains(
"items")) {
332 jsonData = translateItems(prefix, defs, translationObj[
"items"].toObject(), translations, jsonData.toObject());
334 if (translationObj.contains(
"$ref")) {
335 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)
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.
QString decompressIfNeeded(const QString &filePath, const QString &outputPath, bool removeOriginal)