source: trunk/tools/linguist/lupdate/main.cpp@ 561

Last change on this file since 561 was 561, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.1 sources.

File size: 21.9 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the Qt Linguist of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "lupdate.h"
43
44#include <translator.h>
45#include <profileevaluator.h>
46#include <proreader.h>
47
48#include <QtCore/QCoreApplication>
49#include <QtCore/QDebug>
50#include <QtCore/QDir>
51#include <QtCore/QFile>
52#include <QtCore/QFileInfo>
53#include <QtCore/QString>
54#include <QtCore/QStringList>
55#include <QtCore/QTextCodec>
56
57#include <iostream>
58
59static QString m_defaultExtensions;
60
61static void printErr(const QString & out)
62{
63 qWarning("%s", qPrintable(out));
64}
65
66static void printOut(const QString & out)
67{
68 std::cerr << qPrintable(out);
69}
70
71static void recursiveFileInfoList(const QDir &dir,
72 const QSet<QString> &nameFilters, QDir::Filters filter,
73 QFileInfoList *fileinfolist)
74{
75 foreach (const QFileInfo &fi, dir.entryInfoList(filter))
76 if (fi.isDir())
77 recursiveFileInfoList(QDir(fi.absoluteFilePath()), nameFilters, filter, fileinfolist);
78 else if (nameFilters.contains(fi.suffix()))
79 fileinfolist->append(fi);
80}
81
82static void printUsage()
83{
84 printOut(QObject::tr(
85 "Usage:\n"
86 " lupdate [options] [project-file]...\n"
87 " lupdate [options] [source-file|path]... -ts ts-files\n\n"
88 "lupdate is part of Qt's Linguist tool chain. It extracts translatable\n"
89 "messages from Qt UI files, C++, Java and JavaScript/QtScript source code.\n"
90 "Extracted messages are stored in textual translation source files (typically\n"
91 "Qt TS XML). New and modified messages can be merged into existing TS files.\n\n"
92 "Options:\n"
93 " -help Display this information and exit.\n"
94 " -no-obsolete\n"
95 " Drop all obsolete strings.\n"
96 " -extensions <ext>[,<ext>]...\n"
97 " Process files with the given extensions only.\n"
98 " The extension list must be separated with commas, not with whitespace.\n"
99 " Default: '%1'.\n"
100 " -pluralonly\n"
101 " Only include plural form messages.\n"
102 " -silent\n"
103 " Do not explain what is being done.\n"
104 " -no-sort\n"
105 " Do not sort contexts in TS files.\n"
106 " -no-recursive\n"
107 " Do not recursively scan the following directories.\n"
108 " -recursive\n"
109 " Recursively scan the following directories (default).\n"
110 " -I <includepath> or -I<includepath>\n"
111 " Additional location to look for include files.\n"
112 " May be specified multiple times.\n"
113 " -locations {absolute|relative|none}\n"
114 " Specify/override how source code references are saved in TS files.\n"
115 " Default is absolute.\n"
116 " -no-ui-lines\n"
117 " Do not record line numbers in references to UI files.\n"
118 " -disable-heuristic {sametext|similartext|number}\n"
119 " Disable the named merge heuristic. Can be specified multiple times.\n"
120 " -pro <filename>\n"
121 " Name of a .pro file. Useful for files with .pro\n"
122 " file syntax but different file suffix\n"
123 " -source-language <language>[_<region>]\n"
124 " Specify the language of the source strings for new files.\n"
125 " Defaults to POSIX if not specified.\n"
126 " -target-language <language>[_<region>]\n"
127 " Specify the language of the translations for new files.\n"
128 " Guessed from the file name if not specified.\n"
129 " -version\n"
130 " Display the version of lupdate and exit.\n"
131 ).arg(m_defaultExtensions));
132}
133
134static void updateTsFiles(const Translator &fetchedTor, const QStringList &tsFileNames,
135 const QByteArray &codecForTr, const QString &sourceLanguage, const QString &targetLanguage,
136 UpdateOptions options, bool *fail)
137{
138 QDir dir;
139 QString err;
140 foreach (const QString &fileName, tsFileNames) {
141 QString fn = dir.relativeFilePath(fileName);
142 ConversionData cd;
143 Translator tor;
144 cd.m_sortContexts = !(options & NoSort);
145 if (QFile(fileName).exists()) {
146 if (!tor.load(fileName, cd, QLatin1String("auto"))) {
147 printErr(cd.error());
148 *fail = true;
149 continue;
150 }
151 tor.resolveDuplicates();
152 cd.clearErrors();
153 if (!codecForTr.isEmpty() && codecForTr != tor.codecName())
154 qWarning("lupdate warning: Codec for tr() '%s' disagrees with "
155 "existing file's codec '%s'. Expect trouble.",
156 codecForTr.constData(), tor.codecName().constData());
157 if (!targetLanguage.isEmpty() && targetLanguage != tor.languageCode())
158 qWarning("lupdate warning: Specified target language '%s' disagrees with "
159 "existing file's language '%s'. Ignoring.",
160 qPrintable(targetLanguage), qPrintable(tor.languageCode()));
161 if (!sourceLanguage.isEmpty() && sourceLanguage != tor.sourceLanguageCode())
162 qWarning("lupdate warning: Specified source language '%s' disagrees with "
163 "existing file's language '%s'. Ignoring.",
164 qPrintable(sourceLanguage), qPrintable(tor.sourceLanguageCode()));
165 } else {
166 if (!codecForTr.isEmpty())
167 tor.setCodecName(codecForTr);
168 if (!targetLanguage.isEmpty())
169 tor.setLanguageCode(targetLanguage);
170 else
171 tor.setLanguageCode(Translator::guessLanguageCodeFromFileName(fileName));
172 if (!sourceLanguage.isEmpty())
173 tor.setSourceLanguageCode(sourceLanguage);
174 }
175 tor.makeFileNamesAbsolute(QFileInfo(fileName).absoluteDir());
176 if (options & NoLocations)
177 tor.setLocationsType(Translator::NoLocations);
178 else if (options & RelativeLocations)
179 tor.setLocationsType(Translator::RelativeLocations);
180 else if (options & AbsoluteLocations)
181 tor.setLocationsType(Translator::AbsoluteLocations);
182 if (options & Verbose)
183 printOut(QObject::tr("Updating '%1'...\n").arg(fn));
184
185 UpdateOptions theseOptions = options;
186 if (tor.locationsType() == Translator::NoLocations) // Could be set from file
187 theseOptions |= NoLocations;
188 Translator out = merge(tor, fetchedTor, theseOptions, err);
189 if (!codecForTr.isEmpty())
190 out.setCodecName(codecForTr);
191
192 if ((options & Verbose) && !err.isEmpty()) {
193 printOut(err);
194 err.clear();
195 }
196 if (options & PluralOnly) {
197 if (options & Verbose)
198 printOut(QObject::tr("Stripping non plural forms in '%1'...\n").arg(fn));
199 out.stripNonPluralForms();
200 }
201 if (options & NoObsolete)
202 out.stripObsoleteMessages();
203 out.stripEmptyContexts();
204
205 out.normalizeTranslations(cd);
206 if (!cd.errors().isEmpty()) {
207 printErr(cd.error());
208 cd.clearErrors();
209 }
210 if (!out.save(fileName, cd, QLatin1String("auto"))) {
211 printErr(cd.error());
212 *fail = true;
213 }
214 }
215}
216
217int main(int argc, char **argv)
218{
219 QCoreApplication app(argc, argv);
220 m_defaultExtensions = QLatin1String("ui,c,c++,cc,cpp,cxx,ch,h,h++,hh,hpp,hxx");
221
222 QStringList args = app.arguments();
223 QString defaultContext; // This was QLatin1String("@default") before.
224 Translator fetchedTor;
225 QByteArray codecForTr;
226 QByteArray codecForSource;
227 QStringList tsFileNames;
228 QStringList proFiles;
229 QMultiHash<QString, QString> allCSources;
230 QSet<QString> projectRoots;
231 QStringList sourceFiles;
232 QStringList includePath;
233 QString targetLanguage;
234 QString sourceLanguage;
235
236 UpdateOptions options =
237 Verbose | // verbose is on by default starting with Qt 4.2
238 HeuristicSameText | HeuristicSimilarText | HeuristicNumber;
239 int numFiles = 0;
240 bool standardSyntax = true;
241 bool metTsFlag = false;
242 bool recursiveScan = true;
243
244 QString extensions = m_defaultExtensions;
245 QSet<QString> extensionsNameFilters;
246
247 for (int i = 1; i < argc; ++i) {
248 if (args.at(i) == QLatin1String("-ts"))
249 standardSyntax = false;
250 }
251
252 for (int i = 1; i < argc; ++i) {
253 QString arg = args.at(i);
254 if (arg == QLatin1String("-help")
255 || arg == QLatin1String("--help")
256 || arg == QLatin1String("-h")) {
257 printUsage();
258 return 0;
259 } else if (arg == QLatin1String("-pluralonly")) {
260 options |= PluralOnly;
261 continue;
262 } else if (arg == QLatin1String("-noobsolete")
263 || arg == QLatin1String("-no-obsolete")) {
264 options |= NoObsolete;
265 continue;
266 } else if (arg == QLatin1String("-silent")) {
267 options &= ~Verbose;
268 continue;
269 } else if (arg == QLatin1String("-target-language")) {
270 ++i;
271 if (i == argc) {
272 qWarning("The option -target-language requires a parameter.");
273 return 1;
274 }
275 targetLanguage = args[i];
276 continue;
277 } else if (arg == QLatin1String("-source-language")) {
278 ++i;
279 if (i == argc) {
280 qWarning("The option -source-language requires a parameter.");
281 return 1;
282 }
283 sourceLanguage = args[i];
284 continue;
285 } else if (arg == QLatin1String("-disable-heuristic")) {
286 ++i;
287 if (i == argc) {
288 qWarning("The option -disable-heuristic requires a parameter.");
289 return 1;
290 }
291 arg = args[i];
292 if (arg == QLatin1String("sametext")) {
293 options &= ~HeuristicSameText;
294 } else if (arg == QLatin1String("similartext")) {
295 options &= ~HeuristicSimilarText;
296 } else if (arg == QLatin1String("number")) {
297 options &= ~HeuristicNumber;
298 } else {
299 qWarning("Invalid heuristic name passed to -disable-heuristic.");
300 return 1;
301 }
302 continue;
303 } else if (arg == QLatin1String("-locations")) {
304 ++i;
305 if (i == argc) {
306 qWarning("The option -locations requires a parameter.");
307 return 1;
308 }
309 if (args[i] == QLatin1String("none")) {
310 options |= NoLocations;
311 } else if (args[i] == QLatin1String("relative")) {
312 options |= RelativeLocations;
313 } else if (args[i] == QLatin1String("absolute")) {
314 options |= AbsoluteLocations;
315 } else {
316 qWarning("Invalid parameter passed to -locations.");
317 return 1;
318 }
319 continue;
320 } else if (arg == QLatin1String("-no-ui-lines")) {
321 options |= NoUiLines;
322 continue;
323 } else if (arg == QLatin1String("-verbose")) {
324 options |= Verbose;
325 continue;
326 } else if (arg == QLatin1String("-no-recursive")) {
327 recursiveScan = false;
328 continue;
329 } else if (arg == QLatin1String("-recursive")) {
330 recursiveScan = true;
331 continue;
332 } else if (arg == QLatin1String("-no-sort")
333 || arg == QLatin1String("-nosort")) {
334 options |= NoSort;
335 continue;
336 } else if (arg == QLatin1String("-version")) {
337 printOut(QObject::tr("lupdate version %1\n").arg(QLatin1String(QT_VERSION_STR)));
338 return 0;
339 } else if (arg == QLatin1String("-ts")) {
340 metTsFlag = true;
341 continue;
342 } else if (arg == QLatin1String("-extensions")) {
343 ++i;
344 if (i == argc) {
345 qWarning("The -extensions option should be followed by an extension list.");
346 return 1;
347 }
348 extensions = args[i];
349 continue;
350 } else if (arg == QLatin1String("-pro")) {
351 ++i;
352 if (i == argc) {
353 qWarning("The -pro option should be followed by a filename of .pro file.");
354 return 1;
355 }
356 proFiles += args[i];
357 numFiles++;
358 continue;
359 } else if (arg.startsWith(QLatin1String("-I"))) {
360 if (arg.length() == 2) {
361 ++i;
362 if (i == argc) {
363 qWarning("The -I option should be followed by a path.");
364 return 1;
365 }
366 includePath += args[i];
367 } else {
368 includePath += args[i].mid(2);
369 }
370 continue;
371 } else if (arg.startsWith(QLatin1String("-")) && arg != QLatin1String("-")) {
372 qWarning("Unrecognized option '%s'", qPrintable(arg));
373 return 1;
374 }
375
376 numFiles++;
377
378 QString fullText;
379
380 codecForTr.clear();
381 codecForSource.clear();
382
383 if (metTsFlag) {
384 bool found = false;
385 foreach (const Translator::FileFormat &fmt, Translator::registeredFileFormats()) {
386 if (arg.endsWith(QLatin1Char('.') + fmt.extension, Qt::CaseInsensitive)) {
387 QFileInfo fi(arg);
388 if (!fi.exists() || fi.isWritable()) {
389 tsFileNames.append(QFileInfo(arg).absoluteFilePath());
390 } else {
391 qWarning("lupdate warning: For some reason, '%s' is not writable.\n",
392 qPrintable(arg));
393 }
394 found = true;
395 break;
396 }
397 }
398 if (!found) {
399 qWarning("lupdate error: File '%s' has no recognized extension\n",
400 qPrintable(arg));
401 return 1;
402 }
403 } else if (arg.endsWith(QLatin1String(".pro"), Qt::CaseInsensitive)
404 || arg.endsWith(QLatin1String(".pri"), Qt::CaseInsensitive)) {
405 proFiles << arg;
406 } else {
407 QFileInfo fi(arg);
408 if (!fi.exists()) {
409 qWarning("lupdate error: File '%s' does not exists\n", qPrintable(arg));
410 return 1;
411 } else if (fi.isDir()) {
412 if (options & Verbose)
413 printOut(QObject::tr("Scanning directory '%1'...").arg(arg));
414 QDir dir = QDir(fi.filePath());
415 projectRoots.insert(dir.absolutePath() + QLatin1Char('/'));
416 if (extensionsNameFilters.isEmpty()) {
417 foreach (QString ext, extensions.split(QLatin1Char(','))) {
418 ext = ext.trimmed();
419 if (ext.startsWith(QLatin1Char('.')))
420 ext.remove(0, 1);
421 extensionsNameFilters.insert(ext);
422 }
423 }
424 QDir::Filters filters = QDir::Files | QDir::NoSymLinks;
425 if (recursiveScan)
426 filters |= QDir::AllDirs | QDir::NoDotAndDotDot;
427 QFileInfoList fileinfolist;
428 recursiveFileInfoList(dir, extensionsNameFilters, filters, &fileinfolist);
429 int scanRootLen = dir.absolutePath().length();
430 foreach (const QFileInfo &fi, fileinfolist) {
431 QString fn = QDir::cleanPath(fi.absoluteFilePath());
432 sourceFiles << fn;
433
434 if (!fn.endsWith(QLatin1String(".java"))
435 && !fn.endsWith(QLatin1String(".ui"))
436 && !fn.endsWith(QLatin1String(".js"))
437 && !fn.endsWith(QLatin1String(".qs"))) {
438 int offset = 0;
439 int depth = 0;
440 do {
441 offset = fn.lastIndexOf(QLatin1Char('/'), offset - 1);
442 QString ffn = fn.mid(offset + 1);
443 allCSources.insert(ffn, fn);
444 } while (++depth < 3 && offset > scanRootLen);
445 }
446 }
447 } else {
448 sourceFiles << QDir::cleanPath(fi.absoluteFilePath());;
449 }
450 }
451 } // for args
452
453 foreach (const QString &proFile, proFiles)
454 projectRoots.insert(QDir::cleanPath(QFileInfo(proFile).absolutePath()) + QLatin1Char('/'));
455
456 bool firstPass = true;
457 bool fail = false;
458 while (firstPass || !proFiles.isEmpty()) {
459 ConversionData cd;
460 cd.m_defaultContext = defaultContext;
461 cd.m_noUiLines = options & NoUiLines;
462 cd.m_projectRoots = projectRoots;
463 cd.m_includePath = includePath;
464 cd.m_allCSources = allCSources;
465
466 QStringList tsFiles = tsFileNames;
467 if (proFiles.count() > 0) {
468 QFileInfo pfi(proFiles.takeFirst());
469 QHash<QByteArray, QStringList> variables;
470
471 ProFileEvaluator visitor;
472 visitor.setVerbose(options & Verbose);
473
474 ProFile pro(pfi.absoluteFilePath());
475 if (!visitor.queryProFile(&pro))
476 return 2;
477 if (!visitor.accept(&pro))
478 return 2;
479
480 if (visitor.templateType() == ProFileEvaluator::TT_Subdirs) {
481 QDir proDir(pfi.absoluteDir());
482 foreach (const QString &subdir, visitor.values(QLatin1String("SUBDIRS"))) {
483 QString subPro = QDir::cleanPath(proDir.absoluteFilePath(subdir));
484 QFileInfo subInfo(subPro);
485 if (subInfo.isDir())
486 proFiles << (subPro + QLatin1Char('/')
487 + subInfo.fileName() + QLatin1String(".pro"));
488 else
489 proFiles << subPro;
490 }
491 continue;
492 }
493
494 cd.m_includePath += visitor.values(QLatin1String("INCLUDEPATH"));
495
496 evaluateProFile(visitor, &variables, pfi.absolutePath());
497
498 sourceFiles = variables.value("SOURCES");
499
500 QStringList tmp = variables.value("CODECFORTR");
501 if (!tmp.isEmpty() && !tmp.first().isEmpty()) {
502 codecForTr = tmp.first().toLatin1();
503 fetchedTor.setCodecName(codecForTr);
504 cd.m_outputCodec = codecForTr;
505 }
506 tmp = variables.value("CODECFORSRC");
507 if (!tmp.isEmpty() && !tmp.first().isEmpty()) {
508 codecForSource = tmp.first().toLatin1();
509 if (!QTextCodec::codecForName(codecForSource))
510 qWarning("lupdate warning: Codec for source '%s' is invalid. Falling back to codec for tr().",
511 codecForSource.constData());
512 else
513 cd.m_codecForSource = codecForSource;
514 }
515
516 tsFiles += variables.value("TRANSLATIONS");
517 }
518
519 QStringList sourceFilesCpp;
520 for (QStringList::iterator it = sourceFiles.begin(); it != sourceFiles.end(); ++it) {
521 if (it->endsWith(QLatin1String(".java"), Qt::CaseInsensitive))
522 loadJava(fetchedTor, *it, cd);
523 else if (it->endsWith(QLatin1String(".ui"), Qt::CaseInsensitive)
524 || it->endsWith(QLatin1String(".jui"), Qt::CaseInsensitive))
525 loadUI(fetchedTor, *it, cd);
526 else if (it->endsWith(QLatin1String(".js"), Qt::CaseInsensitive)
527 || it->endsWith(QLatin1String(".qs"), Qt::CaseInsensitive))
528 loadQScript(fetchedTor, *it, cd);
529 else
530 sourceFilesCpp << *it;
531 }
532 loadCPP(fetchedTor, sourceFilesCpp, cd);
533 if (!cd.error().isEmpty())
534 printOut(cd.error());
535
536 tsFiles.sort();
537 tsFiles.removeDuplicates();
538
539 if (!tsFiles.isEmpty())
540 updateTsFiles(fetchedTor, tsFiles, codecForTr, sourceLanguage, targetLanguage, options, &fail);
541
542 firstPass = false;
543 }
544
545 if (numFiles == 0) {
546 printUsage();
547 return 1;
548 }
549
550 return fail ? 1 : 0;
551}
Note: See TracBrowser for help on using the repository browser.