339 const QStringList &alienFiles,
340 const QString &sourceLanguage,
const QString &targetLanguage,
341 UpdateOptions options,
bool *fail)
345 if (!msg.id().isEmpty() && msg.sourceText().isEmpty())
346 printErr(QStringLiteral(
"lupdate warning: Message with id '%1' has no source.\n")
350 QList<Translator> aliens;
351 for (
const QString &fileName : alienFiles) {
354 if (!tor.load(fileName, cd, QLatin1String(
"auto"))) {
355 printErr(cd.error());
359 tor.resolveDuplicates();
365 for (
const QString &fileName : tsFileNames) {
366 QString fn = dir.relativeFilePath(fileName);
369 cd.m_sortContexts = !(options & NoSort);
370 if (QFile(fileName).exists()) {
371 if (!tor.load(fileName, cd, QLatin1String(
"auto"))) {
372 printErr(cd.error());
376 tor.resolveDuplicates();
378 if (!targetLanguage.isEmpty() && targetLanguage != tor.languageCode())
379 printErr(QStringLiteral(
"lupdate warning: Specified target language '%1' disagrees with"
380 " existing file's language '%2'. Ignoring.\n")
381 .arg(targetLanguage, tor.languageCode()));
382 if (!sourceLanguage.isEmpty() && sourceLanguage != tor.sourceLanguageCode())
383 printErr(QStringLiteral(
"lupdate warning: Specified source language '%1' disagrees with"
384 " existing file's language '%2'. Ignoring.\n")
385 .arg(sourceLanguage, tor.sourceLanguageCode()));
388 if (tor.translationsExist()) {
390 QLocale::Territory c;
391 tor.languageAndTerritory(tor.languageCode(), &l, &c);
393 if (!getNumerusInfo(l, c, 0, &forms, 0)) {
394 printErr(QStringLiteral(
"File %1 won't be updated: it contains translation but the"
395 " target language is not recognized\n").arg(fileName));
400 if (!targetLanguage.isEmpty())
401 tor.setLanguageCode(targetLanguage);
403 tor.setLanguageCode(Translator::guessLanguageCodeFromFileName(fileName));
404 if (!sourceLanguage.isEmpty())
405 tor.setSourceLanguageCode(sourceLanguage);
407 tor.makeFileNamesAbsolute(QFileInfo(fileName).absoluteDir());
408 if (options & NoLocations)
409 tor.setLocationsType(Translator::NoLocations);
410 else if (options & RelativeLocations)
411 tor.setLocationsType(Translator::RelativeLocations);
412 else if (options & AbsoluteLocations)
413 tor.setLocationsType(Translator::AbsoluteLocations);
414 if (options & Verbose)
415 printOut(QStringLiteral(
"Updating '%1'...\n").arg(fn));
417 UpdateOptions theseOptions = options;
418 if (tor.locationsType() == Translator::NoLocations)
419 theseOptions |= NoLocations;
420 Translator out = merge(tor, fetchedTor, aliens, theseOptions, err);
422 if ((options & Verbose) && !err.isEmpty()) {
426 if (options & PluralOnly) {
427 if (options & Verbose)
428 printOut(QStringLiteral(
"Stripping non plural forms in '%1'...\n").arg(fn));
429 out.stripNonPluralForms();
431 if (options & NoObsolete)
432 out.stripObsoleteMessages();
433 out.stripEmptyContexts();
435 out.normalizeTranslations(cd);
436 if (!cd.errors().isEmpty()) {
437 printErr(cd.error());
440 if (!out.save(fileName, cd, QLatin1String(
"auto"))) {
441 printErr(cd.error());
548 bool requireQmlSupport =
false;
550 QStringList sourceFilesCpp;
551 for (
const auto &sourceFile : sourceFiles) {
552 if (sourceFile.endsWith(QLatin1String(
".java"), Qt::CaseInsensitive))
553 loadJava(fetchedTor, sourceFile, cd);
554 else if (sourceFile.endsWith(QLatin1String(
".ui"), Qt::CaseInsensitive)
555 || sourceFile.endsWith(QLatin1String(
".jui"), Qt::CaseInsensitive))
556 loadUI(fetchedTor, sourceFile, cd);
558 else if (sourceFile.endsWith(QLatin1String(
".js"), Qt::CaseInsensitive)
559 || sourceFile.endsWith(QLatin1String(
".qs"), Qt::CaseInsensitive))
560 loadQScript(fetchedTor, sourceFile, cd);
561 else if (sourceFile.endsWith(QLatin1String(
".qml"), Qt::CaseInsensitive))
562 loadQml(fetchedTor, sourceFile, cd);
564 else if (sourceFile.endsWith(QLatin1String(
".qml"), Qt::CaseInsensitive)
565 || sourceFile.endsWith(QLatin1String(
".js"), Qt::CaseInsensitive)
566 || sourceFile.endsWith(QLatin1String(
".qs"), Qt::CaseInsensitive))
567 requireQmlSupport =
true;
569 else if (sourceFile.endsWith(u".py", Qt::CaseInsensitive))
570 loadPython(fetchedTor, sourceFile, cd);
571 else if (!processTs(fetchedTor, sourceFile, cd))
572 sourceFilesCpp << sourceFile;
576 if (requireQmlSupport)
577 printErr(QStringLiteral(
"lupdate warning: Some files have been ignored due to missing qml/javascript support\n"));
581#if QT_CONFIG(clangcpp)
582 ClangCppParser::loadCPP(fetchedTor, sourceFilesCpp, cd, fail);
585 printErr(QStringLiteral(
"lupdate error: lupdate was built without clang support."));
589 loadCPP(fetchedTor, sourceFilesCpp, cd);
591 if (!cd.error().isEmpty())
592 printErr(cd.error());
616 const QString &targetLanguage)
623 bool nestComplain,
Translator *parentTor,
bool *fail)
const
625 for (
const Project &prj : projects)
626 processProject(options, prj, topLevel, nestComplain, parentTor, fail);
630 void processProject(UpdateOptions options,
const Project &prj,
bool topLevel,
631 bool nestComplain,
Translator *parentTor,
bool *fail)
const
633 QString codecForSource = prj.codec.toLower();
634 if (!codecForSource.isEmpty()) {
635 if (codecForSource == QLatin1String(
"utf-16")
636 || codecForSource == QLatin1String(
"utf16")) {
638 }
else if (codecForSource == QLatin1String(
"utf-8")
639 || codecForSource == QLatin1String(
"utf8")) {
642 printErr(QStringLiteral(
"lupdate warning: Codec for source '%1' is invalid."
643 " Falling back to UTF-8.\n").arg(codecForSource));
648 const QString projectFile = prj.filePath;
649 const QStringList sources = prj.sources;
652 cd.m_projectRoots = projectRoots(projectFile, sources);
653 QStringList projectRootDirs;
654 for (
auto dir : cd.m_projectRoots)
655 projectRootDirs.append(dir);
656 cd.m_rootDirs = projectRootDirs;
657 cd.m_includePath = prj.includePaths;
658 cd.m_excludes = prj.excluded;
661 cd.m_compilationDatabaseDir = prj.compileCommands;
663 cd.m_compilationDatabaseDir = commandLineCompilationDatabaseDir;
666 if (prj.translations) {
667 tsFiles = *prj.translations;
670 printErr(QStringLiteral(
"lupdate warning: TS files from command line "
671 "will override TRANSLATIONS in %1.\n").arg(projectFile));
673 }
else if (nestComplain) {
674 printErr(QStringLiteral(
"lupdate warning: TS files from command line "
675 "prevent recursing into %1.\n").arg(projectFile));
679 if (tsFiles.isEmpty()) {
686 processProjects(
false, options, prj.subProjects,
false, &tor, fail);
687 processSources(tor, sources, cd, fail);
688 updateTsFiles(tor, tsFiles, QStringList(), m_sourceLanguage, m_targetLanguage,
696 printErr(QStringLiteral(
"lupdate warning: no TS files specified. Only diagnostics "
697 "will be produced for '%1'.\n").arg(projectFile));
700 processProjects(
false, options, prj.subProjects, nestComplain, &tor, fail);
701 processSources(tor, sources, cd, fail);
703 processProjects(
false, options, prj.subProjects, nestComplain, parentTor, fail);
704 processSources(*parentTor, sources, cd, fail);
708 QString m_sourceLanguage;
709 QString m_targetLanguage;
714 QCoreApplication app(argc, argv);
715#ifndef QT_BOOTSTRAPPED
717 QTranslator translator;
718 QTranslator qtTranslator;
719 QString sysLocale = QLocale::system().name();
720 QString resourceDir = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
721 if (translator.load(QLatin1String(
"linguist_") + sysLocale, resourceDir)
722 && qtTranslator.load(QLatin1String(
"qt_") + sysLocale, resourceDir)) {
723 app.installTranslator(&translator);
724 app.installTranslator(&qtTranslator);
729 m_defaultExtensions = QLatin1String(
"java,jui,ui,c,c++,cc,cpp,cxx,ch,h,h++,hh,hpp,hxx,js,qs,qml,qrc");
731 QStringList args = app.arguments();
732 QStringList tsFileNames;
733 QStringList proFiles;
734 QString projectDescriptionFile;
735 QString outDir = QDir::currentPath();
736 QMultiHash<QString, QString> allCSources;
737 QSet<QString> projectRoots;
738 QStringList sourceFiles;
739 QStringList resourceFiles;
740 QStringList includePath;
741 QStringList alienFiles;
742 QString targetLanguage;
743 QString sourceLanguage;
745 UpdateOptions options =
747 HeuristicSameText | HeuristicSimilarText;
749 bool metTsFlag =
false;
750 bool metXTsFlag =
false;
751 bool recursiveScan =
true;
753 QString extensions = m_defaultExtensions;
754 QSet<QString> extensionsNameFilters;
756 for (
int i = 1; i < args.size(); ++i) {
757 QString arg = args.at(i);
758 if (arg == QLatin1String(
"-help")
759 || arg == QLatin1String(
"--help")
760 || arg == QLatin1String(
"-h")) {
763 }
else if (arg == QLatin1String(
"-list-languages")) {
764 printOut(getNumerusInfoString());
766 }
else if (arg == QLatin1String(
"-pluralonly")) {
769 }
else if (arg == QLatin1String(
"-noobsolete")
770 || arg == QLatin1String(
"-no-obsolete")) {
773 }
else if (arg == QLatin1String(
"-silent")) {
776 }
else if (arg == QLatin1String(
"-pro-debug")) {
778 }
else if (arg == QLatin1String(
"-project")) {
781 printErr(u"The option -project requires a parameter.\n"_s);
784 if (!projectDescriptionFile.isEmpty()) {
785 printErr(u"The option -project must appear only once.\n"_s);
788 projectDescriptionFile = args[i];
791 }
else if (arg == QLatin1String(
"-target-language")) {
794 printErr(u"The option -target-language requires a parameter.\n"_s);
797 targetLanguage = args[i];
799 }
else if (arg == QLatin1String(
"-source-language")) {
802 printErr(u"The option -source-language requires a parameter.\n"_s);
805 sourceLanguage = args[i];
807 }
else if (arg == QLatin1String(
"-disable-heuristic")) {
810 printErr(u"The option -disable-heuristic requires a parameter.\n"_s);
814 if (arg == QLatin1String(
"sametext")) {
816 }
else if (arg == QLatin1String(
"similartext")) {
819 printErr(u"Invalid heuristic name passed to -disable-heuristic.\n"_s);
823 }
else if (arg == QLatin1String(
"-locations")) {
826 printErr(u"The option -locations requires a parameter.\n"_s);
829 if (args[i] == QLatin1String(
"none")) {
831 }
else if (args[i] == QLatin1String(
"relative")) {
833 }
else if (args[i] == QLatin1String(
"absolute")) {
836 printErr(u"Invalid parameter passed to -locations.\n"_s);
840 }
else if (arg == QLatin1String(
"-no-ui-lines")) {
843 }
else if (arg == QLatin1String(
"-verbose")) {
846 }
else if (arg == QLatin1String(
"-no-recursive")) {
847 recursiveScan =
false;
849 }
else if (arg == QLatin1String(
"-recursive")) {
850 recursiveScan =
true;
852 }
else if (arg == QLatin1String(
"-no-sort")
853 || arg == QLatin1String(
"-nosort")) {
856 }
else if (arg == QLatin1String(
"-version")) {
857 printOut(QStringLiteral(
"lupdate version %1\n").arg(QLatin1String(QT_VERSION_STR)));
859 }
else if (arg == QLatin1String(
"-ts")) {
863 }
else if (arg == QLatin1String(
"-xts")) {
867 }
else if (arg == QLatin1String(
"-extensions")) {
870 printErr(u"The -extensions option should be followed by an extension list.\n"_s);
873 extensions = args[i];
875 }
else if (arg == QLatin1String(
"-tr-function-alias")) {
878 printErr(u"The -tr-function-alias option should be followed by a list of function=alias mappings.\n"_s);
881 if (!handleTrFunctionAliases(args[i]))
884 }
else if (arg == QLatin1String(
"-pro")) {
887 printErr(u"The -pro option should be followed by a filename of .pro file.\n"_s);
890 QString file = QDir::cleanPath(QFileInfo(args[i]).absoluteFilePath());
894 }
else if (arg == QLatin1String(
"-pro-out")) {
897 printErr(u"The -pro-out option should be followed by a directory name.\n"_s);
900 outDir = QDir::cleanPath(QFileInfo(args[i]).absoluteFilePath());
902 }
else if (arg.startsWith(QLatin1String(
"-I"))) {
903 if (arg.size() == 2) {
906 printErr(u"The -I option should be followed by a path.\n"_s);
909 includePath += args[i];
911 includePath += args[i].mid(2);
915#if QT_CONFIG(clangcpp)
916 else if (arg == QLatin1String(
"-clang-parser")) {
917 useClangToParseCpp =
true;
919 if ((i + 1) != argc && !args[i + 1].startsWith(QLatin1String(
"-"))) {
921 commandLineCompilationDatabaseDir = args[i];
925 else if (arg == QLatin1String(
"-project-roots")) {
926 while ((i + 1) != argc && !args[i + 1].startsWith(QLatin1String(
"-"))) {
930 rootDirs.removeDuplicates();
934 else if (arg.startsWith(QLatin1String(
"-")) && arg != QLatin1String(
"-")) {
935 printErr(QStringLiteral(
"Unrecognized option '%1'.\n").arg(arg));
940 if (arg.startsWith(QLatin1String(
"@"))) {
941 QFile lstFile(arg.mid(1));
942 if (!lstFile.open(QIODevice::ReadOnly)) {
943 printErr(QStringLiteral(
"lupdate error: List file '%1' is not readable.\n")
944 .arg(lstFile.fileName()));
947 while (!lstFile.atEnd()) {
948 QString lineContent = QString::fromLocal8Bit(lstFile.readLine().trimmed());
950 if (lineContent.startsWith(QLatin1String(
"-I"))) {
951 if (lineContent.size() == 2) {
952 printErr(u"The -I option should be followed by a path.\n"_s);
955 includePath += lineContent.mid(2);
957 files << lineContent;
964 for (
const QString &file : std::as_const(files)) {
966 for (
const Translator::FileFormat &fmt : std::as_const(Translator::registeredFileFormats())) {
967 if (file.endsWith(QLatin1Char(
'.') + fmt.extension, Qt::CaseInsensitive)) {
969 if (!fi.exists() || fi.isWritable()) {
970 tsFileNames.append(QFileInfo(file).absoluteFilePath());
972 printErr(QStringLiteral(
"lupdate warning: For some reason, '%1' is not writable.\n")
980 printErr(QStringLiteral(
"lupdate error: File '%1' has no recognized extension.\n")
986 }
else if (metXTsFlag) {
989 for (
const QString &file : std::as_const(files)) {
992 printErr(QStringLiteral(
"lupdate error: File '%1' does not exist.\n").arg(file));
995 if (isProOrPriFile(file)) {
996 QString cleanFile = QDir::cleanPath(fi.absoluteFilePath());
997 proFiles << cleanFile;
998 }
else if (fi.isDir()) {
999 if (options & Verbose)
1000 printOut(QStringLiteral(
"Scanning directory '%1'...\n").arg(file));
1001 QDir dir = QDir(fi.filePath());
1002 projectRoots.insert(dir.absolutePath() + QLatin1Char(
'/'));
1003 if (extensionsNameFilters.isEmpty()) {
1004 for (QString ext : extensions.split(QLatin1Char(
','))) {
1005 ext = ext.trimmed();
1006 if (ext.startsWith(QLatin1Char(
'.')))
1008 extensionsNameFilters.insert(ext);
1011 QDir::Filters filters = QDir::Files | QDir::NoSymLinks;
1013 filters |= QDir::AllDirs | QDir::NoDotAndDotDot;
1014 QFileInfoList fileinfolist;
1015 recursiveFileInfoList(dir, extensionsNameFilters, filters, &fileinfolist);
1016 int scanRootLen = dir.absolutePath().size();
1017 for (
const QFileInfo &fi : std::as_const(fileinfolist)) {
1018 QString fn = QDir::cleanPath(fi.absoluteFilePath());
1019 if (fn.endsWith(QLatin1String(
".qrc"), Qt::CaseInsensitive)) {
1020 resourceFiles << fn;
1024 if (!fn.endsWith(QLatin1String(
".java"))
1025 && !fn.endsWith(QLatin1String(
".jui"))
1026 && !fn.endsWith(QLatin1String(
".ui"))
1027 && !fn.endsWith(QLatin1String(
".js"))
1028 && !fn.endsWith(QLatin1String(
".qs"))
1029 && !fn.endsWith(QLatin1String(
".qml"))) {
1033 offset = fn.lastIndexOf(QLatin1Char(
'/'), offset - 1);
1034 QString ffn = fn.mid(offset + 1);
1035 allCSources.insert(ffn, fn);
1036 }
while (++depth < 3 && offset > scanRootLen);
1041 QString fn = QDir::cleanPath(fi.absoluteFilePath());
1042 if (fn.endsWith(QLatin1String(
".qrc"), Qt::CaseInsensitive))
1043 resourceFiles << fn;
1046 projectRoots.insert(fi.absolutePath() + QLatin1Char(
'/'));
1053 if (numFiles == 0) {
1058 if (!targetLanguage.isEmpty() && tsFileNames.size() != 1)
1059 printErr(u"lupdate warning: -target-language usually only"
1060 " makes sense with exactly one TS file.\n"_s);
1062 if (proFiles.isEmpty() && resourceFiles.isEmpty() && sourceFiles.size() == 1
1063 && QFileInfo(sourceFiles.first()).fileName() == u"CMakeLists.txt"_s) {
1064 printErr(u"lupdate error: Passing a CMakeLists.txt as project file is not supported.\n"_s
1065 u"Please use the 'qt_add_lupdate' CMake command and build the "_s
1066 u"'update_translations' target.\n"_s);
1070 QString errorString;
1071 if (!proFiles.isEmpty()) {
1072 runInternalQtTool(u"lupdate-pro"_s, app.arguments().mid(1));
1077 if (!projectDescriptionFile.isEmpty()) {
1078 projectDescription = readProjectDescription(projectDescriptionFile, &errorString);
1079 if (!errorString.isEmpty()) {
1080 printErr(QStringLiteral(
"lupdate error: %1\n").arg(errorString));
1083 if (projectDescription.empty()) {
1084 printErr(QStringLiteral(
"lupdate error:"
1085 " Could not find project descriptions in %1.\n")
1086 .arg(projectDescriptionFile));
1090 for (Project &project : projectDescription)
1091 expandQrcFiles(project);
1095 if (projectDescription.empty()) {
1096 if (tsFileNames.isEmpty())
1097 printErr(u"lupdate warning:"
1098 " no TS files specified. Only diagnostics will be produced.\n"_s);
1104 cd.m_projectRoots = projectRoots;
1105 cd.m_includePath = includePath;
1106 cd.m_allCSources = allCSources;
1107 cd.m_compilationDatabaseDir = commandLineCompilationDatabaseDir;
1108 cd.m_rootDirs = rootDirs;
1109 for (
const QString &resource : std::as_const(resourceFiles))
1110 sourceFiles << getResources(resource);
1111 processSources(fetchedTor, sourceFiles, cd, &fail);
1112 updateTsFiles(fetchedTor, tsFileNames, alienFiles,
1113 sourceLanguage, targetLanguage, options, &fail);
1115 if (!sourceFiles.isEmpty() || !resourceFiles.isEmpty() || !includePath.isEmpty()) {
1116 printErr(QStringLiteral(
"lupdate error:"
1117 " Both project and source files / include paths specified.\n"));
1120 QString errorString;
1122 if (!tsFileNames.isEmpty()) {
1124 projectProcessor.processProjects(
true, options, projectDescription,
true, &fetchedTor,
1127 updateTsFiles(fetchedTor, tsFileNames, alienFiles,
1128 sourceLanguage, targetLanguage, options, &fail);
1131 projectProcessor.processProjects(
true, options, projectDescription,
false,
nullptr,
1135 return fail ? 1 : 0;