forked from qt-creator/qt-creator
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:
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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:-)
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
|
Reference in New Issue
Block a user