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 <cristian.adam@qt.io>
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
Christian Stenger
2023-10-25 15:32:22 +02:00
parent dd9f9c799b
commit 960768302a
5 changed files with 159 additions and 21 deletions

View File

@@ -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
}
]
}
]
}

View File

@@ -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<Link> cmakeFileForBuildKey(const QString &buildKey,
@@ -392,10 +393,58 @@ static expected_str<bool> 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<CMakeTargetNode *>(context)) {
const std::optional<Link> cmakeFile = cmakeFileForBuildKey(n->buildKey(), buildTargets());
if (!cmakeFile.has_value())
return false;
const FilePath targetCMakeFile = cmakeFile->targetFilePath;
std::optional<cmListFile> cmakeListFile = getUncachedCMakeListFile(targetCMakeFile);
if (!cmakeListFile.has_value())
return false;
auto findTs = [](const auto &func) {
if (func.LowerCaseName() != "set")
return false;
std::vector<cmListFileArgument> args = func.Arguments();
return args.size() && args.front().Value == "TS_FILES";
};
std::optional<cmListFileFunction> 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<int>(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<bool> 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<CMakeTargetNode *>(context)) {
const QString targetName = n->buildKey();
const std::optional<Link> 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);
}

View File

@@ -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:-)

View File

@@ -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;
}

View File

@@ -6,6 +6,8 @@
#include "qtsupporttr.h"
#include <projectexplorer/jsonwizard/jsonwizard.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h>
#include <utils/algorithm.h>
#include <utils/filepath.h>
@@ -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
{
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<JsonWizard *>(wizard())->stringValue("ProjectName");
auto jsonWizard = static_cast<JsonWizard *>(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();