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 <QtCore/QCoreApplication>
32 #include <QtCore/QDebug>
33 #include <QtCore/QString>
34 #include <QtCore/QStringList>
35 #include <QtCore/QTranslator>
36 #include <QtCore/QLibraryInfo>
37 
38 #include <iostream>
39 
40 QT_USE_NAMESPACE
41 
42 class LC {
43     Q_DECLARE_TR_FUNCTIONS(LConvert)
44 };
45 
usage(const QStringList & args)46 static int usage(const QStringList &args)
47 {
48     Q_UNUSED(args);
49 
50     QString loaders;
51     QString line(QLatin1String("    %1 - %2\n"));
52     foreach (Translator::FileFormat format, Translator::registeredFileFormats())
53         loaders += line.arg(format.extension, -5).arg(format.description());
54 
55     std::cout << qPrintable(LC::tr("\nUsage:\n"
56         "    lconvert [options] <infile> [<infile>...]\n\n"
57         "lconvert is part of Qt's Linguist tool chain. It can be used as a\n"
58         "stand-alone tool to convert and filter translation data files.\n"
59         "The following file formats are supported:\n\n%1\n"
60         "If multiple input files are specified, they are merged with\n"
61         "translations from later files taking precedence.\n\n"
62         "Options:\n"
63         "    -h\n"
64         "    -help  Display this information and exit.\n\n"
65         "    -i <infile>\n"
66         "    -input-file <infile>\n"
67         "           Specify input file. Use if <infile> might start with a dash.\n"
68         "           This option can be used several times to merge inputs.\n"
69         "           May be '-' (standard input) for use in a pipe.\n\n"
70         "    -o <outfile>\n"
71         "    -output-file <outfile>\n"
72         "           Specify output file. Default is '-' (standard output).\n\n"
73         "    -if <informat>\n"
74         "    -input-format <format>\n"
75         "           Specify input format for subsequent <infile>s.\n"
76         "           The format is auto-detected from the file name and defaults to 'ts'.\n\n"
77         "    -of <outformat>\n"
78         "    -output-format <outformat>\n"
79         "           Specify output format. See -if.\n\n"
80         "    -drop-tags <regexp>\n"
81         "           Drop named extra tags when writing TS or XLIFF files.\n"
82         "           May be specified repeatedly.\n\n"
83         "    -drop-translations\n"
84         "           Drop existing translations and reset the status to 'unfinished'.\n"
85         "           Note: this implies --no-obsolete.\n\n"
86         "    -source-language <language>[_<region>]\n"
87         "           Specify/override the language of the source strings. Defaults to\n"
88         "           POSIX if not specified and the file does not name it yet.\n\n"
89         "    -target-language <language>[_<region>]\n"
90         "           Specify/override the language of the translation.\n"
91         "           The target language is guessed from the file name if this option\n"
92         "           is not specified and the file contents name no language yet.\n\n"
93         "    -no-obsolete\n"
94         "           Drop obsolete messages.\n\n"
95         "    -no-finished\n"
96         "           Drop finished messages.\n\n"
97         "    -no-untranslated\n"
98         "           Drop untranslated messages.\n\n"
99         "    -sort-contexts\n"
100         "           Sort contexts in output TS file alphabetically.\n\n"
101         "    -locations {absolute|relative|none}\n"
102         "           Override how source code references are saved in TS files.\n"
103         "           Default is absolute.\n\n"
104         "    -no-ui-lines\n"
105         "           Drop line numbers from references to UI files.\n\n"
106         "    -verbose\n"
107         "           be a bit more verbose\n\n"
108         "Long options can be specified with only one leading dash, too.\n\n"
109         "Return value:\n"
110         "    0 on success\n"
111         "    1 on command line parse failures\n"
112         "    2 on read failures\n"
113         "    3 on write failures\n").arg(loaders));
114     return 1;
115 }
116 
117 struct File
118 {
119     QString name;
120     QString format;
121 };
122 
main(int argc,char * argv[])123 int main(int argc, char *argv[])
124 {
125     QCoreApplication app(argc, argv);
126 #ifndef QT_BOOTSTRAPPED
127 #ifndef Q_OS_WIN32
128     QTranslator translator;
129     QTranslator qtTranslator;
130     QString sysLocale = QLocale::system().name();
131     QString resourceDir = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
132     if (translator.load(QLatin1String("linguist_") + sysLocale, resourceDir)
133         && qtTranslator.load(QLatin1String("qt_") + sysLocale, resourceDir)) {
134         app.installTranslator(&translator);
135         app.installTranslator(&qtTranslator);
136     }
137 #endif // Q_OS_WIN32
138 #endif
139 
140     QStringList args = app.arguments();
141     QList<File> inFiles;
142     QString inFormat(QLatin1String("auto"));
143     QString outFileName;
144     QString outFormat(QLatin1String("auto"));
145     QString targetLanguage;
146     QString sourceLanguage;
147     bool dropTranslations = false;
148     bool noObsolete = false;
149     bool noFinished = false;
150     bool noUntranslated = false;
151     bool verbose = false;
152     bool noUiLines = false;
153     Translator::LocationsType locations = Translator::DefaultLocations;
154 
155     ConversionData cd;
156     Translator tr;
157 
158     for (int i = 1; i < args.size(); ++i) {
159         if (args[i].startsWith(QLatin1String("--")))
160             args[i].remove(0, 1);
161         if (args[i] == QLatin1String("-o")
162          || args[i] == QLatin1String("-output-file")) {
163             if (++i >= args.size())
164                 return usage(args);
165             outFileName = args[i];
166         } else if (args[i] == QLatin1String("-of")
167                 || args[i] == QLatin1String("-output-format")) {
168             if (++i >= args.size())
169                 return usage(args);
170             outFormat = args[i];
171         } else if (args[i] == QLatin1String("-i")
172                 || args[i] == QLatin1String("-input-file")) {
173             if (++i >= args.size())
174                 return usage(args);
175             File file;
176             file.name = args[i];
177             file.format = inFormat;
178             inFiles.append(file);
179         } else if (args[i] == QLatin1String("-if")
180                 || args[i] == QLatin1String("-input-format")) {
181             if (++i >= args.size())
182                 return usage(args);
183             inFormat = args[i];
184         } else if (args[i] == QLatin1String("-drop-tag") || args[i] == QLatin1String("-drop-tags")) {
185             if (++i >= args.size())
186                 return usage(args);
187             cd.m_dropTags.append(args[i]);
188         } else if (args[i] == QLatin1String("-drop-translations")) {
189             dropTranslations = true;
190         } else if (args[i] == QLatin1String("-target-language")) {
191             if (++i >= args.size())
192                 return usage(args);
193             targetLanguage = args[i];
194         } else if (args[i] == QLatin1String("-source-language")) {
195             if (++i >= args.size())
196                 return usage(args);
197             sourceLanguage = args[i];
198         } else if (args[i].startsWith(QLatin1String("-h"))) {
199             usage(args);
200             return 0;
201         } else if (args[i] == QLatin1String("-no-obsolete")) {
202             noObsolete = true;
203         } else if (args[i] == QLatin1String("-no-finished")) {
204             noFinished = true;
205         } else if (args[i] == QLatin1String("-no-untranslated")) {
206             noUntranslated = true;
207         } else if (args[i] == QLatin1String("-sort-contexts")) {
208             cd.m_sortContexts = true;
209         } else if (args[i] == QLatin1String("-locations")) {
210             if (++i >= args.size())
211                 return usage(args);
212             if (args[i] == QLatin1String("none"))
213                 locations = Translator::NoLocations;
214             else if (args[i] == QLatin1String("relative"))
215                 locations = Translator::RelativeLocations;
216             else if (args[i] == QLatin1String("absolute"))
217                 locations = Translator::AbsoluteLocations;
218             else
219                 return usage(args);
220         } else if (args[i] == QLatin1String("-no-ui-lines")) {
221             noUiLines = true;
222         } else if (args[i] == QLatin1String("-verbose")) {
223             verbose = true;
224         } else if (args[i].startsWith(QLatin1Char('-'))) {
225             return usage(args);
226         } else {
227             File file;
228             file.name = args[i];
229             file.format = inFormat;
230             inFiles.append(file);
231         }
232     }
233 
234     if (inFiles.isEmpty())
235         return usage(args);
236 
237     tr.setLanguageCode(Translator::guessLanguageCodeFromFileName(inFiles[0].name));
238 
239     if (!tr.load(inFiles[0].name, cd, inFiles[0].format)) {
240         std::cerr << qPrintable(cd.error());
241         return 2;
242     }
243     tr.reportDuplicates(tr.resolveDuplicates(), inFiles[0].name, verbose);
244 
245     for (int i = 1; i < inFiles.size(); ++i) {
246         Translator tr2;
247         if (!tr2.load(inFiles[i].name, cd, inFiles[i].format)) {
248             std::cerr << qPrintable(cd.error());
249             return 2;
250         }
251         tr2.reportDuplicates(tr2.resolveDuplicates(), inFiles[i].name, verbose);
252         for (int j = 0; j < tr2.messageCount(); ++j)
253             tr.replaceSorted(tr2.message(j));
254     }
255 
256     if (!targetLanguage.isEmpty())
257         tr.setLanguageCode(targetLanguage);
258     if (!sourceLanguage.isEmpty())
259         tr.setSourceLanguageCode(sourceLanguage);
260     if (noObsolete)
261         tr.stripObsoleteMessages();
262     if (noFinished)
263         tr.stripFinishedMessages();
264     if (noUntranslated)
265         tr.stripUntranslatedMessages();
266     if (dropTranslations)
267         tr.dropTranslations();
268     if (noUiLines)
269         tr.dropUiLines();
270     if (locations != Translator::DefaultLocations)
271         tr.setLocationsType(locations);
272 
273     tr.normalizeTranslations(cd);
274     if (!cd.errors().isEmpty()) {
275         std::cerr << qPrintable(cd.error());
276         cd.clearErrors();
277     }
278     if (!tr.save(outFileName, cd, outFormat)) {
279         std::cerr << qPrintable(cd.error());
280         return 3;
281     }
282     return 0;
283 }
284