From a0e6373fd62b623267731b725b3bbf661782a04f Mon Sep 17 00:00:00 2001 From: Victor Tran Date: Tue, 2 Mar 2021 01:12:16 +1100 Subject: [PATCH] Add tltrdesktop utility --- lib/prifiles/buildmaster.pri | 3 + lib/prifiles/gentranslations.pri | 3 +- lib/prifiles/processdesktoptranslations.pri | 33 +++++ the-libs.pro | 4 + tltrdesktop/desktopfile.cpp | 102 +++++++++++++++ tltrdesktop/desktopfile.h | 46 +++++++ tltrdesktop/jsonfile.cpp | 80 ++++++++++++ tltrdesktop/jsonfile.h | 45 +++++++ tltrdesktop/main.cpp | 136 ++++++++++++++++++++ tltrdesktop/tltrdesktop.pro | 22 ++++ 10 files changed, 473 insertions(+), 1 deletion(-) create mode 100644 lib/prifiles/processdesktoptranslations.pri create mode 100644 tltrdesktop/desktopfile.cpp create mode 100644 tltrdesktop/desktopfile.h create mode 100644 tltrdesktop/jsonfile.cpp create mode 100644 tltrdesktop/jsonfile.h create mode 100644 tltrdesktop/main.cpp create mode 100644 tltrdesktop/tltrdesktop.pro diff --git a/lib/prifiles/buildmaster.pri b/lib/prifiles/buildmaster.pri index 4fbb73e..30b64c0 100644 --- a/lib/prifiles/buildmaster.pri +++ b/lib/prifiles/buildmaster.pri @@ -6,3 +6,6 @@ include(installtranslations.pri) # Check if this is a Blueprint build include(checkblueprint.pri) + +# Generate .desktop translations +include(processdesktoptranslations.pri) diff --git a/lib/prifiles/gentranslations.pri b/lib/prifiles/gentranslations.pri index afc5ded..58c4d12 100644 --- a/lib/prifiles/gentranslations.pri +++ b/lib/prifiles/gentranslations.pri @@ -29,4 +29,5 @@ isEmpty(SKIP_GENTRANSLATION) { } DISTFILES += \ - $$PWD/checkblueprint.pri + $$PWD/checkblueprint.pri \ + $$PWD/processdesktoptranslations.pri diff --git a/lib/prifiles/processdesktoptranslations.pri b/lib/prifiles/processdesktoptranslations.pri new file mode 100644 index 0000000..a31e849 --- /dev/null +++ b/lib/prifiles/processdesktoptranslations.pri @@ -0,0 +1,33 @@ +unix:!macx { + desktopgen.output = ${QMAKE_FILE_BASE}.desktop + desktopgen.commands = tltrdesktop --desktop-generate --desktop-template ${QMAKE_FILE_NAME} --json-directory $$_PRO_FILE_PWD_/translations/desktop/${QMAKE_FILE_NAME}/ --desktop-output ${QMAKE_FILE_OUT} + desktopgen.input = DESKTOP_FILE + desktopgen.CONFIG = no_link target_predeps + desktopgen.variable_out = DESKTOP_FILE_GENERATED + + desktopgenbp.output = ${QMAKE_FILE_BASE}.desktop + desktopgenbp.commands = tltrdesktop --desktop-generate --desktop-template ${QMAKE_FILE_NAME} --json-directory $$_PRO_FILE_PWD_/translations/desktop/${QMAKE_FILE_NAME}/ --desktop-output ${QMAKE_FILE_OUT} + desktopgenbp.input = DESKTOP_FILE_BLUEPRINT + desktopgenbp.CONFIG = no_link target_predeps + desktopgenbp.variable_out = DESKTOP_FILE_BLUEPRINT_GENERATED + + QMAKE_EXTRA_COMPILERS = desktopgen desktopgenbp + + !isEmpty(DESKTOP_FILE):!blueprint { + desktop.files = DESKTOP_FILE_GENERATED + desktop.path = /usr/share/applications + desktop.extra = $$QMAKE_COPY $${OUT_PWD}/$$DESKTOP_FILE $(INSTALL_ROOT)/usr/share/applications + INSTALLS += desktop + + message(Will install $$DESKTOP_FILE as the desktop file) + } + + !isEmpty(DESKTOP_FILE_BLUEPRINT):blueprint { + desktop.files = DESKTOP_FILE_GENERATED_BLUEPRINT + desktop.path = /usr/share/applications + desktop.extra = $$QMAKE_COPY $${OUT_PWD}/$$DESKTOP_FILE_BLUEPRINT $(INSTALL_ROOT)/usr/share/applications + INSTALLS += desktop + + message(Will install $$DESKTOP_FILE_BLUEPRINT as the desktop file) + } +} diff --git a/the-libs.pro b/the-libs.pro index 8fa8323..b190c41 100644 --- a/the-libs.pro +++ b/the-libs.pro @@ -7,6 +7,10 @@ unittest.depends = library SUBDIRS += library +unix:!macx { + SUBDIRS += tltrdesktop +} + #!android { # unittest #} diff --git a/tltrdesktop/desktopfile.cpp b/tltrdesktop/desktopfile.cpp new file mode 100644 index 0000000..335da03 --- /dev/null +++ b/tltrdesktop/desktopfile.cpp @@ -0,0 +1,102 @@ +/**************************************** + * + * INSERT-PROJECT-NAME-HERE - INSERT-GENERIC-NAME-HERE + * Copyright (C) 2021 Victor Tran + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * *************************************/ +#include "desktopfile.h" + +#include +#include + +struct DesktopFilePrivate { + QStringList lines; + QStringList outputLines; + int currentLine = -1; + + bool lineWritten = true; + + QString currentLineText(); +}; + +DesktopFile::DesktopFile(QString file) { + d = new DesktopFilePrivate(); + + QFile f(file); + f.open(QFile::ReadOnly); + d->lines = QString(f.readAll()).split('\n'); +} + +DesktopFile::~DesktopFile() { + delete d; +} + +bool DesktopFile::nextLine() { + if (!d->lineWritten) { + //Copy this line verbatim to the output if it's not a translator's comment + if (!d->currentLineText().startsWith("#T:")) d->outputLines.append(d->currentLineText()); + } + + d->currentLine++; + d->lineWritten = false; + return d->lines.count() > d->currentLine; +} + +bool DesktopFile::isKeyValue() { + return d->currentLineText().contains("="); +} + +QString DesktopFile::key() { + QString key = d->currentLineText().left(d->currentLineText().indexOf("=")); + if (key.endsWith("[]")) key.chop(2); + return key; +} + +QString DesktopFile::value() { + return d->currentLineText().mid(d->currentLineText().indexOf("=") + 1); +} + +QString DesktopFile::comment() { + if (d->currentLine == 0) return ""; + QString previousLine = d->lines.value(d->currentLine - 1); + if (!previousLine.startsWith("#T:")) return ""; + return previousLine.mid(3); +} + +bool DesktopFile::isLineTranslatable() { + if (!isKeyValue()) return false; + return d->currentLineText().left(d->currentLineText().indexOf("=")).endsWith("[]"); +} + +void DesktopFile::insert(QString key, QString value, QString locale) { + QString line = QStringLiteral("%1%2=%3").arg(key); + if (locale.isEmpty()) { + line = line.arg(""); + } else { + line = line.arg(QStringLiteral("[%1]").arg(locale)); + } + line = line.arg(value); + d->outputLines.append(line); + d->lineWritten = true; +} + +QString DesktopFile::outputText() { + return d->outputLines.join("\n"); +} + +QString DesktopFilePrivate::currentLineText() { + return lines.at(currentLine); +} diff --git a/tltrdesktop/desktopfile.h b/tltrdesktop/desktopfile.h new file mode 100644 index 0000000..0419a9c --- /dev/null +++ b/tltrdesktop/desktopfile.h @@ -0,0 +1,46 @@ +/**************************************** + * + * INSERT-PROJECT-NAME-HERE - INSERT-GENERIC-NAME-HERE + * Copyright (C) 2021 Victor Tran + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * *************************************/ +#ifndef DESKTOPFILE_H +#define DESKTOPFILE_H + +#include + +struct DesktopFilePrivate; +class DesktopFile { + public: + DesktopFile(QString file); + ~DesktopFile(); + + bool nextLine(); + bool isKeyValue(); + QString key(); + QString value(); + QString comment(); + bool isLineTranslatable(); + + void insert(QString key, QString value, QString locale = ""); + + QString outputText(); + + private: + DesktopFilePrivate* d; +}; + +#endif // DESKTOPFILE_H diff --git a/tltrdesktop/jsonfile.cpp b/tltrdesktop/jsonfile.cpp new file mode 100644 index 0000000..3b56b11 --- /dev/null +++ b/tltrdesktop/jsonfile.cpp @@ -0,0 +1,80 @@ +/**************************************** + * + * INSERT-PROJECT-NAME-HERE - INSERT-GENERIC-NAME-HERE + * Copyright (C) 2021 Victor Tran + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * *************************************/ +#include "jsonfile.h" + +#include +#include +#include + +struct JsonFilePrivate { + uint refcount = 0; + QJsonObject rootObject; +}; + +JsonFile::JsonFile() { + d = new JsonFilePrivate(); + d->refcount++; +} + +JsonFile::JsonFile(QString file) { + d = new JsonFilePrivate(); + d->refcount++; + + QFile f(file); + f.open(QFile::ReadOnly); + d->rootObject = QJsonDocument::fromJson(f.readAll()).object(); +} + +JsonFile::JsonFile(const JsonFile& other) { + this->d = other.d; + d->refcount++; +} + +JsonFile::~JsonFile() { + d->refcount--; + if (d->refcount == 0) delete d; +} + +void JsonFile::insertString(QString key, QString message, QString description) { + QJsonObject stringObject; + stringObject.insert("message", message); + if (!description.isEmpty()) stringObject.insert("description", description); + d->rootObject.insert(key, stringObject); +} + +QString JsonFile::message(QString key) const { + QJsonValue stringValue = d->rootObject.value(key); + if (!stringValue.isObject()) return ""; + + return stringValue.toObject().value("message").toString(); +} + +QByteArray JsonFile::output() { + return QJsonDocument(d->rootObject).toJson(); +} + +JsonFile& JsonFile::operator =(const JsonFile& other) { + d->refcount--; + if (d->refcount == 0) delete d; + + d = other.d; + d->refcount++; + return *this; +} diff --git a/tltrdesktop/jsonfile.h b/tltrdesktop/jsonfile.h new file mode 100644 index 0000000..2396ab2 --- /dev/null +++ b/tltrdesktop/jsonfile.h @@ -0,0 +1,45 @@ +/**************************************** + * + * INSERT-PROJECT-NAME-HERE - INSERT-GENERIC-NAME-HERE + * Copyright (C) 2021 Victor Tran + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * *************************************/ +#ifndef JSONFILE_H +#define JSONFILE_H + +#include + +struct JsonFilePrivate; + +class JsonFile { + public: + JsonFile(); + JsonFile(QString file); + JsonFile(const JsonFile& other); + ~JsonFile(); + + void insertString(QString key, QString message, QString description = ""); + QString message(QString key) const; + + QByteArray output(); + + JsonFile& operator=(const JsonFile& other); + + private: + JsonFilePrivate* d; +}; + +#endif // JSONFILE_H diff --git a/tltrdesktop/main.cpp b/tltrdesktop/main.cpp new file mode 100644 index 0000000..75a0d57 --- /dev/null +++ b/tltrdesktop/main.cpp @@ -0,0 +1,136 @@ +/**************************************** + * + * INSERT-PROJECT-NAME-HERE - INSERT-GENERIC-NAME-HERE + * Copyright (C) 2021 Victor Tran + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * *************************************/ +#include +#include +#include +#include + +#include "desktopfile.h" +#include "jsonfile.h" + +void genJsonFile(QString desktopFile, QDir outputDirectory) { + QTextStream output(stdout); + + JsonFile file; + DesktopFile desktop(desktopFile); + while (desktop.nextLine()) { + if (desktop.isLineTranslatable()) { + //Export this line to the translation file + file.insertString(desktop.key(), desktop.value(), desktop.comment()); + } + } + + //Write the output to en.json + QString outputFile = outputDirectory.absoluteFilePath("en.json"); + output << "Generating file " << outputFile << "...\n"; + + QFile f(outputFile); + f.open(QFile::WriteOnly); + f.write(file.output()); + f.close(); +} + +void genDesktopFile(QString templateFile, QDir jsonDirectory, QString outputFile) { + QTextStream output(stdout); + QMap jsonFiles; + for (const QFileInfo& file : jsonDirectory.entryInfoList({"*.json"}, QDir::Files)) { + JsonFile jsonFile(file.absoluteFilePath()); + jsonFiles.insert(file.baseName(), jsonFile); + } + + DesktopFile desktop(templateFile); + while (desktop.nextLine()) { + if (desktop.isLineTranslatable()) { + //Insert the translations into the desktop file + //First, insert the translation without a locale + desktop.insert(desktop.key(), desktop.value()); + + for (const QString& locale : jsonFiles.keys()) { + QString value = jsonFiles.value(locale).message(desktop.key()); + desktop.insert(desktop.key(), value, locale); + } + } + } + + //Output to the output file + output << "Generating file " << outputFile << "...\n"; + + QFile f(outputFile); + f.open(QFile::WriteOnly); + f.write(desktop.outputText().toUtf8()); + f.close(); +} + +int main(int argc, char* argv[]) { + QCoreApplication a(argc, argv); + + QCommandLineParser parser; + parser.addOption({{"j", "json-generate"}, "Generate JSON Translation files from .desktop files"}); + parser.addOption({{"k", "desktop-generate"}, "Generate .desktop files from JSON Translation files"}); + parser.addOption({{"d", "json-directory"}, "Directory of JSON Translation files", "json-directory"}); + parser.addOption({{"t", "desktop-template"}, ".desktop template file", "desktop template"}); + parser.addOption({{"o", "desktop-output"}, ".desktop output file", "desktop output"}); + QCommandLineOption helpOption = parser.addHelpOption(); + parser.parse(a.arguments()); + + QTextStream output(stdout); + QTextStream eoutput(stderr); + if (parser.isSet(helpOption)) { + output << parser.helpText() << "\n"; + return 0; + } + + bool genJson = parser.isSet("json-generate"); + bool genDesktop = parser.isSet("desktop-generate"); + if (!genJson && !genDesktop) { + eoutput << "error: At least one of --json-generate or --desktop-generate must be specified.\n"; + return 1; + } + + if (!parser.isSet("desktop-template")) { + eoutput << "error: A desktop template file must be specified with --desktop-template.\n"; + return 1; + } + + if (!parser.isSet("json-directory")) { + eoutput << "error: A JSON output directory must be specified with --json-directory.\n"; + return 1; + } + + if (!parser.isSet("desktop-output") && genDesktop) { + eoutput << "error: A .desktop output file must be specified with --desktop-output.\n"; + return 1; + } + + QString desktopTemplate = parser.value("desktop-template"); + QDir outputDirectory(parser.value("json-directory")); + if (!outputDirectory.exists()) outputDirectory.mkpath("."); + + if (genJson) { + //Generate the en JSON file from a .desktop file + genJsonFile(desktopTemplate, outputDirectory); + } + if (genDesktop) { + //Generate the final .desktop file from JSON files + genDesktopFile(desktopTemplate, outputDirectory, parser.value("desktop-output")); + } + + return 0; +} diff --git a/tltrdesktop/tltrdesktop.pro b/tltrdesktop/tltrdesktop.pro new file mode 100644 index 0000000..ca2f549 --- /dev/null +++ b/tltrdesktop/tltrdesktop.pro @@ -0,0 +1,22 @@ +QT -= gui + +CONFIG += c++11 console +CONFIG -= app_bundle + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + desktopfile.cpp \ + jsonfile.cpp \ + main.cpp + +unix:!macx { + target.path = /usr/bin + INSTALLS += target +} + +HEADERS += \ + desktopfile.h \ + jsonfile.h