154 QCoreApplication app(argc, argv);
155#ifndef QT_BOOTSTRAPPED
157 QTranslator translator;
158 QTranslator qtTranslator;
159 QString resourceDir = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
160 if (translator.load(
"linguist_en"_L1, resourceDir)
161 && qtTranslator.load(
"qt_en"_L1, resourceDir)) {
162 app.installTranslator(&translator);
163 app.installTranslator(&qtTranslator);
168 m_defaultExtensions = QLatin1String(
"java,jui,ui,c,c++,cc,cpp,cxx,ch,h,"_L1
169 "h++,hh,hpp,hxx,js,mjs,qs,qml,qrc"_L1);
171 QStringList args = app.arguments();
172 QStringList tsFileNames;
173 QStringList proFiles;
174 QString projectDescriptionFile;
175 QString outDir = QDir::currentPath();
176 QMultiHash<QString, QString> allCSources;
177 QSet<QString> projectRoots;
178 QStringList sourceFiles;
179 QStringList resourceFiles;
180 QStringList includePath;
181 QStringList alienFiles;
182 QString targetLanguage;
183 QString sourceLanguage;
185 UpdateOptions options =
189 bool metTsFlag =
false;
190 bool metXTsFlag =
false;
191 bool recursiveScan =
true;
193 QString extensions = m_defaultExtensions;
194 QSet<QString> extensionsNameFilters;
196 for (
int i = 1; i < args.size(); ++i) {
197 QString arg = args.at(i);
198 if (arg ==
"-help"_L1 || arg ==
"--help"_L1 || arg ==
"-h"_L1) {
201 }
else if (arg ==
"-list-languages"_L1) {
202 printOut(getNumerusInfoString());
204 }
else if (arg ==
"-pluralonly"_L1) {
207 }
else if (arg ==
"-noobsolete"_L1 || arg ==
"-no-obsolete"_L1) {
210 }
else if (arg ==
"-silent"_L1) {
213 }
else if (arg ==
"-pro-debug"_L1) {
215 }
else if (arg ==
"-project"_L1) {
218 printErr(u"The option -project requires a parameter.\n"_s);
221 if (!projectDescriptionFile.isEmpty()) {
222 printErr(u"The option -project must appear only once.\n"_s);
225 projectDescriptionFile = args[i];
228 }
else if (arg ==
"-target-language"_L1) {
231 printErr(u"The option -target-language requires a parameter.\n"_s);
234 targetLanguage = args[i];
236 }
else if (arg ==
"-source-language"_L1) {
239 printErr(u"The option -source-language requires a parameter.\n"_s);
242 sourceLanguage = args[i];
244 }
else if (arg ==
"-disable-heuristic"_L1) {
247 printErr(u"The option -disable-heuristic requires a parameter.\n"_s);
251 if (arg ==
"sametext"_L1) {
253 }
else if (arg ==
"similartext"_L1) {
256 printErr(u"Invalid heuristic name passed to -disable-heuristic.\n"_s);
260 }
else if (arg ==
"-locations"_L1) {
263 printErr(u"The option -locations requires a parameter.\n"_s);
266 if (args[i] ==
"none"_L1) {
268 }
else if (args[i] ==
"relative"_L1) {
270 }
else if (args[i] ==
"absolute"_L1) {
273 printErr(u"Invalid parameter passed to -locations.\n"_s);
277 }
else if (arg ==
"-no-ui-lines"_L1) {
280 }
else if (arg ==
"-verbose"_L1) {
283 }
else if (arg ==
"-warnings-are-errors"_L1) {
286 }
else if (arg ==
"-no-recursive"_L1) {
287 recursiveScan =
false;
289 }
else if (arg ==
"-recursive"_L1) {
290 recursiveScan =
true;
292 }
else if (arg ==
"-no-sort"_L1 || arg ==
"-nosort"_L1) {
295 }
else if (arg ==
"-sort-messages"_L1) {
298 }
else if (arg ==
"-version"_L1) {
299 printOut(QStringLiteral(
"lupdate version %1\n").arg(QLatin1String(QT_VERSION_STR)));
301 }
else if (arg ==
"-ts"_L1) {
305 }
else if (arg ==
"-xts"_L1) {
309 }
else if (arg ==
"-extensions"_L1) {
312 printErr(u"The -extensions option should be followed by an extension list.\n"_s);
315 extensions = args[i];
317 }
else if (arg ==
"-tr-function-alias"_L1) {
320 printErr(u"The -tr-function-alias option should be followed by a list of function=alias mappings.\n"_s);
323 if (!parseTrFunctionAliasString(args[i]))
326 }
else if (arg ==
"-pro"_L1) {
327 printErr(u"lupdate warning: The -pro option is deprecated. "
328 u"Please use the lupdate-pro tool instead.\n"_s);
331 printErr(u"The -pro option should be followed by a filename of .pro file.\n"_s);
334 QString file = QDir::cleanPath(QFileInfo(args[i]).absoluteFilePath());
338 }
else if (arg ==
"-pro-out"_L1) {
341 printErr(u"The -pro-out option should be followed by a directory name.\n"_s);
344 outDir = QDir::cleanPath(QFileInfo(args[i]).absoluteFilePath());
346 }
else if (arg.startsWith(
"-I"_L1)) {
347 if (arg.size() == 2) {
350 printErr(u"The -I option should be followed by a path.\n"_s);
353 includePath += args[i];
355 includePath += args[i].mid(2);
359 else if (arg.startsWith(
"-"_L1) && arg !=
"-"_L1) {
360 printErr(
"Unrecognized option '%1'.\n"_L1.arg(arg));
365 if (arg.startsWith(
"@"_L1)) {
366 QFile lstFile(arg.mid(1));
367 if (!lstFile.open(QIODevice::ReadOnly)) {
368 printErr(QStringLiteral(
"lupdate error: List file '%1' is not readable.\n")
369 .arg(lstFile.fileName()));
372 while (!lstFile.atEnd()) {
373 QString lineContent = QString::fromLocal8Bit(lstFile.readLine().trimmed());
375 if (lineContent.startsWith(
"-I"_L1)) {
376 if (lineContent.size() == 2) {
377 printErr(u"The -I option should be followed by a path.\n"_s);
380 includePath += lineContent.mid(2);
382 files << lineContent;
389 for (
const QString &file : std::as_const(files)) {
391 for (
const Translator::FileFormat &fmt : std::as_const(Translator::registeredFileFormats())) {
392 if (file.endsWith(u'.' + fmt.extension, Qt::CaseInsensitive)) {
394 if (!fi.exists() || fi.isWritable()) {
395 tsFileNames.append(QFileInfo(file).absoluteFilePath());
397 printWarning(options,
398 "For some reason, '%1' is not writable.\n"_L1
400 if (options & Werror)
408 printErr(QStringLiteral(
"lupdate error: File '%1' has no recognized extension.\n")
414 }
else if (metXTsFlag) {
417 for (
const QString &file : std::as_const(files)) {
420 printErr(QStringLiteral(
"lupdate error: File '%1' does not exist.\n").arg(file));
423 if (isProOrPriFile(file)) {
424 printErr(u"lupdate warning: Passing .pro/.pri files to lupdate is deprecated. "
425 u"Please use the lupdate-pro tool instead.\n"_s);
426 QString cleanFile = QDir::cleanPath(fi.absoluteFilePath());
427 proFiles << cleanFile;
428 }
else if (fi.isDir()) {
429 if (options & Verbose)
430 printOut(QStringLiteral(
"Scanning directory '%1'...\n").arg(file));
431 QDir dir = QDir(fi.filePath());
432 projectRoots.insert(dir.absolutePath() + u'/');
433 if (extensionsNameFilters.isEmpty()) {
434 for (QString ext : extensions.split(u',')) {
436 if (ext.startsWith(u'.'))
438 extensionsNameFilters.insert(ext);
441 QDir::Filters filters = QDir::Files | QDir::NoSymLinks;
443 filters |= QDir::AllDirs | QDir::NoDotAndDotDot;
444 QFileInfoList fileinfolist;
445 recursiveFileInfoList(dir, extensionsNameFilters, filters, &fileinfolist);
446 int scanRootLen = dir.absolutePath().size();
447 for (
const QFileInfo &fi : std::as_const(fileinfolist)) {
448 QString fn = QDir::cleanPath(fi.absoluteFilePath());
449 if (fn.endsWith(
".qrc"_L1, Qt::CaseInsensitive)) {
454 if (!fn.endsWith(
".java"_L1) && !fn.endsWith(
".jui"_L1)
455 && !fn.endsWith(
".ui"_L1) && !fn.endsWith(
".js"_L1)
456 && !fn.endsWith(
".mjs"_L1) && !fn.endsWith(
".qs"_L1)
457 && !fn.endsWith(
".qml"_L1)) {
461 offset = fn.lastIndexOf(u'/', offset - 1);
462 QString ffn = fn.mid(offset + 1);
463 allCSources.insert(ffn, fn);
464 }
while (++depth < 3 && offset > scanRootLen);
469 QString fn = QDir::cleanPath(fi.absoluteFilePath());
470 if (fn.endsWith(
".qrc"_L1, Qt::CaseInsensitive))
474 projectRoots.insert(fi.absolutePath() + u'/');
486 if (!targetLanguage.isEmpty() && tsFileNames.size() != 1) {
487 printWarning(options,
488 u"-target-language usually only makes sense with exactly one TS file.\n"_s);
493 if (proFiles.isEmpty() && resourceFiles.isEmpty() && sourceFiles.size() == 1
494 && QFileInfo(sourceFiles.first()).fileName() == u"CMakeLists.txt"_s) {
495 printErr(u"lupdate error: Passing a CMakeLists.txt as project file is not supported.\n"_s
496 u"Please use the 'qt_add_lupdate' CMake command and build the "_s
497 u"'update_translations' target.\n"_s);
504 if (!proFiles.isEmpty()) {
505 QStringList translationsVariables = { u"TRANSLATIONS"_s };
506 QHash<QString, QString> outDirMap;
507 QString outDir = QDir::currentPath();
508 for (
const QString &proFile : std::as_const(proFiles))
509 outDirMap[proFile] = outDir;
513 projectDescription = generateProjects(proFiles, translationsVariables, outDirMap,
514 0,
false, &errorString, &results);
515 if (!errorString.isEmpty()) {
516 printErr(
"lupdate error: %1\n"_L1.arg(errorString));
519 if (projectDescription.empty()) {
520 printErr(
"lupdate error: No projects found in .pro files\n"_L1);
523 }
else if (!projectDescriptionFile.isEmpty()) {
524 projectDescription = projectDescriptionFromFile(projectDescriptionFile, &errorString);
525 if (!errorString.isEmpty()) {
526 printErr(QStringLiteral(
"lupdate error: %1\n").arg(errorString));
529 if (projectDescription.empty()) {
530 printErr(QStringLiteral(
"lupdate error:"
531 " Could not find project descriptions in %1.\n")
532 .arg(projectDescriptionFile));
538 if (projectDescription.empty()) {
539 if (tsFileNames.isEmpty()) {
540 printWarning(options, u"no TS files specified."_s,
541 u"Only diagnostics will be produced.\n"_s,
542 u"Terminating the operation.\n"_s);
547 for (
const QString &resource : std::as_const(resourceFiles))
548 sourceFiles << getSourceFilesFromQrc(resource);
549 ok &= processSourceFiles(sourceFiles, tsFileNames, alienFiles, projectRoots, includePath,
550 allCSources, sourceLanguage, targetLanguage, options);
552 if (!sourceFiles.isEmpty() || !resourceFiles.isEmpty() || !includePath.isEmpty()) {
553 printErr(QStringLiteral(
"lupdate error:"
554 " Both project and source files / include paths specified.\n"));
558 ok &= processProjectDescription(projectDescription, tsFileNames, alienFiles,
559 sourceLanguage, targetLanguage, options);