Revision control
Copy as Markdown
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "filetransformer.h"
#include "fileverifier.h"
#include "utils.h"
#include "recorddirectory.h"
#include <translator.h>
#include <trlib/trparser.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QCommandLineParser>
#include <QtCore/QCommandLineOption>
#include <QDirIterator>
#include <iostream>
QT_USE_NAMESPACE
using namespace Qt::StringLiterals;
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationName("ltext2id"_L1);
QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
QCommandLineParser parser;
parser.setApplicationDescription(
"ltext2id is part of the Qt Linguist toolchain. It transforms\n"
"a project that uses text-based translations into using id-based\n"
"translations. It extracts the source and translation files (TS\n"
"files) from the given project root by traversing the\n"
"directories recursively. The code will be updated to use\n"
"id-based translations. Also, the TS files are updated accordingly.\n"
"Please make a backup of your code base before running ltext2id.\n"
"For best results, include the sources along with TS files to get\n"
"consistent transformation across sources and TS files."_L1);
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption noAutoIdOption(
"no-auto-id"_L1,
"Do not auto generate IDs when missing meta string IDs. Ignores the\n"
"translation functions without meta IDs."_L1);
parser.addOption(noAutoIdOption);
QCommandLineOption noLabelsOption(
"no-labels"_L1,
"Do not generate labels for id-based translations. The default\n"
"behavior is to use message contexts as labels, to preserve\n"
"the same visualization form in Linguist."_L1);
parser.addOption(noLabelsOption);
QCommandLineOption onlyMetaIdOption(
"only-meta-id"_L1,
"Only generate ID suggestions in form of meta strings. Does not\n"
"perform the actual transformation into ID based."_L1);
parser.addOption(onlyMetaIdOption);
QCommandLineOption quietOption("quiet"_L1, "Do not print progress output"_L1);
parser.addOption(quietOption);
QCommandLineOption sortMessagesOption(
"sort-messages"_L1, "Sort messages in a context alphabetically in TS files."_L1);
parser.addOption(sortMessagesOption);
QCommandLineOption sourceUtf16Option(
"source-utf16"_L1,
"If the encoding of the source files is UTF16 (default is UTF-8)"_L1);
parser.addOption(sourceUtf16Option);
parser.addPositionalArgument("project-root"_L1,
"Path to the project root directory or file"_L1);
parser.process(app);
const QStringList positionalArgs = parser.positionalArguments();
if (positionalArgs.isEmpty()) {
std::cerr << "ltext2id error: a project root must be specified\n";
parser.showHelp(1);
}
const QString projectRoot = positionalArgs.first();
const bool labels = !parser.isSet(noLabelsOption);
const bool sortMessages = parser.isSet(sortMessagesOption);
const bool utf16 = parser.isSet(sourceUtf16Option);
const bool quiet = parser.isSet(quietOption);
const bool autoId = !parser.isSet(noAutoIdOption);
const bool onlyId = parser.isSet(onlyMetaIdOption);
QFileInfo rootInfo(projectRoot);
if (!rootInfo.exists()) {
std::cerr << "ltext2id error: invalid project root\n";
parser.showHelp(1);
}
QStringList sources;
QStringList cSources;
QStringList translations;
auto routeFile = [&sources, &cSources, &translations](QString file) {
file = QFileInfo(file).absoluteFilePath();
if (QString ext = QFileInfo(file).suffix(); FileTransformer::cppExtensions.contains(ext)) {
cSources << file;
sources << std::move(file);
} else if (FileTransformer::otherExtensions.contains(ext))
sources << std::move(file);
else if (ext == "ts" || ext == "TS")
translations << std::move(file);
};
if (rootInfo.isDir()) {
QDirIterator it(projectRoot, QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories);
while (it.hasNext())
routeFile(it.next());
} else {
routeFile(projectRoot);
}
ConversionData cd;
Translator tor;
cd.m_projectRoots = { std::move(projectRoot) };
cd.m_allCSources = Utils::getIncludeOptions(rootInfo, cSources);
cd.m_sourceIsUtf16 = utf16;
processSources(tor, sources, cd);
RecordDirectory records;
for (const TranslatorMessage &msg : tor.messages())
if (msg.context().isEmpty())
records.recordExistingId(msg.id());
for (const TranslatorMessage &msg : tor.messages())
if (!msg.context().isEmpty() && (autoId || !msg.extra(meta_id_key).isEmpty()))
records.recordMessage(msg);
bool fail = false;
FileTransformer transformer(records, labels, quiet);
FileVerifier verifier(records, quiet);
if (onlyId) {
transformer.generateMetaIds();
fail = !transformer.updateTsFiles(translations);
} else {
transformer.transformUi();
transformer.transformSources();
fail = !transformer.transformTsFiles(translations, sortMessages);
verifier.verifySources(sources, cd);
fail |= !records.errors().empty();
if (fail)
Utils::printErr("ltext2id failed to migrate some translations. Please look for "
"the comments from ltext2id annotated by '//ltext2id error:' in "
"your source code to find the failed transformations and fix them "
"manually as instructed."_L1);
}
std::cout << "ltext2id: finished migration from text-based to id-based translation.\n";
return fail ? 1 : 0;
}