349 const QStringList &alienFiles,
350 const QString &sourceLanguage,
const QString &targetLanguage,
351 UpdateOptions options,
bool *fail)
355 if (!msg.id().isEmpty() && msg.sourceText().isEmpty()) {
356 printWarning(options,
357 "Message with id '%1' has no source.\n"_L1.arg(msg.id()));
362 QList<Translator> aliens;
363 for (
const QString &fileName : alienFiles) {
366 if (!tor.load(fileName, cd,
"auto"_L1)) {
367 printErr(cd.error());
371 tor.resolveDuplicates();
376 for (
const QString &fileName : tsFileNames) {
377 QString fn = dir.relativeFilePath(fileName);
380 cd.m_sortContexts = !(options & NoSort);
381 cd.m_sortMessages = options & SortMessages;
382 if (QFile(fileName).exists()) {
383 if (!tor.load(fileName, cd,
"auto"_L1)) {
384 printErr(cd.error());
388 tor.resolveDuplicates();
390 if (!targetLanguage.isEmpty() && targetLanguage != tor.languageCode()) {
391 printWarning(options,
392 "Specified target language '%1' disagrees with"
393 " existing file's language '%2'.\n"_L1
394 .arg(targetLanguage, tor.languageCode()),
396 if (options & Werror)
399 if (!sourceLanguage.isEmpty() && sourceLanguage != tor.sourceLanguageCode()) {
400 printWarning(options,
401 "Specified source language '%1' disagrees with"
402 " existing file's language '%2'.\n"_L1
403 .arg(sourceLanguage, tor.sourceLanguageCode()),
405 if (options & Werror)
410 if (tor.translationsExist()) {
412 if (tor.languageCode().isEmpty()) {
413 printErr(
"File %1 won't be updated: it does not specify any "
414 "target languages. To set a target language, open "
415 "the file in Qt Linguist.\n"_L1.arg(fileName));
419 QLocale::Territory c;
420 tor.languageAndTerritory(tor.languageCode(), &l, &c);
422 if (!getNumerusInfo(l, c, 0, &forms, 0)) {
423 printErr(QStringLiteral(
"File %1 won't be updated: it contains translation but the"
424 " target language is not recognized\n").arg(fileName));
429 if (!targetLanguage.isEmpty())
430 tor.setLanguageCode(targetLanguage);
432 tor.setLanguageCode(Translator::guessLanguageCodeFromFileName(fileName));
433 if (!sourceLanguage.isEmpty())
434 tor.setSourceLanguageCode(sourceLanguage);
436 tor.makeFileNamesAbsolute(QFileInfo(fileName).absoluteDir());
437 if (options & NoLocations)
438 tor.setLocationsType(Translator::NoLocations);
439 else if (options & RelativeLocations)
440 tor.setLocationsType(Translator::RelativeLocations);
441 else if (options & AbsoluteLocations)
442 tor.setLocationsType(Translator::AbsoluteLocations);
443 if (options & Verbose)
444 printOut(QStringLiteral(
"Updating '%1'...\n").arg(fn));
446 UpdateOptions theseOptions = options;
447 if (tor.locationsType() == Translator::NoLocations)
448 theseOptions |= NoLocations;
449 Translator out = merge(tor, fetchedTor, aliens, theseOptions, err);
451 if ((options & Verbose) && !err.isEmpty()) {
455 if (options & PluralOnly) {
456 if (options & Verbose)
457 printOut(QStringLiteral(
"Stripping non plural forms in '%1'...\n").arg(fn));
458 out.stripNonPluralForms();
460 if (options & NoObsolete)
461 out.stripObsoleteMessages();
462 out.stripEmptyContexts();
464 out.normalizeTranslations(cd);
465 if (!cd.errors().isEmpty()) {
466 printErr(cd.error());
469 if (!out.save(fileName, cd,
"auto"_L1)) {
470 printErr(cd.error());
577 bool requireQmlSupport =
false;
579 QStringList sourceFilesCpp;
580 for (
const auto &sourceFile : sourceFiles) {
581 if (sourceFile.endsWith(
".java"_L1, Qt::CaseInsensitive))
582 loadJava(fetchedTor, sourceFile, cd);
583 else if (sourceFile.endsWith(
".ui"_L1, Qt::CaseInsensitive)
584 || sourceFile.endsWith(
".jui"_L1, Qt::CaseInsensitive))
585 loadUI(fetchedTor, sourceFile, cd);
587 else if (sourceFile.endsWith(
".js"_L1, Qt::CaseInsensitive)
588 || sourceFile.endsWith(
".qs"_L1, Qt::CaseInsensitive)) {
589 loadQScript(fetchedTor, sourceFile, cd);
590 }
else if (sourceFile.endsWith(
".mjs"_L1, Qt::CaseInsensitive)) {
591 loadJSModule(fetchedTor, sourceFile, cd);
592 }
else if (sourceFile.endsWith(
".qml"_L1, Qt::CaseInsensitive))
593 loadQml(fetchedTor, sourceFile, cd);
595 else if (sourceFile.endsWith(
".qml"_L1, Qt::CaseInsensitive)
596 || sourceFile.endsWith(
".js"_L1, Qt::CaseInsensitive)
597 || sourceFile.endsWith(
".mjs"_L1, Qt::CaseInsensitive)
598 || sourceFile.endsWith(
".qs"_L1, Qt::CaseInsensitive))
599 requireQmlSupport =
true;
601 else if (sourceFile.endsWith(u".py", Qt::CaseInsensitive))
602 loadPython(fetchedTor, sourceFile, cd);
603 else if (!processTs(fetchedTor, sourceFile, cd))
604 sourceFilesCpp << sourceFile;
608 if (requireQmlSupport) {
609 printWarning(options, u"missing qml/javascript support\n"_s,
610 u"Some files have been ignored.\n"_s);
611 if (options & Werror)
618 loadCPP(fetchedTor, sourceFilesCpp, cd);
620 if (!cd.error().isEmpty())
621 printErr(cd.error());
645 const QString &targetLanguage)
652 bool nestComplain,
Translator *parentTor,
bool *fail)
const
654 for (
const Project &prj : projects)
655 processProject(options, prj, topLevel, nestComplain, parentTor, fail);
660 void processProject(UpdateOptions options,
const Project &prj,
bool topLevel,
661 bool nestComplain,
Translator *parentTor,
bool *fail)
const
664 QString codecForSource = prj.codec.toLower();
665 if (!codecForSource.isEmpty()) {
666 if (codecForSource ==
"utf-16"_L1 || codecForSource ==
"utf16"_L1) {
668 }
else if (codecForSource ==
"utf-8"_L1 || codecForSource ==
"utf8"_L1) {
673 "Codec for source '%1' is invalid.\n"_L1.arg(codecForSource),
674 u"Falling back to UTF-8.\n"_s);
681 const QString projectFile = prj.filePath;
682 const QStringList sources = prj.sources;
685 cd.m_projectRoots = projectRoots(projectFile, sources);
686 QStringList projectRootDirs;
687 for (
auto dir : cd.m_projectRoots)
688 projectRootDirs.append(dir);
689 cd.m_rootDirs = projectRootDirs;
690 cd.m_includePath = prj.includePaths;
691 cd.m_excludes = prj.excluded;
694 cd.m_compilationDatabaseDir = prj.compileCommands;
696 cd.m_compilationDatabaseDir = commandLineCompilationDatabaseDir;
699 if (prj.translations) {
700 tsFiles = *prj.translations;
703 printWarning(options, u"Existing top level."_s,
704 "TS files from command line will "
705 "override TRANSLATIONS in %1.\n"_L1.arg(projectFile),
706 u"Terminating the operation.\n"_s);
710 }
else if (nestComplain) {
711 printWarning(options,
712 "TS files from command line "
713 "prevent recursing into %1.\n"_L1.arg(projectFile));
717 if (tsFiles.isEmpty()) {
724 processProjects(
false, options, prj.subProjects,
false, &tor, fail);
725 processSources(tor, sources, cd, options);
726 updateTsFiles(tor, tsFiles, QStringList(), m_sourceLanguage, m_targetLanguage,
736 printWarning(options, u"no TS files specified."_s,
737 "Only diagnostics will be produced for %1.\n"_L1.arg(projectFile),
738 u"Terminating the operation.\n"_s);
743 processProjects(
false, options, prj.subProjects, nestComplain, &tor, fail);
744 processSources(tor, sources, cd, options);
746 processProjects(
false, options, prj.subProjects, nestComplain, parentTor, fail);
747 processSources(*parentTor, sources, cd, options);
751 QString m_sourceLanguage;
752 QString m_targetLanguage;
757 QCoreApplication app(argc, argv);
758#ifndef QT_BOOTSTRAPPED
760 QTranslator translator;
761 QTranslator qtTranslator;
762 QString resourceDir = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
763 if (translator.load(
"linguist_en"_L1, resourceDir)
764 && qtTranslator.load(
"qt_en"_L1, resourceDir)) {
765 app.installTranslator(&translator);
766 app.installTranslator(&qtTranslator);
771 m_defaultExtensions = QLatin1String(
"java,jui,ui,c,c++,cc,cpp,cxx,ch,h,"_L1
772 "h++,hh,hpp,hxx,js,mjs,qs,qml,qrc"_L1);
774 QStringList args = app.arguments();
775 QStringList tsFileNames;
776 QStringList proFiles;
777 QString projectDescriptionFile;
778 QString outDir = QDir::currentPath();
779 QMultiHash<QString, QString> allCSources;
780 QSet<QString> projectRoots;
781 QStringList sourceFiles;
782 QStringList resourceFiles;
783 QStringList includePath;
784 QStringList alienFiles;
785 QString targetLanguage;
786 QString sourceLanguage;
788 UpdateOptions options =
792 bool metTsFlag =
false;
793 bool metXTsFlag =
false;
794 bool recursiveScan =
true;
798 QString extensions = m_defaultExtensions;
799 QSet<QString> extensionsNameFilters;
801 for (
int i = 1; i < args.size(); ++i) {
802 QString arg = args.at(i);
803 if (arg ==
"-help"_L1 || arg ==
"--help"_L1 || arg ==
"-h"_L1) {
806 }
else if (arg ==
"-list-languages"_L1) {
807 printOut(getNumerusInfoString());
809 }
else if (arg ==
"-pluralonly"_L1) {
812 }
else if (arg ==
"-noobsolete"_L1 || arg ==
"-no-obsolete"_L1) {
815 }
else if (arg ==
"-silent"_L1) {
818 }
else if (arg ==
"-pro-debug"_L1) {
820 }
else if (arg ==
"-project"_L1) {
823 printErr(u"The option -project requires a parameter.\n"_s);
826 if (!projectDescriptionFile.isEmpty()) {
827 printErr(u"The option -project must appear only once.\n"_s);
830 projectDescriptionFile = args[i];
833 }
else if (arg ==
"-target-language"_L1) {
836 printErr(u"The option -target-language requires a parameter.\n"_s);
839 targetLanguage = args[i];
841 }
else if (arg ==
"-source-language"_L1) {
844 printErr(u"The option -source-language requires a parameter.\n"_s);
847 sourceLanguage = args[i];
849 }
else if (arg ==
"-disable-heuristic"_L1) {
852 printErr(u"The option -disable-heuristic requires a parameter.\n"_s);
856 if (arg ==
"sametext"_L1) {
858 }
else if (arg ==
"similartext"_L1) {
861 printErr(u"Invalid heuristic name passed to -disable-heuristic.\n"_s);
865 }
else if (arg ==
"-locations"_L1) {
868 printErr(u"The option -locations requires a parameter.\n"_s);
871 if (args[i] ==
"none"_L1) {
873 }
else if (args[i] ==
"relative"_L1) {
875 }
else if (args[i] ==
"absolute"_L1) {
878 printErr(u"Invalid parameter passed to -locations.\n"_s);
882 }
else if (arg ==
"-no-ui-lines"_L1) {
885 }
else if (arg ==
"-verbose"_L1) {
888 }
else if (arg ==
"-warnings-are-errors"_L1) {
891 }
else if (arg ==
"-no-recursive"_L1) {
892 recursiveScan =
false;
894 }
else if (arg ==
"-recursive"_L1) {
895 recursiveScan =
true;
897 }
else if (arg ==
"-no-sort"_L1 || arg ==
"-nosort"_L1) {
900 }
else if (arg ==
"-sort-messages"_L1) {
903 }
else if (arg ==
"-version"_L1) {
904 printOut(QStringLiteral(
"lupdate version %1\n").arg(QLatin1String(QT_VERSION_STR)));
906 }
else if (arg ==
"-ts"_L1) {
910 }
else if (arg ==
"-xts"_L1) {
914 }
else if (arg ==
"-extensions"_L1) {
917 printErr(u"The -extensions option should be followed by an extension list.\n"_s);
920 extensions = args[i];
922 }
else if (arg ==
"-tr-function-alias"_L1) {
925 printErr(u"The -tr-function-alias option should be followed by a list of function=alias mappings.\n"_s);
928 if (!handleTrFunctionAliases(args[i]))
931 }
else if (arg ==
"-pro"_L1) {
934 printErr(u"The -pro option should be followed by a filename of .pro file.\n"_s);
937 QString file = QDir::cleanPath(QFileInfo(args[i]).absoluteFilePath());
941 }
else if (arg ==
"-pro-out"_L1) {
944 printErr(u"The -pro-out option should be followed by a directory name.\n"_s);
947 outDir = QDir::cleanPath(QFileInfo(args[i]).absoluteFilePath());
949 }
else if (arg.startsWith(
"-I"_L1)) {
950 if (arg.size() == 2) {
953 printErr(u"The -I option should be followed by a path.\n"_s);
956 includePath += args[i];
958 includePath += args[i].mid(2);
962 else if (arg.startsWith(
"-"_L1) && arg !=
"-"_L1) {
963 printErr(
"Unrecognized option '%1'.\n"_L1.arg(arg));
968 if (arg.startsWith(
"@"_L1)) {
969 QFile lstFile(arg.mid(1));
970 if (!lstFile.open(QIODevice::ReadOnly)) {
971 printErr(QStringLiteral(
"lupdate error: List file '%1' is not readable.\n")
972 .arg(lstFile.fileName()));
975 while (!lstFile.atEnd()) {
976 QString lineContent = QString::fromLocal8Bit(lstFile.readLine().trimmed());
978 if (lineContent.startsWith(
"-I"_L1)) {
979 if (lineContent.size() == 2) {
980 printErr(u"The -I option should be followed by a path.\n"_s);
983 includePath += lineContent.mid(2);
985 files << lineContent;
992 for (
const QString &file : std::as_const(files)) {
994 for (
const Translator::FileFormat &fmt : std::as_const(Translator::registeredFileFormats())) {
995 if (file.endsWith(u'.' + fmt.extension, Qt::CaseInsensitive)) {
997 if (!fi.exists() || fi.isWritable()) {
998 tsFileNames.append(QFileInfo(file).absoluteFilePath());
1000 printWarning(options,
1001 "For some reason, '%1' is not writable.\n"_L1
1003 if (options & Werror)
1011 printErr(QStringLiteral(
"lupdate error: File '%1' has no recognized extension.\n")
1017 }
else if (metXTsFlag) {
1018 alienFiles += files;
1020 for (
const QString &file : std::as_const(files)) {
1023 printErr(QStringLiteral(
"lupdate error: File '%1' does not exist.\n").arg(file));
1026 if (isProOrPriFile(file)) {
1027 QString cleanFile = QDir::cleanPath(fi.absoluteFilePath());
1028 proFiles << cleanFile;
1029 }
else if (fi.isDir()) {
1030 if (options & Verbose)
1031 printOut(QStringLiteral(
"Scanning directory '%1'...\n").arg(file));
1032 QDir dir = QDir(fi.filePath());
1033 projectRoots.insert(dir.absolutePath() + u'/');
1034 if (extensionsNameFilters.isEmpty()) {
1035 for (QString ext : extensions.split(u',')) {
1036 ext = ext.trimmed();
1037 if (ext.startsWith(u'.'))
1039 extensionsNameFilters.insert(ext);
1042 QDir::Filters filters = QDir::Files | QDir::NoSymLinks;
1044 filters |= QDir::AllDirs | QDir::NoDotAndDotDot;
1045 QFileInfoList fileinfolist;
1046 recursiveFileInfoList(dir, extensionsNameFilters, filters, &fileinfolist);
1047 int scanRootLen = dir.absolutePath().size();
1048 for (
const QFileInfo &fi : std::as_const(fileinfolist)) {
1049 QString fn = QDir::cleanPath(fi.absoluteFilePath());
1050 if (fn.endsWith(
".qrc"_L1, Qt::CaseInsensitive)) {
1051 resourceFiles << fn;
1055 if (!fn.endsWith(
".java"_L1) && !fn.endsWith(
".jui"_L1)
1056 && !fn.endsWith(
".ui"_L1) && !fn.endsWith(
".js"_L1)
1057 && !fn.endsWith(
".mjs"_L1) && !fn.endsWith(
".qs"_L1)
1058 && !fn.endsWith(
".qml"_L1)) {
1062 offset = fn.lastIndexOf(u'/', offset - 1);
1063 QString ffn = fn.mid(offset + 1);
1064 allCSources.insert(ffn, fn);
1065 }
while (++depth < 3 && offset > scanRootLen);
1070 QString fn = QDir::cleanPath(fi.absoluteFilePath());
1071 if (fn.endsWith(
".qrc"_L1, Qt::CaseInsensitive))
1072 resourceFiles << fn;
1075 projectRoots.insert(fi.absolutePath() + u'/');
1082 if (numFiles == 0) {
1087 if (!targetLanguage.isEmpty() && tsFileNames.size() != 1) {
1088 printWarning(options,
1089 u"-target-language usually only makes sense with exactly one TS file.\n"_s);
1094 if (proFiles.isEmpty() && resourceFiles.isEmpty() && sourceFiles.size() == 1
1095 && QFileInfo(sourceFiles.first()).fileName() == u"CMakeLists.txt"_s) {
1096 printErr(u"lupdate error: Passing a CMakeLists.txt as project file is not supported.\n"_s
1097 u"Please use the 'qt_add_lupdate' CMake command and build the "_s
1098 u"'update_translations' target.\n"_s);
1102 QString errorString;
1103 if (!proFiles.isEmpty()) {
1104 runInternalQtTool(u"lupdate-pro"_s, app.arguments().mid(1));
1109 if (!projectDescriptionFile.isEmpty()) {
1110 projectDescription = readProjectDescription(projectDescriptionFile, &errorString);
1111 if (!errorString.isEmpty()) {
1112 printErr(QStringLiteral(
"lupdate error: %1\n").arg(errorString));
1115 if (projectDescription.empty()) {
1116 printErr(QStringLiteral(
"lupdate error:"
1117 " Could not find project descriptions in %1.\n")
1118 .arg(projectDescriptionFile));
1122 for (Project &project : projectDescription)
1123 expandQrcFiles(project);
1126 if (projectDescription.empty()) {
1127 if (tsFileNames.isEmpty()) {
1128 printWarning(options, u"no TS files specified."_s,
1129 u"Only diagnostics will be produced.\n"_s,
1130 u"Terminating the operation.\n"_s);
1139 cd.m_projectRoots = projectRoots;
1140 cd.m_includePath = includePath;
1141 cd.m_allCSources = allCSources;
1142 cd.m_compilationDatabaseDir = commandLineCompilationDatabaseDir;
1143 cd.m_rootDirs = rootDirs;
1144 for (
const QString &resource : std::as_const(resourceFiles))
1145 sourceFiles << getResources(resource);
1146 processSources(fetchedTor, sourceFiles, cd, options);
1147 updateTsFiles(fetchedTor, tsFileNames, alienFiles,
1148 sourceLanguage, targetLanguage, options, &fail);
1150 if (!sourceFiles.isEmpty() || !resourceFiles.isEmpty() || !includePath.isEmpty()) {
1151 printErr(QStringLiteral(
"lupdate error:"
1152 " Both project and source files / include paths specified.\n"));
1156 if (!tsFileNames.isEmpty()) {
1158 projectProcessor.processProjects(
true, options, projectDescription,
true, &fetchedTor,
1161 updateTsFiles(fetchedTor, tsFileNames, alienFiles,
1162 sourceLanguage, targetLanguage, options, &fail);
1165 projectProcessor.processProjects(
true, options, projectDescription,
false,
nullptr,
1169 return fail ? 1 : 0;