7#include <trlib/trparser.h>
9#include <QtCore/qdir.h>
10#include <QtCore/qfile.h>
11#include <QtCore/qfileinfo.h>
12#include <QtCore/qregularexpression.h>
13#include <QtCore/qset.h>
17using namespace Qt::StringLiterals;
21static void printOut(
const QString &out)
23 std::cout << qPrintable(out);
26static void printErr(
const QString &out)
28 std::cerr << qPrintable(out);
31static void printWarning(UpdateOptions options,
33 const QString &warningMsg = {},
34 const QString &errorMsg = {})
38 text.prepend(
"lupdate error: "_L1);
39 if (!errorMsg.isEmpty())
40 text.append(
" "_L1).append(errorMsg);
42 text.prepend(
"lupdate warning: "_L1);
43 if (!warningMsg.isEmpty())
44 text.append(
" "_L1).append(warningMsg);
50static bool readFileContent(
const QString &filePath, QByteArray *content, QString *errorString)
53 if (!file.open(QIODevice::ReadOnly)) {
54 *errorString = file.errorString();
57 *content = file.readAll();
61static bool readFileContent(
const QString &filePath, QString *content, QString *errorString)
64 if (!readFileContent(filePath, &ba, errorString))
66 *content = QString::fromLocal8Bit(ba);
70static void removeExcludedSources(
Projects &projects)
72 for (Project &project : projects) {
73 for (
const QRegularExpression &rx : std::as_const(project.excluded)) {
74 for (
auto it = project.sources.begin(); it != project.sources.end(); ) {
75 if (rx.match(*it).hasMatch())
76 it = project.sources.erase(it);
81 removeExcludedSources(project.subProjects);
87static QStringList extractQrcFiles(
Project &project)
89 auto it = project.sources.begin();
91 while (it != project.sources.end()) {
93 QString fn = QDir::cleanPath(fi.absoluteFilePath());
94 if (fn.endsWith(
".qrc"_L1, Qt::CaseInsensitive)) {
96 it = project.sources.erase(it);
105static void expandQrcFiles(
Project &project)
107 for (
const QString &qrcFile : extractQrcFiles(project))
108 project.sources << getSourceFilesFromQrc(qrcFile);
111static QSet<QString> projectRoots(
const QString &projectFile,
const QStringList &sourceFiles)
113 const QString proPath = QFileInfo(projectFile).path();
114 QSet<QString> sourceDirs;
115 sourceDirs.insert(proPath + u'/');
116 for (
const QString &sf : sourceFiles)
117 sourceDirs.insert(sf.left(sf.lastIndexOf(u'/') + 1));
118 QStringList rootList = sourceDirs.values();
120 for (
int prev = 0, curr = 1; curr < rootList.size(); )
121 if (rootList.at(curr).startsWith(rootList.at(prev)))
122 rootList.removeAt(curr);
125 return QSet<QString>(rootList.cbegin(), rootList.cend());
128static bool updateTsFiles(
const Translator &fetchedTor,
const QStringList &tsFileNames,
129 const QStringList &alienFiles,
130 const QString &sourceLanguage,
const QString &targetLanguage,
131 UpdateOptions options)
136 if (!msg.id().isEmpty() && msg.sourceText().isEmpty()) {
137 printWarning(options,
138 "Message with id '%1' has no source.\n"_L1.arg(msg.id()));
143 QList<Translator> aliens;
144 for (
const QString &fileName : alienFiles) {
147 if (!tor.load(fileName, cd,
"auto"_L1)) {
148 printErr(cd.error());
152 tor.resolveDuplicates();
157 for (
const QString &fileName : tsFileNames) {
158 QString fn = dir.relativeFilePath(fileName);
161 cd.m_sortContexts = !(options & NoSort);
162 cd.m_sortMessages = options & SortMessages;
163 if (QFile(fileName).exists()) {
164 if (!tor.load(fileName, cd,
"auto"_L1)) {
165 printErr(cd.error());
169 tor.resolveDuplicates();
171 if (!targetLanguage.isEmpty() && targetLanguage != tor.languageCode()) {
172 printWarning(options,
173 "Specified target language '%1' disagrees with"
174 " existing file's language '%2'.\n"_L1
175 .arg(targetLanguage, tor.languageCode()),
177 if (options & Werror)
180 if (!sourceLanguage.isEmpty() && sourceLanguage != tor.sourceLanguageCode()) {
181 printWarning(options,
182 "Specified source language '%1' disagrees with"
183 " existing file's language '%2'.\n"_L1
184 .arg(sourceLanguage, tor.sourceLanguageCode()),
186 if (options & Werror)
191 if (tor.translationsExist()) {
193 if (tor.languageCode().isEmpty()) {
194 printErr(
"File %1 won't be updated: it does not specify any "
195 "target languages. To set a target language, open "
196 "the file in Qt Linguist.\n"_L1.arg(fileName));
200 QLocale::Territory c;
201 tor.languageAndTerritory(tor.languageCode(), &l, &c);
203 if (!getNumerusInfo(l, c, 0, &forms, 0)) {
204 printErr(QStringLiteral(
"File %1 won't be updated: it contains translation but the"
205 " target language is not recognized\n").arg(fileName));
210 if (!targetLanguage.isEmpty())
211 tor.setLanguageCode(targetLanguage);
213 tor.setLanguageCode(Translator::guessLanguageCodeFromFileName(fileName));
214 if (!sourceLanguage.isEmpty())
215 tor.setSourceLanguageCode(sourceLanguage);
217 tor.makeFileNamesAbsolute(QFileInfo(fileName).absoluteDir());
218 if (options & NoLocations)
219 tor.setLocationsType(Translator::NoLocations);
220 else if (options & RelativeLocations)
221 tor.setLocationsType(Translator::RelativeLocations);
222 else if (options & AbsoluteLocations)
223 tor.setLocationsType(Translator::AbsoluteLocations);
224 if (options & Verbose)
225 printOut(QStringLiteral(
"Updating '%1'...\n").arg(fn));
227 UpdateOptions theseOptions = options;
228 if (tor.locationsType() == Translator::NoLocations)
229 theseOptions |= NoLocations;
230 Translator out = merge(tor, fetchedTor, aliens, theseOptions, err);
232 if ((options & Verbose) && !err.isEmpty()) {
236 if (options & PluralOnly) {
237 if (options & Verbose)
238 printOut(QStringLiteral(
"Stripping non plural forms in '%1'...\n").arg(fn));
239 out.stripNonPluralForms();
241 if (options & NoObsolete)
242 out.stripObsoleteMessages();
243 out.stripEmptyContexts();
245 out.normalizeTranslations(cd);
246 if (!cd.errors().isEmpty()) {
247 printErr(cd.error());
250 if (!out.save(fileName, cd,
"auto"_L1)) {
251 printErr(cd.error());
258class ProjectProcessor
261 ProjectProcessor(
const QString &sourceLanguage,
262 const QString &targetLanguage)
263 : m_sourceLanguage(sourceLanguage),
264 m_targetLanguage(targetLanguage)
268 bool processProjects(
bool topLevel, UpdateOptions options,
const Projects &projects,
269 bool nestComplain,
Translator *parentTor)
const
272 for (
const Project &prj : projects)
273 ok &= processProject(options, prj, topLevel, nestComplain, parentTor);
279 bool processProject(UpdateOptions options,
const Project &prj,
bool topLevel,
280 bool nestComplain,
Translator *parentTor)
const
283 QString codecForSource = prj.codec.toLower();
284 if (!codecForSource.isEmpty()) {
285 if (codecForSource ==
"utf-16"_L1 || codecForSource ==
"utf16"_L1) {
287 }
else if (codecForSource ==
"utf-8"_L1 || codecForSource ==
"utf8"_L1) {
292 "Codec for source '%1' is invalid.\n"_L1.arg(codecForSource),
293 u"Falling back to UTF-8.\n"_s);
300 const QString projectFile = prj.filePath;
301 const QStringList sources = prj.sources;
304 cd.m_projectRoots = projectRoots(projectFile, sources);
305 QStringList projectRootDirs;
306 for (
const auto& dir : std::as_const(cd.m_projectRoots))
307 projectRootDirs.append(dir);
308 cd.m_includePath = prj.includePaths;
309 cd.m_excludes = prj.excluded;
313 if (prj.translations) {
314 tsFiles = *prj.translations;
317 printWarning(options, u"Existing top level."_s,
318 "TS files from command line will "
319 "override TRANSLATIONS in %1.\n"_L1.arg(projectFile),
320 u"Terminating the operation.\n"_s);
324 }
else if (nestComplain) {
325 printWarning(options,
326 "TS files from command line "
327 "prevent recursing into %1.\n"_L1.arg(projectFile));
331 if (tsFiles.isEmpty()) {
338 ok = processProjects(
false, options, prj.subProjects,
false, &tor);
339 processSources(tor, sources, cd);
340 ok &= updateTsFiles(tor, tsFiles, QStringList(), m_sourceLanguage, m_targetLanguage,
349 printWarning(options, u"no TS files specified."_s,
350 "Only diagnostics will be produced for %1.\n"_L1.arg(projectFile),
351 u"Terminating the operation.\n"_s);
356 ok = processProjects(
false, options, prj.subProjects, nestComplain, &tor);
357 processSources(tor, sources, cd);
359 ok = processProjects(
false, options, prj.subProjects, nestComplain, parentTor);
360 processSources(*parentTor, sources, cd);
365 QString m_sourceLanguage;
366 QString m_targetLanguage;
375 if (!QFile::exists(resourceFile))
376 return QStringList();
379 if (!readFileContent(resourceFile, &content, &errStr)) {
380 printErr(QStringLiteral(
"lupdate error: Can not read %1: %2\n").arg(resourceFile, errStr));
381 return QStringList();
383 ReadQrcResult rqr = readQrcFile(resourceFile, content);
384 if (rqr.hasError()) {
385 printErr(QStringLiteral(
"lupdate error: %1:%2: %3\n")
386 .arg(resourceFile, QString::number(rqr.line), rqr.errorString));
393 const QStringList &tsFileNames,
394 const QStringList &alienFiles,
395 const QString &sourceLanguage,
396 const QString &targetLanguage,
397 UpdateOptions options)
401 removeExcludedSources(projectDescription);
402 for (Project &project : projectDescription)
403 expandQrcFiles(project);
405 ProjectProcessor projectProcessor(sourceLanguage, targetLanguage);
406 if (!tsFileNames.isEmpty()) {
408 ok &= projectProcessor.processProjects(
true, options, projectDescription,
true, &fetchedTor);
410 ok &= updateTsFiles(fetchedTor, tsFileNames, alienFiles,
411 sourceLanguage, targetLanguage, options);
414 ok &= projectProcessor.processProjects(
true, options, projectDescription,
false,
nullptr);
420 const QStringList &sourceFiles,
421 const QStringList &tsFileNames,
422 const QStringList &alienFiles,
423 const QSet<QString> &projectRoots,
424 const QStringList &includePath,
425 const QMultiHash<QString, QString> &allCSources,
426 const QString &sourceLanguage,
427 const QString &targetLanguage,
428 UpdateOptions options)
434 cd.m_projectRoots = projectRoots;
435 cd.m_includePath = includePath;
436 cd.m_allCSources = allCSources;
437 processSources(fetchedTor, sourceFiles, cd);
438 return updateTsFiles(fetchedTor, tsFileNames, alienFiles,
439 sourceLanguage, targetLanguage, options);
const TranslatorMessage & constMessage(int i) const
std::vector< Project > Projects
bool processSourceFiles(const QStringList &sourceFiles, const QStringList &tsFileNames, const QStringList &alienFiles, const QSet< QString > &projectRoots, const QStringList &includePath, const QMultiHash< QString, QString > &allCSources, const QString &sourceLanguage, const QString &targetLanguage, UpdateOptions options)
bool processProjectDescription(Projects &projectDescription, const QStringList &tsFileNames, const QStringList &alienFiles, const QString &sourceLanguage, const QString &targetLanguage, UpdateOptions options)
QList< QString > QStringList
Constructs a string list that contains the given string, str.