1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Linguist of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "translator.h"
30
31#include <profileutils.h>
32#include <projectdescriptionreader.h>
33#include <runqttool.h>
34
35#ifndef QT_BOOTSTRAPPED
36#include <QtCore/QCoreApplication>
37#include <QtCore/QTranslator>
38#endif
39#include <QtCore/QDebug>
40#include <QtCore/QDir>
41#include <QtCore/QFile>
42#include <QtCore/QFileInfo>
43#include <QtCore/QRegExp>
44#include <QtCore/QString>
45#include <QtCore/QStringList>
46#include <QtCore/QTextStream>
47#include <QtCore/QLibraryInfo>
48
49QT_USE_NAMESPACE
50
51#ifdef QT_BOOTSTRAPPED
52struct LR {
53 static inline QString tr(const char *sourceText, const char *comment = 0)
54 {
55 return QCoreApplication::translate("LRelease", sourceText, comment);
56 }
57};
58#else
59class LR {
60 Q_DECLARE_TR_FUNCTIONS(LRelease)
61};
62#endif
63
64static void printOut(const QString & out)
65{
66 QTextStream stream(stdout);
67 stream << out;
68}
69
70static void printErr(const QString & out)
71{
72 QTextStream stream(stderr);
73 stream << out;
74}
75
76static void printUsage()
77{
78 printOut(out: LR::tr(
79 sourceText: "Usage:\n"
80 " lrelease [options] -project project-file\n"
81 " lrelease [options] ts-files [-qm qm-file]\n\n"
82 "lrelease is part of Qt's Linguist tool chain. It can be used as a\n"
83 "stand-alone tool to convert XML-based translations files in the TS\n"
84 "format into the 'compiled' QM format used by QTranslator objects.\n\n"
85 "Passing .pro files to lrelease is deprecated.\n"
86 "Please use the lrelease-pro tool instead, or use qmake's lrelease.prf\n"
87 "feature.\n\n"
88 "Options:\n"
89 " -help Display this information and exit\n"
90 " -idbased\n"
91 " Use IDs instead of source strings for message keying\n"
92 " -compress\n"
93 " Compress the QM files\n"
94 " -nounfinished\n"
95 " Do not include unfinished translations\n"
96 " -removeidentical\n"
97 " If the translated text is the same as\n"
98 " the source text, do not include the message\n"
99 " -markuntranslated <prefix>\n"
100 " If a message has no real translation, use the source text\n"
101 " prefixed with the given string instead\n"
102 " -project <filename>\n"
103 " Name of a file containing the project's description in JSON format.\n"
104 " Such a file may be generated from a .pro file using the lprodump tool.\n"
105 " -silent\n"
106 " Do not explain what is being done\n"
107 " -version\n"
108 " Display the version of lrelease and exit\n"
109 ));
110}
111
112static bool loadTsFile(Translator &tor, const QString &tsFileName, bool /* verbose */)
113{
114 ConversionData cd;
115 bool ok = tor.load(filename: tsFileName, err&: cd, format: QLatin1String("auto"));
116 if (!ok) {
117 printErr(out: LR::tr(sourceText: "lrelease error: %1").arg(a: cd.error()));
118 } else {
119 if (!cd.errors().isEmpty())
120 printOut(out: cd.error());
121 }
122 cd.clearErrors();
123 return ok;
124}
125
126static bool releaseTranslator(Translator &tor, const QString &qmFileName,
127 ConversionData &cd, bool removeIdentical)
128{
129 tor.reportDuplicates(dupes: tor.resolveDuplicates(), fileName: qmFileName, verbose: cd.isVerbose());
130
131 if (cd.isVerbose())
132 printOut(out: LR::tr(sourceText: "Updating '%1'...\n").arg(a: qmFileName));
133 if (removeIdentical) {
134 if (cd.isVerbose())
135 printOut(out: LR::tr(sourceText: "Removing translations equal to source text in '%1'...\n").arg(a: qmFileName));
136 tor.stripIdenticalSourceTranslations();
137 }
138
139 QFile file(qmFileName);
140 if (!file.open(flags: QIODevice::WriteOnly)) {
141 printErr(out: LR::tr(sourceText: "lrelease error: cannot create '%1': %2\n")
142 .arg(args: qmFileName, args: file.errorString()));
143 return false;
144 }
145
146 tor.normalizeTranslations(cd);
147 bool ok = saveQM(translator: tor, dev&: file, cd);
148 file.close();
149
150 if (!ok) {
151 printErr(out: LR::tr(sourceText: "lrelease error: cannot save '%1': %2")
152 .arg(args: qmFileName, args: cd.error()));
153 } else if (!cd.errors().isEmpty()) {
154 printOut(out: cd.error());
155 }
156 cd.clearErrors();
157 return ok;
158}
159
160static bool releaseTsFile(const QString& tsFileName,
161 ConversionData &cd, bool removeIdentical)
162{
163 Translator tor;
164 if (!loadTsFile(tor, tsFileName, cd.isVerbose()))
165 return false;
166
167 QString qmFileName = tsFileName;
168 foreach (const Translator::FileFormat &fmt, Translator::registeredFileFormats()) {
169 if (qmFileName.endsWith(s: QLatin1Char('.') + fmt.extension)) {
170 qmFileName.chop(n: fmt.extension.length() + 1);
171 break;
172 }
173 }
174 qmFileName += QLatin1String(".qm");
175
176 return releaseTranslator(tor, qmFileName, cd, removeIdentical);
177}
178
179static QStringList translationsFromProjects(const Projects &projects, bool topLevel);
180
181static QStringList translationsFromProject(const Project &project, bool topLevel)
182{
183 QStringList result;
184 if (project.translations)
185 result = *project.translations;
186 result << translationsFromProjects(projects: project.subProjects, topLevel: false);
187 if (topLevel && result.isEmpty()) {
188 printErr(out: LR::tr(sourceText: "lrelease warning: Met no 'TRANSLATIONS' entry in project file '%1'\n")
189 .arg(a: project.filePath));
190 }
191 return result;
192}
193
194static QStringList translationsFromProjects(const Projects &projects, bool topLevel = true)
195{
196 QStringList result;
197 for (const Project &p : projects)
198 result << translationsFromProject(project: p, topLevel);
199 return result;
200}
201
202int main(int argc, char **argv)
203{
204 QCoreApplication app(argc, argv);
205#ifndef QT_BOOTSTRAPPED
206#ifndef Q_OS_WIN32
207 QTranslator translator;
208 QTranslator qtTranslator;
209 QString sysLocale = QLocale::system().name();
210 QString resourceDir = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
211 if (translator.load(filename: QLatin1String("linguist_") + sysLocale, directory: resourceDir)
212 && qtTranslator.load(filename: QLatin1String("qt_") + sysLocale, directory: resourceDir)) {
213 app.installTranslator(messageFile: &translator);
214 app.installTranslator(messageFile: &qtTranslator);
215 }
216#endif // Q_OS_WIN32
217#endif // QT_BOOTSTRAPPED
218
219 ConversionData cd;
220 cd.m_verbose = true; // the default is true starting with Qt 4.2
221 bool removeIdentical = false;
222 Translator tor;
223 QStringList inputFiles;
224 QString outputFile;
225 QString projectDescriptionFile;
226
227 for (int i = 1; i < argc; ++i) {
228 if (!strcmp(s1: argv[i], s2: "-compress")) {
229 cd.m_saveMode = SaveStripped;
230 continue;
231 } else if (!strcmp(s1: argv[i], s2: "-idbased")) {
232 cd.m_idBased = true;
233 continue;
234 } else if (!strcmp(s1: argv[i], s2: "-nocompress")) {
235 cd.m_saveMode = SaveEverything;
236 continue;
237 } else if (!strcmp(s1: argv[i], s2: "-removeidentical")) {
238 removeIdentical = true;
239 continue;
240 } else if (!strcmp(s1: argv[i], s2: "-nounfinished")) {
241 cd.m_ignoreUnfinished = true;
242 continue;
243 } else if (!strcmp(s1: argv[i], s2: "-markuntranslated")) {
244 if (i == argc - 1) {
245 printUsage();
246 return 1;
247 }
248 cd.m_unTrPrefix = QString::fromLocal8Bit(str: argv[++i]);
249 } else if (!strcmp(s1: argv[i], s2: "-project")) {
250 if (i == argc - 1) {
251 printErr(out: LR::tr(sourceText: "The option -project requires a parameter.\n"));
252 return 1;
253 }
254 if (!projectDescriptionFile.isEmpty()) {
255 printErr(out: LR::tr(sourceText: "The option -project must appear only once.\n"));
256 return 1;
257 }
258 projectDescriptionFile = QString::fromLocal8Bit(str: argv[++i]);
259 } else if (!strcmp(s1: argv[i], s2: "-silent")) {
260 cd.m_verbose = false;
261 continue;
262 } else if (!strcmp(s1: argv[i], s2: "-verbose")) {
263 cd.m_verbose = true;
264 continue;
265 } else if (!strcmp(s1: argv[i], s2: "-version")) {
266 printOut(out: LR::tr(sourceText: "lrelease version %1\n").arg(a: QLatin1String(QT_VERSION_STR)));
267 return 0;
268 } else if (!strcmp(s1: argv[i], s2: "-qm")) {
269 if (i == argc - 1) {
270 printUsage();
271 return 1;
272 }
273 outputFile = QString::fromLocal8Bit(str: argv[++i]);
274 } else if (!strcmp(s1: argv[i], s2: "-help")) {
275 printUsage();
276 return 0;
277 } else if (argv[i][0] == '-') {
278 printUsage();
279 return 1;
280 } else {
281 inputFiles << QString::fromLocal8Bit(str: argv[i]);
282 }
283 }
284
285 if (inputFiles.isEmpty() && projectDescriptionFile.isEmpty()) {
286 printUsage();
287 return 1;
288 }
289
290 QString errorString;
291 if (!extractProFiles(files: &inputFiles).isEmpty()) {
292 runQtTool(QStringLiteral("lrelease-pro"), arguments: app.arguments().mid(pos: 1));
293 return 0;
294 }
295
296 if (!projectDescriptionFile.isEmpty()) {
297 if (!inputFiles.isEmpty()) {
298 printErr(out: LR::tr(sourceText: "lrelease error: Do not specify TS files if -project is given.\n"));
299 return 1;
300 }
301 Projects projectDescription = readProjectDescription(filePath: projectDescriptionFile, errorString: &errorString);
302 if (!errorString.isEmpty()) {
303 printErr(out: LR::tr(sourceText: "lrelease error: %1\n").arg(a: errorString));
304 return 1;
305 }
306 inputFiles = translationsFromProjects(projects: projectDescription);
307 }
308
309 foreach (const QString &inputFile, inputFiles) {
310 if (outputFile.isEmpty()) {
311 if (!releaseTsFile(tsFileName: inputFile, cd, removeIdentical))
312 return 1;
313 } else {
314 if (!loadTsFile(tor, tsFileName: inputFile, cd.isVerbose()))
315 return 1;
316 }
317 }
318
319 if (!outputFile.isEmpty())
320 return releaseTranslator(tor, qmFileName: outputFile, cd, removeIdentical) ? 0 : 1;
321
322 return 0;
323}
324

source code of qttools/src/linguist/lrelease/main.cpp