1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "translator.h"
5
6#include <profileutils.h>
7#include <projectdescriptionreader.h>
8#include <runqttool.h>
9
10#ifndef QT_BOOTSTRAPPED
11#include <QtCore/QCoreApplication>
12#include <QtCore/QTranslator>
13#endif
14#include <QtCore/QDebug>
15#include <QtCore/QDir>
16#include <QtCore/QFile>
17#include <QtCore/QFileInfo>
18#include <QtCore/QString>
19#include <QtCore/QStringList>
20#include <QtCore/QTextStream>
21#include <QtCore/QLibraryInfo>
22
23QT_USE_NAMESPACE
24
25using namespace Qt::StringLiterals;
26
27static void printOut(const QString & out)
28{
29 QTextStream stream(stdout);
30 stream << out;
31}
32
33static void printErr(const QString & out)
34{
35 QTextStream stream(stderr);
36 stream << out;
37}
38
39static void printUsage()
40{
41 printOut(out: uR"(Usage:
42 lrelease [options] -project project-file
43 lrelease [options] ts-files [-qm qm-file]
44
45lrelease is part of Qt's Linguist tool chain. It can be used as a
46stand-alone tool to convert XML-based translations files in the TS
47format into the 'compiled' QM format used by QTranslator objects.
48
49Passing .pro files to lrelease is deprecated.
50Please use the lrelease-pro tool instead, or use qmake's lrelease.prf
51feature.
52
53Options:
54 -help Display this information and exit
55 -idbased
56 Use IDs instead of source strings for message keying
57 -compress
58 Compress the QM files
59 -nounfinished
60 Do not include unfinished translations
61 -removeidentical
62 If the translated text is the same as
63 the source text, do not include the message
64 -markuntranslated <prefix>
65 If a message has no real translation, use the source text
66 prefixed with the given string instead
67 -project <filename>
68 Name of a file containing the project's description in JSON format.
69 Such a file may be generated from a .pro file using the lprodump tool.
70 -silent
71 Do not explain what is being done
72 -version
73 Display the version of lrelease and exit
74)"_s);
75}
76
77static bool loadTsFile(Translator &tor, const QString &tsFileName, bool /* verbose */)
78{
79 ConversionData cd;
80 bool ok = tor.load(filename: tsFileName, err&: cd, format: QLatin1String("auto"));
81 if (!ok) {
82 printErr(out: QLatin1String("lrelease error: %1").arg(args: cd.error()));
83 } else {
84 if (!cd.errors().isEmpty())
85 printOut(out: cd.error());
86 }
87 cd.clearErrors();
88 return ok;
89}
90
91static bool releaseTranslator(Translator &tor, const QString &qmFileName,
92 ConversionData &cd, bool removeIdentical)
93{
94 tor.reportDuplicates(dupes: tor.resolveDuplicates(), fileName: qmFileName, verbose: cd.isVerbose());
95
96 if (cd.isVerbose())
97 printOut(out: QLatin1String("Updating '%1'...\n").arg(args: qmFileName));
98 if (removeIdentical) {
99 if (cd.isVerbose())
100 printOut(out: QLatin1String("Removing translations equal to source text in '%1'...\n")
101 .arg(args: qmFileName));
102 tor.stripIdenticalSourceTranslations();
103 }
104
105 QFile file(qmFileName);
106 if (!file.open(flags: QIODevice::WriteOnly)) {
107 printErr(out: QLatin1String("lrelease error: cannot create '%1': %2\n")
108 .arg(args: qmFileName, args: file.errorString()));
109 return false;
110 }
111
112 tor.normalizeTranslations(cd);
113 bool ok = saveQM(translator: tor, dev&: file, cd);
114 file.close();
115
116 if (!ok) {
117 printErr(out: QLatin1String("lrelease error: cannot save '%1': %2").arg(args: qmFileName, args: cd.error()));
118 } else if (!cd.errors().isEmpty()) {
119 printOut(out: cd.error());
120 }
121 cd.clearErrors();
122 return ok;
123}
124
125static bool releaseTsFile(const QString& tsFileName,
126 ConversionData &cd, bool removeIdentical)
127{
128 Translator tor;
129 if (!loadTsFile(tor, tsFileName, cd.isVerbose()))
130 return false;
131
132 QString qmFileName = tsFileName;
133 for (const Translator::FileFormat &fmt : std::as_const(t&: Translator::registeredFileFormats())) {
134 if (qmFileName.endsWith(s: QLatin1Char('.') + fmt.extension)) {
135 qmFileName.chop(n: fmt.extension.size() + 1);
136 break;
137 }
138 }
139 qmFileName += QLatin1String(".qm");
140
141 return releaseTranslator(tor, qmFileName, cd, removeIdentical);
142}
143
144static QStringList translationsFromProjects(const Projects &projects, bool topLevel);
145
146static QStringList translationsFromProject(const Project &project, bool topLevel)
147{
148 QStringList result;
149 if (project.translations)
150 result = *project.translations;
151 result << translationsFromProjects(projects: project.subProjects, topLevel: false);
152 if (topLevel && result.isEmpty()) {
153 printErr(
154 out: QLatin1String("lrelease warning: Met no 'TRANSLATIONS' entry in project file '%1'\n")
155 .arg(args: project.filePath));
156 }
157 return result;
158}
159
160static QStringList translationsFromProjects(const Projects &projects, bool topLevel = true)
161{
162 QStringList result;
163 for (const Project &p : projects)
164 result << translationsFromProject(project: p, topLevel);
165 return result;
166}
167
168int main(int argc, char **argv)
169{
170 QCoreApplication app(argc, argv);
171
172 ConversionData cd;
173 cd.m_verbose = true; // the default is true starting with Qt 4.2
174 bool removeIdentical = false;
175 Translator tor;
176 QStringList inputFiles;
177 QString outputFile;
178 QString projectDescriptionFile;
179
180 for (int i = 1; i < argc; ++i) {
181 if (!strcmp(s1: argv[i], s2: "-compress")) {
182 cd.m_saveMode = SaveStripped;
183 continue;
184 } else if (!strcmp(s1: argv[i], s2: "-idbased")) {
185 cd.m_idBased = true;
186 continue;
187 } else if (!strcmp(s1: argv[i], s2: "-nocompress")) {
188 cd.m_saveMode = SaveEverything;
189 continue;
190 } else if (!strcmp(s1: argv[i], s2: "-removeidentical")) {
191 removeIdentical = true;
192 continue;
193 } else if (!strcmp(s1: argv[i], s2: "-nounfinished")) {
194 cd.m_ignoreUnfinished = true;
195 continue;
196 } else if (!strcmp(s1: argv[i], s2: "-markuntranslated")) {
197 if (i == argc - 1) {
198 printUsage();
199 return 1;
200 }
201 cd.m_unTrPrefix = QString::fromLocal8Bit(ba: argv[++i]);
202 } else if (!strcmp(s1: argv[i], s2: "-project")) {
203 if (i == argc - 1) {
204 printErr(out: QLatin1String("The option -project requires a parameter.\n"));
205 return 1;
206 }
207 if (!projectDescriptionFile.isEmpty()) {
208 printErr(out: QLatin1String("The option -project must appear only once.\n"));
209 return 1;
210 }
211 projectDescriptionFile = QString::fromLocal8Bit(ba: argv[++i]);
212 } else if (!strcmp(s1: argv[i], s2: "-silent")) {
213 cd.m_verbose = false;
214 continue;
215 } else if (!strcmp(s1: argv[i], s2: "-verbose")) {
216 cd.m_verbose = true;
217 continue;
218 } else if (!strcmp(s1: argv[i], s2: "-version")) {
219 printOut(out: QLatin1String("lrelease version %1\n").arg(args: QLatin1String(QT_VERSION_STR)));
220 return 0;
221 } else if (!strcmp(s1: argv[i], s2: "-qm")) {
222 if (i == argc - 1) {
223 printUsage();
224 return 1;
225 }
226 outputFile = QString::fromLocal8Bit(ba: argv[++i]);
227 } else if (!strcmp(s1: argv[i], s2: "-help")) {
228 printUsage();
229 return 0;
230 } else if (argv[i][0] == '-') {
231 printUsage();
232 return 1;
233 } else {
234 inputFiles << QString::fromLocal8Bit(ba: argv[i]);
235 }
236 }
237
238 if (inputFiles.isEmpty() && projectDescriptionFile.isEmpty()) {
239 printUsage();
240 return 1;
241 }
242
243 QString errorString;
244 if (!extractProFiles(files: &inputFiles).isEmpty()) {
245 runInternalQtTool(toolName: QLatin1String("lrelease-pro"), arguments: app.arguments().mid(pos: 1));
246 return 0;
247 }
248
249 if (!projectDescriptionFile.isEmpty()) {
250 if (!inputFiles.isEmpty()) {
251 printErr(out: QLatin1String(
252 "lrelease error: Do not specify TS files if -project is given.\n"));
253 return 1;
254 }
255 Projects projectDescription = readProjectDescription(filePath: projectDescriptionFile, errorString: &errorString);
256 if (!errorString.isEmpty()) {
257 printErr(out: QLatin1String("lrelease error: %1\n").arg(args&: errorString));
258 return 1;
259 }
260 inputFiles = translationsFromProjects(projects: projectDescription);
261 }
262
263 for (const QString &inputFile : std::as_const(t&: inputFiles)) {
264 if (outputFile.isEmpty()) {
265 if (!releaseTsFile(tsFileName: inputFile, cd, removeIdentical))
266 return 1;
267 } else {
268 if (!loadTsFile(tor, tsFileName: inputFile, cd.isVerbose()))
269 return 1;
270 }
271 }
272
273 if (!outputFile.isEmpty())
274 return releaseTranslator(tor, qmFileName: outputFile, cd, removeIdentical) ? 0 : 1;
275
276 return 0;
277}
278

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