From 960768302a1eb85f9cab67244fbdab2a8353951b Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Wed, 25 Oct 2023 15:32:22 +0200 Subject: [PATCH] QtSupport: Support adding translation file ..for the major build systems. CMake build system will get further improvements. For now this only works for adding ts files to the target after the project had been created with QC's wizard with some translation already added. Qbs and qmake just work. The C++ sources obviously will not change automatically to integrate translation support for the application. Task-number: QTCREATORBUG-29775 Change-Id: I80e4c21156f4bb8d5ef0bd6edf805021a55770ee Reviewed-by: Cristian Adam Reviewed-by: Christian Kandeler --- .../wizards/files/translation/wizard.json | 45 +++++++++ .../cmakeprojectmanager/cmakebuildsystem.cpp | 93 ++++++++++++++++--- .../cmakeprojectmanager/cmakebuildsystem.h | 5 + .../qmakeprojectmanager/qmakeparsernodes.cpp | 4 + .../qtsupport/translationwizardpage.cpp | 33 +++++-- 5 files changed, 159 insertions(+), 21 deletions(-) create mode 100644 share/qtcreator/templates/wizards/files/translation/wizard.json diff --git a/share/qtcreator/templates/wizards/files/translation/wizard.json b/share/qtcreator/templates/wizards/files/translation/wizard.json new file mode 100644 index 00000000000..04c2f145d1f --- /dev/null +++ b/share/qtcreator/templates/wizards/files/translation/wizard.json @@ -0,0 +1,45 @@ +{ + "version": 1, + "supportedProjectTypes": [ ], + "id": "Q.Translation", + "category": "R.Qt", + "trDescription": "Creates a translation file that you can add to a Qt project.", + "trDisplayName": "Qt Translation File", + "trDisplayCategory": "Qt", + "iconText": "ts", + "enabled": "%{JS: value('Plugins').indexOf('QtSupport') >= 0}", + + "options": [ + { + "key": "TargetPath", "value": "%{InitialPath}" + } + ], + + "pages" : [ + { + "trDisplayName": "Location", + "trShortTitle": "Location", + "typeId": "QtTranslation", + "data": { + "singleFile": true + } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators" : [ + { + "typeId": "File", + "data": [ + { + "source": "../../projects/translation.ts", + "target": "%{TsFileName}", + "openInEditor": true + } + ] + } + ] +} diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index 157160c33f6..4e10ec6ecfe 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -219,16 +219,17 @@ bool CMakeBuildSystem::supportsAction(Node *context, ProjectAction action, const return BuildSystem::supportsAction(context, action, node); } +static QString relativeFilePaths(const FilePaths &filePaths, const FilePath &projectDir) +{ + return Utils::transform(filePaths, [projectDir](const FilePath &path) { + return path.canonicalPath().relativePathFrom(projectDir).cleanPath().toString(); + }).join(' '); +}; + static QString newFilesForFunction(const std::string &cmakeFunction, const FilePaths &filePaths, const FilePath &projDir) { - auto relativeFilePaths = [projDir](const FilePaths &filePaths) { - return Utils::transform(filePaths, [projDir](const FilePath &path) { - return path.canonicalPath().relativePathFrom(projDir).cleanPath().toString(); - }); - }; - if (cmakeFunction == "qt_add_qml_module" || cmakeFunction == "qt6_add_qml_module") { FilePaths sourceFiles; FilePaths resourceFiles; @@ -255,16 +256,16 @@ static QString newFilesForFunction(const std::string &cmakeFunction, QStringList result; if (!sourceFiles.isEmpty()) - result << QString("SOURCES %1").arg(relativeFilePaths(sourceFiles).join(" ")); + result << QString("SOURCES %1").arg(relativeFilePaths(sourceFiles, projDir)); if (!resourceFiles.isEmpty()) - result << QString("RESOURCES %1").arg(relativeFilePaths(resourceFiles).join(" ")); + result << QString("RESOURCES %1").arg(relativeFilePaths(resourceFiles, projDir)); if (!qmlFiles.isEmpty()) - result << QString("QML_FILES %1").arg(relativeFilePaths(qmlFiles).join(" ")); + result << QString("QML_FILES %1").arg(relativeFilePaths(qmlFiles, projDir)); return result.join("\n"); } - return relativeFilePaths(filePaths).join(" "); + return relativeFilePaths(filePaths, projDir); } static std::optional cmakeFileForBuildKey(const QString &buildKey, @@ -392,10 +393,58 @@ static expected_str insertSnippetSilently(const FilePath &cmakeFile, return true; } -bool CMakeBuildSystem::addFiles(Node *context, const FilePaths &filePaths, FilePaths *notAdded) +bool CMakeBuildSystem::addTsFiles(Node *context, const FilePaths &filePaths, FilePaths *notAdded) { if (notAdded) - *notAdded = filePaths; + notAdded->append(filePaths); + + if (auto n = dynamic_cast(context)) { + const std::optional cmakeFile = cmakeFileForBuildKey(n->buildKey(), buildTargets()); + if (!cmakeFile.has_value()) + return false; + + const FilePath targetCMakeFile = cmakeFile->targetFilePath; + std::optional cmakeListFile = getUncachedCMakeListFile(targetCMakeFile); + if (!cmakeListFile.has_value()) + return false; + + auto findTs = [](const auto &func) { + if (func.LowerCaseName() != "set") + return false; + std::vector args = func.Arguments(); + return args.size() && args.front().Value == "TS_FILES"; + }; + std::optional function = findFunction(*cmakeListFile, findTs); + if (!function.has_value()) + return false; + + const QString filesToAdd = relativeFilePaths(filePaths, n->filePath().canonicalPath()); + auto lastArgument = function->Arguments().back(); + const int lastArgLength = static_cast(lastArgument.Value.size()) - 1; + SnippetAndLocation snippetLocation{QString("\n%1").arg(filesToAdd), + lastArgument.Line, lastArgument.Column + lastArgLength}; + // Take into consideration the quotes + if (lastArgument.Delim == cmListFileArgument::Quoted) + snippetLocation.column += 2; + + expected_str inserted = insertSnippetSilently(targetCMakeFile, snippetLocation); + if (!inserted) { + qCCritical(cmakeBuildSystemLog) << inserted.error(); + return false; + } + + if (notAdded) + notAdded->removeIf([filePaths](const FilePath &p) { return filePaths.contains(p); }); + return true; + } + return false; +} + +bool CMakeBuildSystem::addSrcFiles(Node *context, const FilePaths &filePaths, FilePaths *notAdded) +{ + if (notAdded) + notAdded->append(filePaths); + if (auto n = dynamic_cast(context)) { const QString targetName = n->buildKey(); const std::optional cmakeFile = cmakeFileForBuildKey(targetName, buildTargets()); @@ -443,10 +492,28 @@ bool CMakeBuildSystem::addFiles(Node *context, const FilePaths &filePaths, FileP } if (notAdded) - notAdded->clear(); + notAdded->removeIf([filePaths](const FilePath &p) { return filePaths.contains(p); }); return true; } + return false; +} + +bool CMakeBuildSystem::addFiles(Node *context, const FilePaths &filePaths, FilePaths *notAdded) +{ + FilePaths tsFiles, srcFiles; + std::tie(tsFiles, srcFiles) = Utils::partition(filePaths, [](const FilePath &fp) { + return Utils::mimeTypeForFile(fp.toString()).name() == Utils::Constants::LINGUIST_MIMETYPE; + }); + bool success = true; + if (!srcFiles.isEmpty()) + success = addSrcFiles(context, srcFiles, notAdded); + + if (!tsFiles.isEmpty()) + success = addTsFiles(context, tsFiles, notAdded) || success; + + if (success) + return true; return BuildSystem::addFiles(context, filePaths, notAdded); } diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h index 6086146223b..dd85a52e228 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h @@ -146,6 +146,11 @@ private: void setError(const QString &message); void setWarning(const QString &message); + bool addSrcFiles(ProjectExplorer::Node *context, const Utils::FilePaths &filePaths, + Utils::FilePaths *); + bool addTsFiles(ProjectExplorer::Node *context, const Utils::FilePaths &filePaths, + Utils::FilePaths *); + // Actually ask for parsing: enum ReparseParameters { REPARSE_DEFAULT = 0, // Nothing special:-) diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp index ac8af4d3cee..d04bd262d0d 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp @@ -1028,6 +1028,9 @@ QString QmakePriFile::varNameForAdding(const QString &mimeType) if (mimeType == QLatin1String(PROFILE_MIMETYPE)) return QLatin1String("SUBDIRS"); + if (mimeType == QLatin1String(Utils::Constants::LINGUIST_MIMETYPE)) + return QLatin1String("TRANSLATIONS"); + return QLatin1String("DISTFILES"); } @@ -1052,6 +1055,7 @@ QStringList QmakePriFile::varNamesForRemoving() vars << QLatin1String("ICON"); vars << QLatin1String("QMAKE_INFO_PLIST"); vars << QLatin1String("STATECHARTS"); + vars << QLatin1String("TRANSLATIONS"); return vars; } diff --git a/src/plugins/qtsupport/translationwizardpage.cpp b/src/plugins/qtsupport/translationwizardpage.cpp index 968701cdee5..79dde408d0a 100644 --- a/src/plugins/qtsupport/translationwizardpage.cpp +++ b/src/plugins/qtsupport/translationwizardpage.cpp @@ -6,6 +6,8 @@ #include "qtsupporttr.h" #include +#include +#include #include #include @@ -36,7 +38,7 @@ class TranslationWizardPage : public WizardPage Q_OBJECT public: - TranslationWizardPage(const QString &enabledExpr); + TranslationWizardPage(const QString &enabledExpr, bool singleFile); private: void initializePage() override; @@ -49,6 +51,7 @@ private: QComboBox m_languageComboBox; QLineEdit m_fileNameLineEdit; const QString m_enabledExpr; + const bool m_isProjectWizard; }; TranslationWizardPageFactory::TranslationWizardPageFactory() @@ -61,17 +64,22 @@ WizardPage *TranslationWizardPageFactory::create(JsonWizard *wizard, Id typeId, { Q_UNUSED(wizard) Q_UNUSED(typeId) - return new TranslationWizardPage(data.toMap().value("enabled").toString()); + return new TranslationWizardPage(data.toMap().value("enabled").toString(), + data.toMap().value("singleFile").toBool()); } -TranslationWizardPage::TranslationWizardPage(const QString &enabledExpr) +TranslationWizardPage::TranslationWizardPage(const QString &enabledExpr, bool singleFile) : m_enabledExpr(enabledExpr) + , m_isProjectWizard(!singleFile) { const auto mainLayout = new QVBoxLayout(this); const auto descriptionLabel = new QLabel( - Tr::tr("If you plan to provide translations for your project's " - "user interface via the Qt Linguist tool, please select a language here. " - "A corresponding translation (.ts) file will be generated for you.")); + singleFile ? Tr::tr("Please select a language for which a corresponding " + "translation (.ts) file will be generated for you.") + : Tr::tr("If you plan to provide translations for your project's " + "user interface via the Qt Linguist tool, please select a " + "language here. A corresponding translation (.ts) file will be " + "generated for you.")); descriptionLabel->setWordWrap(true); mainLayout->addWidget(descriptionLabel); const auto formLayout = new QFormLayout; @@ -116,7 +124,9 @@ void TranslationWizardPage::initializePage() bool TranslationWizardPage::isComplete() const { - return m_languageComboBox.currentIndex() == 0 || !tsBaseName().isEmpty(); + if (m_isProjectWizard) + return m_languageComboBox.currentIndex() == 0 || !tsBaseName().isEmpty(); + return m_languageComboBox.currentIndex() > 0 && !tsBaseName().isEmpty(); } bool TranslationWizardPage::validatePage() @@ -131,7 +141,14 @@ void TranslationWizardPage::updateLineEdit() { m_fileNameLineEdit.setEnabled(m_languageComboBox.currentIndex() != 0); if (m_fileNameLineEdit.isEnabled()) { - const QString projectName = static_cast(wizard())->stringValue("ProjectName"); + auto jsonWizard = static_cast(wizard()); + QString projectName = jsonWizard->stringValue("ProjectName"); + if (!m_isProjectWizard && projectName.isEmpty()) { + if (auto project = ProjectExplorer::ProjectManager::startupProject()) + projectName = FileUtils::fileSystemFriendlyName(project->displayName()); + else + projectName = FilePath::fromUserInput(jsonWizard->stringValue("InitialPath")).baseName(); + } m_fileNameLineEdit.setText(projectName + '_' + m_languageComboBox.currentData().toString()); } else { m_fileNameLineEdit.clear();