1/****************************************************************************
2**
3** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
5**
6** This file is part of the Qt Linguist of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Digia. For licensing terms and
14** conditions see http://qt.digia.com/licensing. For further information
15** use the contact form at http://qt.digia.com/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 2.1 requirements
23** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24**
25** In addition, as a special exception, Digia gives you certain additional
26** rights. These rights are described in the Digia Qt LGPL Exception
27** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28**
29** GNU General Public License Usage
30** Alternatively, this file may be used under the terms of the GNU
31** General Public License version 3.0 as published by the Free Software
32** Foundation and appearing in the file LICENSE.GPL included in the
33** packaging of this file. Please review the following information to
34** ensure the GNU General Public License version 3.0 requirements will be
35** met: http://www.gnu.org/copyleft/gpl.html.
36**
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "translator.h"
43
44#include <profileparser.h>
45#include <profileevaluator.h>
46
47#ifndef QT_BOOTSTRAPPED
48#include <QtCore/QCoreApplication>
49#include <QtCore/QTranslator>
50#endif
51#include <QtCore/QDebug>
52#include <QtCore/QDir>
53#include <QtCore/QFile>
54#include <QtCore/QFileInfo>
55#include <QtCore/QRegExp>
56#include <QtCore/QString>
57#include <QtCore/QStringList>
58#include <QtCore/QTextStream>
59
60QT_USE_NAMESPACE
61
62#ifdef QT_BOOTSTRAPPED
63static QString binDir;
64
65static void initBinaryDir(
66#ifndef Q_OS_WIN
67 const char *argv0
68#endif
69 );
70
71struct LR {
72 static inline QString tr(const char *sourceText, const char *comment = 0)
73 {
74 return QCoreApplication::translate("LRelease", sourceText, comment);
75 }
76};
77#else
78class LR {
79 Q_DECLARE_TR_FUNCTIONS(LRelease)
80};
81#endif
82
83static void printOut(const QString & out)
84{
85 QTextStream stream(stdout);
86 stream << out;
87}
88
89static void printErr(const QString & out)
90{
91 QTextStream stream(stderr);
92 stream << out;
93}
94
95static void printUsage()
96{
97 printOut(LR::tr(
98 "Usage:\n"
99 " lrelease [options] project-file\n"
100 " lrelease [options] ts-files [-qm qm-file]\n\n"
101 "lrelease is part of Qt's Linguist tool chain. It can be used as a\n"
102 "stand-alone tool to convert XML-based translations files in the TS\n"
103 "format into the 'compiled' QM format used by QTranslator objects.\n\n"
104 "Options:\n"
105 " -help Display this information and exit\n"
106 " -idbased\n"
107 " Use IDs instead of source strings for message keying\n"
108 " -compress\n"
109 " Compress the QM files\n"
110 " -nounfinished\n"
111 " Do not include unfinished translations\n"
112 " -removeidentical\n"
113 " If the translated text is the same as\n"
114 " the source text, do not include the message\n"
115 " -markuntranslated <prefix>\n"
116 " If a message has no real translation, use the source text\n"
117 " prefixed with the given string instead\n"
118 " -silent\n"
119 " Do not explain what is being done\n"
120 " -version\n"
121 " Display the version of lrelease and exit\n"
122 ));
123}
124
125static bool loadTsFile(Translator &tor, const QString &tsFileName, bool /* verbose */)
126{
127 ConversionData cd;
128 bool ok = tor.load(tsFileName, cd, QLatin1String("auto"));
129 if (!ok) {
130 printErr(LR::tr("lrelease error: %1").arg(cd.error()));
131 } else {
132 if (!cd.errors().isEmpty())
133 printOut(cd.error());
134 }
135 cd.clearErrors();
136 return ok;
137}
138
139static bool releaseTranslator(Translator &tor, const QString &qmFileName,
140 ConversionData &cd, bool removeIdentical)
141{
142 tor.reportDuplicates(tor.resolveDuplicates(), qmFileName, cd.isVerbose());
143
144 if (cd.isVerbose())
145 printOut(LR::tr("Updating '%1'...\n").arg(qmFileName));
146 if (removeIdentical) {
147 if (cd.isVerbose())
148 printOut(LR::tr("Removing translations equal to source text in '%1'...\n").arg(qmFileName));
149 tor.stripIdenticalSourceTranslations();
150 }
151
152 QFile file(qmFileName);
153 if (!file.open(QIODevice::WriteOnly)) {
154 printErr(LR::tr("lrelease error: cannot create '%1': %2\n")
155 .arg(qmFileName, file.errorString()));
156 return false;
157 }
158
159 tor.normalizeTranslations(cd);
160 bool ok = saveQM(tor, file, cd);
161 file.close();
162
163 if (!ok) {
164 printErr(LR::tr("lrelease error: cannot save '%1': %2")
165 .arg(qmFileName, cd.error()));
166 } else if (!cd.errors().isEmpty()) {
167 printOut(cd.error());
168 }
169 cd.clearErrors();
170 return ok;
171}
172
173static bool releaseTsFile(const QString& tsFileName,
174 ConversionData &cd, bool removeIdentical)
175{
176 Translator tor;
177 if (!loadTsFile(tor, tsFileName, cd.isVerbose()))
178 return false;
179
180 QString qmFileName = tsFileName;
181 foreach (const Translator::FileFormat &fmt, Translator::registeredFileFormats()) {
182 if (qmFileName.endsWith(QLatin1Char('.') + fmt.extension)) {
183 qmFileName.chop(fmt.extension.length() + 1);
184 break;
185 }
186 }
187 qmFileName += QLatin1String(".qm");
188
189 return releaseTranslator(tor, qmFileName, cd, removeIdentical);
190}
191
192static void print(const QString &fileName, int lineNo, const QString &msg)
193{
194 if (lineNo)
195 printErr(QString::fromLatin1("%2(%1): %3").arg(lineNo).arg(fileName, msg));
196 else
197 printErr(msg);
198}
199
200class ParseHandler : public ProFileParserHandler {
201public:
202 virtual void parseError(const QString &fileName, int lineNo, const QString &msg)
203 { if (verbose) print(fileName, lineNo, msg); }
204
205 bool verbose;
206};
207
208class EvalHandler : public ProFileEvaluatorHandler {
209public:
210 virtual void configError(const QString &msg)
211 { printErr(msg); }
212 virtual void evalError(const QString &fileName, int lineNo, const QString &msg)
213 { if (verbose) print(fileName, lineNo, msg); }
214 virtual void fileMessage(const QString &msg)
215 { printErr(msg); }
216
217 virtual void aboutToEval(ProFile *, ProFile *, EvalFileType) {}
218 virtual void doneWithEval(ProFile *) {}
219
220 bool verbose;
221};
222
223static ParseHandler parseHandler;
224static EvalHandler evalHandler;
225
226int main(int argc, char **argv)
227{
228#ifdef QT_BOOTSTRAPPED
229 initBinaryDir(
230#ifndef Q_OS_WIN
231 argv[0]
232#endif
233 );
234#else
235 QCoreApplication app(argc, argv);
236#ifndef Q_OS_WIN32
237 QTranslator translator;
238 QTranslator qtTranslator;
239 QString sysLocale = QLocale::system().name();
240 QString resourceDir = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
241 if (translator.load(QLatin1String("linguist_") + sysLocale, resourceDir)
242 && qtTranslator.load(QLatin1String("qt_") + sysLocale, resourceDir)) {
243 app.installTranslator(&translator);
244 app.installTranslator(&qtTranslator);
245 }
246#endif // Q_OS_WIN32
247#endif // QT_BOOTSTRAPPED
248
249 ConversionData cd;
250 cd.m_verbose = true; // the default is true starting with Qt 4.2
251 bool removeIdentical = false;
252 Translator tor;
253 QStringList inputFiles;
254 QString outputFile;
255
256 for (int i = 1; i < argc; ++i) {
257 if (!strcmp(argv[i], "-compress")) {
258 cd.m_saveMode = SaveStripped;
259 continue;
260 } else if (!strcmp(argv[i], "-idbased")) {
261 cd.m_idBased = true;
262 continue;
263 } else if (!strcmp(argv[i], "-nocompress")) {
264 cd.m_saveMode = SaveEverything;
265 continue;
266 } else if (!strcmp(argv[i], "-removeidentical")) {
267 removeIdentical = true;
268 continue;
269 } else if (!strcmp(argv[i], "-nounfinished")) {
270 cd.m_ignoreUnfinished = true;
271 continue;
272 } else if (!strcmp(argv[i], "-markuntranslated")) {
273 if (i == argc - 1) {
274 printUsage();
275 return 1;
276 }
277 cd.m_unTrPrefix = QString::fromLocal8Bit(argv[++i]);
278 } else if (!strcmp(argv[i], "-silent")) {
279 cd.m_verbose = false;
280 continue;
281 } else if (!strcmp(argv[i], "-verbose")) {
282 cd.m_verbose = true;
283 continue;
284 } else if (!strcmp(argv[i], "-version")) {
285 printOut(LR::tr("lrelease version %1\n").arg(QLatin1String(QT_VERSION_STR)));
286 return 0;
287 } else if (!strcmp(argv[i], "-qm")) {
288 if (i == argc - 1) {
289 printUsage();
290 return 1;
291 }
292 outputFile = QString::fromLocal8Bit(argv[++i]);
293 } else if (!strcmp(argv[i], "-help")) {
294 printUsage();
295 return 0;
296 } else if (argv[i][0] == '-') {
297 printUsage();
298 return 1;
299 } else {
300 inputFiles << QString::fromLocal8Bit(argv[i]);
301 }
302 }
303
304 if (inputFiles.isEmpty()) {
305 printUsage();
306 return 1;
307 }
308
309 foreach (const QString &inputFile, inputFiles) {
310 if (inputFile.endsWith(QLatin1String(".pro"), Qt::CaseInsensitive)
311 || inputFile.endsWith(QLatin1String(".pri"), Qt::CaseInsensitive)) {
312 QFileInfo fi(inputFile);
313
314 parseHandler.verbose = evalHandler.verbose = cd.isVerbose();
315 ProFileOption option;
316#ifdef QT_BOOTSTRAPPED
317 option.initProperties(binDir + QLatin1String("/qmake"));
318#else
319 option.initProperties(app.applicationDirPath() + QLatin1String("/qmake"));
320#endif
321 ProFileParser parser(0, &parseHandler);
322 ProFileEvaluator visitor(&option, &parser, &evalHandler);
323
324 ProFile *pro;
325 if (!(pro = parser.parsedProFile(QDir::cleanPath(fi.absoluteFilePath())))) {
326 printErr(LR::tr(
327 "lrelease error: cannot read project file '%1'.\n")
328 .arg(inputFile));
329 continue;
330 }
331 if (!visitor.accept(pro)) {
332 printErr(LR::tr(
333 "lrelease error: cannot process project file '%1'.\n")
334 .arg(inputFile));
335 pro->deref();
336 continue;
337 }
338 pro->deref();
339
340 QStringList translations = visitor.values(QLatin1String("TRANSLATIONS"));
341 if (translations.isEmpty()) {
342 printErr(LR::tr(
343 "lrelease warning: Met no 'TRANSLATIONS' entry in project file '%1'\n")
344 .arg(inputFile));
345 } else {
346 QDir proDir(fi.absolutePath());
347 foreach (const QString &trans, translations)
348 if (!releaseTsFile(QFileInfo(proDir, trans).filePath(), cd, removeIdentical))
349 return 1;
350 }
351 } else {
352 if (outputFile.isEmpty()) {
353 if (!releaseTsFile(inputFile, cd, removeIdentical))
354 return 1;
355 } else {
356 if (!loadTsFile(tor, inputFile, cd.isVerbose()))
357 return 1;
358 }
359 }
360 }
361
362 if (!outputFile.isEmpty())
363 return releaseTranslator(tor, outputFile, cd, removeIdentical) ? 0 : 1;
364
365 return 0;
366}
367
368#ifdef QT_BOOTSTRAPPED
369
370#ifdef Q_OS_WIN
371# include <windows.h>
372#endif
373
374static void initBinaryDir(
375#ifndef Q_OS_WIN
376 const char *_argv0
377#endif
378 )
379{
380#ifdef Q_OS_WIN
381 wchar_t module_name[MAX_PATH];
382 GetModuleFileName(0, module_name, MAX_PATH);
383 QFileInfo filePath = QString::fromWCharArray(module_name);
384 binDir = filePath.path();
385#else
386 QString argv0 = QFile::decodeName(QByteArray(_argv0));
387 QString absPath;
388
389 if (!argv0.isEmpty() && argv0.at(0) == QLatin1Char('/')) {
390 /*
391 If argv0 starts with a slash, it is already an absolute
392 file path.
393 */
394 absPath = argv0;
395 } else if (argv0.contains(QLatin1Char('/'))) {
396 /*
397 If argv0 contains one or more slashes, it is a file path
398 relative to the current directory.
399 */
400 absPath = QDir::current().absoluteFilePath(argv0);
401 } else {
402 /*
403 Otherwise, the file path has to be determined using the
404 PATH environment variable.
405 */
406 QByteArray pEnv = qgetenv("PATH");
407 QDir currentDir = QDir::current();
408 QStringList paths = QString::fromLocal8Bit(pEnv.constData()).split(QLatin1String(":"));
409 for (QStringList::const_iterator p = paths.constBegin(); p != paths.constEnd(); ++p) {
410 if ((*p).isEmpty())
411 continue;
412 QString candidate = currentDir.absoluteFilePath(*p + QLatin1Char('/') + argv0);
413 QFileInfo candidate_fi(candidate);
414 if (candidate_fi.exists() && !candidate_fi.isDir()) {
415 binDir = candidate_fi.canonicalPath();
416 return;
417 }
418 }
419 return;
420 }
421
422 QFileInfo fi(absPath);
423 if (fi.exists())
424 binDir = fi.canonicalPath();
425#endif
426}
427
428#endif // QT_BOOTSTRAPPED
429