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();