360 const QStringList &alienFiles,
361 const QString &sourceLanguage,
const QString &targetLanguage,
362 UpdateOptions options,
bool *fail)
366 if (!msg.id().isEmpty() && msg.sourceText().isEmpty()) {
367 printWarning(options,
368 "Message with id '%1' has no source.\n"_L1.arg(msg.id()));
373 QList<Translator> aliens;
374 for (
const QString &fileName : alienFiles) {
377 if (!tor.load(fileName, cd, QLatin1String(
"auto"))) {
378 printErr(cd.error());
382 tor.resolveDuplicates();
387 for (
const QString &fileName : tsFileNames) {
388 QString fn = dir.relativeFilePath(fileName);
391 cd.m_sortContexts = !(options & NoSort);
392 if (QFile(fileName).exists()) {
393 if (!tor.load(fileName, cd, QLatin1String(
"auto"))) {
394 printErr(cd.error());
398 tor.resolveDuplicates();
400 if (!targetLanguage.isEmpty() && targetLanguage != tor.languageCode()) {
401 printWarning(options,
402 "Specified target language '%1' disagrees with"
403 " existing file's language '%2'.\n"_L1
404 .arg(targetLanguage, tor.languageCode()),
406 if (options & Werror)
409 if (!sourceLanguage.isEmpty() && sourceLanguage != tor.sourceLanguageCode()) {
410 printWarning(options,
411 "Specified source language '%1' disagrees with"
412 " existing file's language '%2'.\n"_L1
413 .arg(sourceLanguage, tor.sourceLanguageCode()),
415 if (options & Werror)
420 if (tor.translationsExist()) {
422 QLocale::Territory c;
423 tor.languageAndTerritory(tor.languageCode(), &l, &c);
425 if (!getNumerusInfo(l, c, 0, &forms, 0)) {
426 printErr(QStringLiteral(
"File %1 won't be updated: it contains translation but the"
427 " target language is not recognized\n").arg(fileName));
432 if (!targetLanguage.isEmpty())
433 tor.setLanguageCode(targetLanguage);
435 tor.setLanguageCode(Translator::guessLanguageCodeFromFileName(fileName));
436 if (!sourceLanguage.isEmpty())
437 tor.setSourceLanguageCode(sourceLanguage);
439 tor.makeFileNamesAbsolute(QFileInfo(fileName).absoluteDir());
440 if (options & NoLocations)
441 tor.setLocationsType(Translator::NoLocations);
442 else if (options & RelativeLocations)
443 tor.setLocationsType(Translator::RelativeLocations);
444 else if (options & AbsoluteLocations)
445 tor.setLocationsType(Translator::AbsoluteLocations);
446 if (options & Verbose)
447 printOut(QStringLiteral(
"Updating '%1'...\n").arg(fn));
449 UpdateOptions theseOptions = options;
450 if (tor.locationsType() == Translator::NoLocations)
451 theseOptions |= NoLocations;
452 Translator out = merge(tor, fetchedTor, aliens, theseOptions, err);
454 if ((options & Verbose) && !err.isEmpty()) {
458 if (options & PluralOnly) {
459 if (options & Verbose)
460 printOut(QStringLiteral(
"Stripping non plural forms in '%1'...\n").arg(fn));
461 out.stripNonPluralForms();
463 if (options & NoObsolete)
464 out.stripObsoleteMessages();
465 out.stripEmptyContexts();
467 out.normalizeTranslations(cd);
468 if (!cd.errors().isEmpty()) {
469 printErr(cd.error());
472 if (!out.save(fileName, cd, QLatin1String(
"auto"))) {
473 printErr(cd.error());
580 bool requireQmlSupport =
false;
582 QStringList sourceFilesCpp;
583 for (
const auto &sourceFile : sourceFiles) {
584 if (sourceFile.endsWith(QLatin1String(
".java"), Qt::CaseInsensitive))
585 loadJava(fetchedTor, sourceFile, cd);
586 else if (sourceFile.endsWith(QLatin1String(
".ui"), Qt::CaseInsensitive)
587 || sourceFile.endsWith(QLatin1String(
".jui"), Qt::CaseInsensitive))
588 loadUI(fetchedTor, sourceFile, cd);
590 else if (sourceFile.endsWith(QLatin1String(
".js"), Qt::CaseInsensitive)
591 || sourceFile.endsWith(QLatin1String(
".qs"), Qt::CaseInsensitive)) {
592 loadQScript(fetchedTor, sourceFile, cd);
593 }
else if (sourceFile.endsWith(QLatin1String(
".mjs"), Qt::CaseInsensitive)) {
594 loadJSModule(fetchedTor, sourceFile, cd);
595 }
else if (sourceFile.endsWith(QLatin1String(
".qml"), Qt::CaseInsensitive))
596 loadQml(fetchedTor, sourceFile, cd);
598 else if (sourceFile.endsWith(QLatin1String(
".qml"), Qt::CaseInsensitive)
599 || sourceFile.endsWith(QLatin1String(
".js"), Qt::CaseInsensitive)
600 || sourceFile.endsWith(QLatin1String(
".mjs"), Qt::CaseInsensitive)
601 || sourceFile.endsWith(QLatin1String(
".qs"), Qt::CaseInsensitive))
602 requireQmlSupport =
true;
604 else if (sourceFile.endsWith(u".py", Qt::CaseInsensitive))
605 loadPython(fetchedTor, sourceFile, cd);
606 else if (!processTs(fetchedTor, sourceFile, cd))
607 sourceFilesCpp << sourceFile;
611 if (requireQmlSupport) {
612 printWarning(options, u"missing qml/javascript support\n"_s,
613 u"Some files have been ignored.\n"_s);
614 if (options & Werror)
622#if QT_CONFIG(clangcpp)
623 ClangCppParser::loadCPP(fetchedTor, sourceFilesCpp, cd, fail);
626 printErr(QStringLiteral(
"lupdate error: lupdate was built without clang support."));
630 loadCPP(fetchedTor, sourceFilesCpp, cd);
632 if (!cd.error().isEmpty())
633 printErr(cd.error());
657 const QString &targetLanguage)
664 bool nestComplain,
Translator *parentTor,
bool *fail)
const
666 for (
const Project &prj : projects)
667 processProject(options, prj, topLevel, nestComplain, parentTor, fail);
672 void processProject(UpdateOptions options,
const Project &prj,
bool topLevel,
673 bool nestComplain,
Translator *parentTor,
bool *fail)
const
676 QString codecForSource = prj.codec.toLower();
677 if (!codecForSource.isEmpty()) {
678 if (codecForSource == QLatin1String(
"utf-16")
679 || codecForSource == QLatin1String(
"utf16")) {
681 }
else if (codecForSource == QLatin1String(
"utf-8")
682 || codecForSource == QLatin1String(
"utf8")) {
687 "Codec for source '%1' is invalid.\n"_L1.arg(codecForSource),
688 u"Falling back to UTF-8.\n"_s);
695 const QString projectFile = prj.filePath;
696 const QStringList sources = prj.sources;
699 cd.m_projectRoots = projectRoots(projectFile, sources);
700 QStringList projectRootDirs;
701 for (
auto dir : cd.m_projectRoots)
702 projectRootDirs.append(dir);
703 cd.m_rootDirs = projectRootDirs;
704 cd.m_includePath = prj.includePaths;
705 cd.m_excludes = prj.excluded;
708 cd.m_compilationDatabaseDir = prj.compileCommands;
710 cd.m_compilationDatabaseDir = commandLineCompilationDatabaseDir;
713 if (prj.translations) {
714 tsFiles = *prj.translations;
717 printWarning(options, u"Existing top level."_s,
718 "TS files from command line will "
719 "override TRANSLATIONS in %1.\n"_L1.arg(projectFile),
720 u"Terminating the operation.\n"_s);
724 }
else if (nestComplain) {
725 printWarning(options,
726 "TS files from command line "
727 "prevent recursing into %1.\n"_L1.arg(projectFile));
731 if (tsFiles.isEmpty()) {
738 processProjects(
false, options, prj.subProjects,
false, &tor, fail);
739 processSources(tor, sources, cd, options, fail);
740 updateTsFiles(tor, tsFiles, QStringList(), m_sourceLanguage, m_targetLanguage,
750 printWarning(options, u"no TS files specified."_s,
751 "Only diagnostics will be produced for %1.\n"_L1.arg(projectFile),
752 u"Terminating the operation.\n"_s);
757 processProjects(
false, options, prj.subProjects, nestComplain, &tor, fail);
758 processSources(tor, sources, cd, options, fail);
760 processProjects(
false, options, prj.subProjects, nestComplain, parentTor, fail);
761 processSources(*parentTor, sources, cd, options, fail);
765 QString m_sourceLanguage;
766 QString m_targetLanguage;
771 QCoreApplication app(argc, argv);
772#ifndef QT_BOOTSTRAPPED
774 QTranslator translator;
775 QTranslator qtTranslator;
776 QString sysLocale = QLocale::system().name();
777 QString resourceDir = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
778 if (translator.load(QLatin1String(
"linguist_") + sysLocale, resourceDir)
779 && qtTranslator.load(QLatin1String(
"qt_") + sysLocale, resourceDir)) {
780 app.installTranslator(&translator);
781 app.installTranslator(&qtTranslator);
786 m_defaultExtensions = QLatin1String(
"java,jui,ui,c,c++,cc,cpp,cxx,ch,h,"_L1
787 "h++,hh,hpp,hxx,js,mjs,qs,qml,qrc"_L1);
789 QStringList args = app.arguments();
790 QStringList tsFileNames;
791 QStringList proFiles;
792 QString projectDescriptionFile;
793 QString outDir = QDir::currentPath();
794 QMultiHash<QString, QString> allCSources;
795 QSet<QString> projectRoots;
796 QStringList sourceFiles;
797 QStringList resourceFiles;
798 QStringList includePath;
799 QStringList alienFiles;
800 QString targetLanguage;
801 QString sourceLanguage;
803 UpdateOptions options =
805 HeuristicSameText | HeuristicSimilarText;
807 bool metTsFlag =
false;
808 bool metXTsFlag =
false;
809 bool recursiveScan =
true;
813 QString extensions = m_defaultExtensions;
814 QSet<QString> extensionsNameFilters;
816 for (
int i = 1; i < args.size(); ++i) {
817 QString arg = args.at(i);
818 if (arg == QLatin1String(
"-help")
819 || arg == QLatin1String(
"--help")
820 || arg == QLatin1String(
"-h")) {
823 }
else if (arg == QLatin1String(
"-list-languages")) {
824 printOut(getNumerusInfoString());
826 }
else if (arg == QLatin1String(
"-pluralonly")) {
829 }
else if (arg == QLatin1String(
"-noobsolete")
830 || arg == QLatin1String(
"-no-obsolete")) {
833 }
else if (arg == QLatin1String(
"-silent")) {
836 }
else if (arg == QLatin1String(
"-pro-debug")) {
838 }
else if (arg == QLatin1String(
"-project")) {
841 printErr(u"The option -project requires a parameter.\n"_s);
844 if (!projectDescriptionFile.isEmpty()) {
845 printErr(u"The option -project must appear only once.\n"_s);
848 projectDescriptionFile = args[i];
851 }
else if (arg == QLatin1String(
"-target-language")) {
854 printErr(u"The option -target-language requires a parameter.\n"_s);
857 targetLanguage = args[i];
859 }
else if (arg == QLatin1String(
"-source-language")) {
862 printErr(u"The option -source-language requires a parameter.\n"_s);
865 sourceLanguage = args[i];
867 }
else if (arg == QLatin1String(
"-disable-heuristic")) {
870 printErr(u"The option -disable-heuristic requires a parameter.\n"_s);
874 if (arg == QLatin1String(
"sametext")) {
876 }
else if (arg == QLatin1String(
"similartext")) {
879 printErr(u"Invalid heuristic name passed to -disable-heuristic.\n"_s);
883 }
else if (arg == QLatin1String(
"-locations")) {
886 printErr(u"The option -locations requires a parameter.\n"_s);
889 if (args[i] == QLatin1String(
"none")) {
891 }
else if (args[i] == QLatin1String(
"relative")) {
893 }
else if (args[i] == QLatin1String(
"absolute")) {
896 printErr(u"Invalid parameter passed to -locations.\n"_s);
900 }
else if (arg == QLatin1String(
"-no-ui-lines")) {
903 }
else if (arg == QLatin1String(
"-verbose")) {
906 }
else if (arg == QLatin1String(
"-warnings-are-errors")) {
909 }
else if (arg == QLatin1String(
"-no-recursive")) {
910 recursiveScan =
false;
912 }
else if (arg == QLatin1String(
"-recursive")) {
913 recursiveScan =
true;
915 }
else if (arg == QLatin1String(
"-no-sort")
916 || arg == QLatin1String(
"-nosort")) {
919 }
else if (arg == QLatin1String(
"-version")) {
920 printOut(QStringLiteral(
"lupdate version %1\n").arg(QLatin1String(QT_VERSION_STR)));
922 }
else if (arg == QLatin1String(
"-ts")) {
926 }
else if (arg == QLatin1String(
"-xts")) {
930 }
else if (arg == QLatin1String(
"-extensions")) {
933 printErr(u"The -extensions option should be followed by an extension list.\n"_s);
936 extensions = args[i];
938 }
else if (arg == QLatin1String(
"-tr-function-alias")) {
941 printErr(u"The -tr-function-alias option should be followed by a list of function=alias mappings.\n"_s);
944 if (!handleTrFunctionAliases(args[i]))
947 }
else if (arg == QLatin1String(
"-pro")) {
950 printErr(u"The -pro option should be followed by a filename of .pro file.\n"_s);
953 QString file = QDir::cleanPath(QFileInfo(args[i]).absoluteFilePath());
957 }
else if (arg == QLatin1String(
"-pro-out")) {
960 printErr(u"The -pro-out option should be followed by a directory name.\n"_s);
963 outDir = QDir::cleanPath(QFileInfo(args[i]).absoluteFilePath());
965 }
else if (arg.startsWith(QLatin1String(
"-I"))) {
966 if (arg.size() == 2) {
969 printErr(u"The -I option should be followed by a path.\n"_s);
972 includePath += args[i];
974 includePath += args[i].mid(2);
978#if QT_CONFIG(clangcpp)
979 else if (arg == QLatin1String(
"-clang-parser")) {
980 useClangToParseCpp =
true;
982 if ((i + 1) != argc && !args[i + 1].startsWith(QLatin1String(
"-"))) {
984 commandLineCompilationDatabaseDir = args[i];
987 }
else if (arg == QLatin1String(
"-project-roots")) {
988 while ((i + 1) != argc && !args[i + 1].startsWith(QLatin1String(
"-"))) {
992 rootDirs.removeDuplicates();
996 else if (arg.startsWith(QLatin1String(
"-")) && arg != QLatin1String(
"-")) {
997 printErr(QStringLiteral(
"Unrecognized option '%1'.\n").arg(arg));
1002 if (arg.startsWith(QLatin1String(
"@"))) {
1003 QFile lstFile(arg.mid(1));
1004 if (!lstFile.open(QIODevice::ReadOnly)) {
1005 printErr(QStringLiteral(
"lupdate error: List file '%1' is not readable.\n")
1006 .arg(lstFile.fileName()));
1009 while (!lstFile.atEnd()) {
1010 QString lineContent = QString::fromLocal8Bit(lstFile.readLine().trimmed());
1012 if (lineContent.startsWith(QLatin1String(
"-I"))) {
1013 if (lineContent.size() == 2) {
1014 printErr(u"The -I option should be followed by a path.\n"_s);
1017 includePath += lineContent.mid(2);
1019 files << lineContent;
1026 for (
const QString &file : std::as_const(files)) {
1028 for (
const Translator::FileFormat &fmt : std::as_const(Translator::registeredFileFormats())) {
1029 if (file.endsWith(QLatin1Char(
'.') + fmt.extension, Qt::CaseInsensitive)) {
1031 if (!fi.exists() || fi.isWritable()) {
1032 tsFileNames.append(QFileInfo(file).absoluteFilePath());
1034 printWarning(options,
1035 "For some reason, '%1' is not writable.\n"_L1
1037 if (options & Werror)
1045 printErr(QStringLiteral(
"lupdate error: File '%1' has no recognized extension.\n")
1051 }
else if (metXTsFlag) {
1052 alienFiles += files;
1054 for (
const QString &file : std::as_const(files)) {
1057 printErr(QStringLiteral(
"lupdate error: File '%1' does not exist.\n").arg(file));
1060 if (isProOrPriFile(file)) {
1061 QString cleanFile = QDir::cleanPath(fi.absoluteFilePath());
1062 proFiles << cleanFile;
1063 }
else if (fi.isDir()) {
1064 if (options & Verbose)
1065 printOut(QStringLiteral(
"Scanning directory '%1'...\n").arg(file));
1066 QDir dir = QDir(fi.filePath());
1067 projectRoots.insert(dir.absolutePath() + QLatin1Char(
'/'));
1068 if (extensionsNameFilters.isEmpty()) {
1069 for (QString ext : extensions.split(QLatin1Char(
','))) {
1070 ext = ext.trimmed();
1071 if (ext.startsWith(QLatin1Char(
'.')))
1073 extensionsNameFilters.insert(ext);
1076 QDir::Filters filters = QDir::Files | QDir::NoSymLinks;
1078 filters |= QDir::AllDirs | QDir::NoDotAndDotDot;
1079 QFileInfoList fileinfolist;
1080 recursiveFileInfoList(dir, extensionsNameFilters, filters, &fileinfolist);
1081 int scanRootLen = dir.absolutePath().size();
1082 for (
const QFileInfo &fi : std::as_const(fileinfolist)) {
1083 QString fn = QDir::cleanPath(fi.absoluteFilePath());
1084 if (fn.endsWith(QLatin1String(
".qrc"), Qt::CaseInsensitive)) {
1085 resourceFiles << fn;
1089 if (!fn.endsWith(QLatin1String(
".java"))
1090 && !fn.endsWith(QLatin1String(
".jui"))
1091 && !fn.endsWith(QLatin1String(
".ui"))
1092 && !fn.endsWith(QLatin1String(
".js"))
1093 && !fn.endsWith(QLatin1String(
".mjs"))
1094 && !fn.endsWith(QLatin1String(
".qs"))
1095 && !fn.endsWith(QLatin1String(
".qml"))) {
1099 offset = fn.lastIndexOf(QLatin1Char(
'/'), offset - 1);
1100 QString ffn = fn.mid(offset + 1);
1101 allCSources.insert(ffn, fn);
1102 }
while (++depth < 3 && offset > scanRootLen);
1107 QString fn = QDir::cleanPath(fi.absoluteFilePath());
1108 if (fn.endsWith(QLatin1String(
".qrc"), Qt::CaseInsensitive))
1109 resourceFiles << fn;
1112 projectRoots.insert(fi.absolutePath() + QLatin1Char(
'/'));
1119 if (numFiles == 0) {
1124 if (!targetLanguage.isEmpty() && tsFileNames.size() != 1) {
1125 printWarning(options,
1126 u"-target-language usually only makes sense with exactly one TS file.\n"_s);
1131 if (proFiles.isEmpty() && resourceFiles.isEmpty() && sourceFiles.size() == 1
1132 && QFileInfo(sourceFiles.first()).fileName() == u"CMakeLists.txt"_s) {
1133 printErr(u"lupdate error: Passing a CMakeLists.txt as project file is not supported.\n"_s
1134 u"Please use the 'qt_add_lupdate' CMake command and build the "_s
1135 u"'update_translations' target.\n"_s);
1139 QString errorString;
1140 if (!proFiles.isEmpty()) {
1141 runInternalQtTool(u"lupdate-pro"_s, app.arguments().mid(1));
1146 if (!projectDescriptionFile.isEmpty()) {
1147 projectDescription = readProjectDescription(projectDescriptionFile, &errorString);
1148 if (!errorString.isEmpty()) {
1149 printErr(QStringLiteral(
"lupdate error: %1\n").arg(errorString));
1152 if (projectDescription.empty()) {
1153 printErr(QStringLiteral(
"lupdate error:"
1154 " Could not find project descriptions in %1.\n")
1155 .arg(projectDescriptionFile));
1159 for (Project &project : projectDescription)
1160 expandQrcFiles(project);
1163 if (projectDescription.empty()) {
1164 if (tsFileNames.isEmpty()) {
1165 printWarning(options, u"no TS files specified."_s,
1166 u"Only diagnostics will be produced.\n"_s,
1167 u"Terminating the operation.\n"_s);
1176 cd.m_projectRoots = projectRoots;
1177 cd.m_includePath = includePath;
1178 cd.m_allCSources = allCSources;
1179 cd.m_compilationDatabaseDir = commandLineCompilationDatabaseDir;
1180 cd.m_rootDirs = rootDirs;
1181 for (
const QString &resource : std::as_const(resourceFiles))
1182 sourceFiles << getResources(resource);
1183 processSources(fetchedTor, sourceFiles, cd, options, &fail);
1184 updateTsFiles(fetchedTor, tsFileNames, alienFiles,
1185 sourceLanguage, targetLanguage, options, &fail);
1187 if (!sourceFiles.isEmpty() || !resourceFiles.isEmpty() || !includePath.isEmpty()) {
1188 printErr(QStringLiteral(
"lupdate error:"
1189 " Both project and source files / include paths specified.\n"));
1192 QString errorString;
1194 if (!tsFileNames.isEmpty()) {
1196 projectProcessor.processProjects(
true, options, projectDescription,
true, &fetchedTor,
1199 updateTsFiles(fetchedTor, tsFileNames, alienFiles,
1200 sourceLanguage, targetLanguage, options, &fail);
1203 projectProcessor.processProjects(
true, options, projectDescription,
false,
nullptr,
1207 return fail ? 1 : 0;