Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
merge.cpp
Go to the documentation of this file.
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 "trparser.h"
5#include "simtexth.h"
6#include "translator.h"
7
8#include <QtCore/QCoreApplication>
9#include <QtCore/QDebug>
10#include <QtCore/QList>
11#include <QtCore/QMap>
12#include <QtCore/QStringList>
13
15
16using namespace Qt::Literals::StringLiterals;
17
18/*
19 Augments a Translator with trivially derived translations.
20
21 For example, if "Enabled:" is consistendly translated as "Eingeschaltet:" no
22 matter the context or the comment, "Eingeschaltet:" is added as the
23 translation of any untranslated "Enabled:" text and is marked Unfinished.
24
25 Returns the number of additional messages that this heuristic translated.
26*/
27
29{
30 QMap<QString, QStringList> translated;
31 QMap<QString, bool> avoid; // Want a QTreeSet, in fact
32 QList<bool> untranslated(tor.messageCount());
33 int inserted = 0;
34
35 for (int i = 0; i < tor.messageCount(); ++i) {
36 const TranslatorMessage &msg = tor.message(i);
37 if (!msg.isTranslated()) {
38 if (msg.type() == TranslatorMessage::Unfinished)
39 untranslated[i] = true;
40 } else {
41 const QString &key = msg.sourceText();
42 const auto t = translated.constFind(key);
43 if (t != translated.constEnd()) {
44 /*
45 The same source text is translated at least two
46 different ways. Do nothing then.
47 */
48 if (*t != msg.translations()) {
49 translated.remove(key);
50 avoid.insert(key, true);
51 }
52 } else if (!avoid.contains(key)) {
53 translated.insert(key, msg.translations());
54 }
55 }
56 }
57
58 for (int i = 0; i < tor.messageCount(); ++i) {
59 if (untranslated[i]) {
61 const auto t = translated.constFind(msg.sourceText());
62 if (t != translated.constEnd()) {
63 msg.setTranslations(*t);
64 ++inserted;
65 }
66 }
67 }
68 return inserted;
69}
70
71
72
73/*
74 Merges two Translator objects. The first one
75 is a set of source texts and translations for a previous version of
76 the internationalized program; the second one is a set of fresh
77 source texts newly extracted from the source code, without any
78 translation yet.
79*/
80
82 const Translator &tor, const Translator &virginTor, const QList<Translator> &aliens,
83 UpdateOptions options, QString &err)
84{
85 int known = 0;
86 int neww = 0;
87 int obsoleted = 0;
88 int similarTextHeuristicCount = 0;
89
90 Translator outTor;
91 outTor.setLanguageCode(tor.languageCode());
92 outTor.setSourceLanguageCode(tor.sourceLanguageCode());
94
95 /*
96 The types of all the messages from the vernacular translator
97 are updated according to the virgin translator.
98 */
99 for (TranslatorMessage m : tor.messages()) {
100 TranslatorMessage::Type newType = TranslatorMessage::Finished;
101
102
103 TranslatorMessage::ExtraData extras;
104 const TranslatorMessage *mv;
105 int mvi = virginTor.find(m);
106 if (mvi < 0) {
107 if (!(options & HeuristicSimilarText)) {
108 makeObsolete:
109 switch (m.type()) {
110 case TranslatorMessage::Finished:
111 newType = TranslatorMessage::Vanished;
112 obsoleted++;
113 break;
114 case TranslatorMessage::Unfinished:
115 newType = TranslatorMessage::Obsolete;
116 obsoleted++;
117 break;
118 default:
119 newType = m.type();
120 break;
121 }
122 m.clearReferences();
123 } else {
124 mvi = virginTor.find(m.context(), m.comment(), m.allReferences());
125 if (mvi < 0) {
126 // did not find it in the virgin, mark it as obsolete
127 goto makeObsolete;
128 }
129 mv = &virginTor.constMessage(mvi);
130 // Do not just accept it if its on the same line number,
131 // but different source text.
132 // Also check if the texts are more or less similar before
133 // we consider them to represent the same message...
134 if (getSimilarityScore(m.sourceText(), mv->sourceText()) < textSimilarityThreshold) {
135 // The virgin and vernacular sourceTexts are so different that we could not find it
136 goto makeObsolete;
137 }
138 // It is just slightly modified, assume that it is the same string
139
140 extras = mv->extras();
141
142 // Mark it as unfinished. (Since the source text
143 // was changed it might require re-translating...)
144 newType = TranslatorMessage::Unfinished;
145 ++similarTextHeuristicCount;
146 neww++;
147 goto outdateSource;
148 }
149 } else {
150 mv = &virginTor.message(mvi);
151 extras = mv->extras();
152 if (!mv->id().isEmpty()
153 && (mv->context() != m.context()
154 || mv->sourceText() != m.sourceText()
155 || mv->comment() != m.comment())) {
156 known++;
157 newType = TranslatorMessage::Unfinished;
158 m.setContext(mv->context());
159 m.setComment(mv->comment());
160 if (mv->sourceText() != m.sourceText()) {
161 outdateSource:
162 m.setOldSourceText(m.sourceText());
163 m.setSourceText(mv->sourceText());
164 const QString &oldpluralsource = m.extra("po-msgid_plural"_L1);
165 if (!oldpluralsource.isEmpty())
166 extras.insert("po-old_msgid_plural"_L1, oldpluralsource);
167 }
168 } else {
169 switch (m.type()) {
170 case TranslatorMessage::Finished:
171 default:
172 if (m.isPlural() == mv->isPlural()) {
173 newType = TranslatorMessage::Finished;
174 } else {
175 newType = TranslatorMessage::Unfinished;
176 }
177 known++;
178 break;
179 case TranslatorMessage::Unfinished:
180 newType = TranslatorMessage::Unfinished;
181 known++;
182 break;
183 case TranslatorMessage::Vanished:
184 newType = TranslatorMessage::Finished;
185 neww++;
186 break;
187 case TranslatorMessage::Obsolete:
188 newType = TranslatorMessage::Unfinished;
189 neww++;
190 break;
191 }
192 }
193
194 // Always get the filename and linenumber info from the
195 // virgin Translator, in case it has changed location.
196 // This should also enable us to read a file that does not
197 // have the <location> element.
198 // why not use operator=()? Because it overwrites e.g. userData.
199 m.setReferences(mv->allReferences());
200 m.setPlural(mv->isPlural());
201 m.setExtras(extras);
202 m.setExtraComment(mv->extraComment());
203 m.setId(mv->id());
204 m.setLabel(mv->label());
205 }
206
207
208 m.setType(newType);
209 outTor.append(m);
210 }
211
212 /*
213 Messages found only in the virgin translator are added to the
214 vernacular translator.
215 */
216 for (const TranslatorMessage &mv : virginTor.messages()) {
217
218 if (tor.find(mv) >= 0)
219 continue;
220 if (options & HeuristicSimilarText) {
221 int mi = tor.find(mv.context(), mv.comment(), mv.allReferences());
222 if (mi >= 0) {
223 // The similar message found in tor (ts file) must NOT correspond exactly
224 // to an other message is virginTor
225 if (virginTor.find(tor.constMessage(mi)) < 0) {
226 if (getSimilarityScore(tor.constMessage(mi).sourceText(), mv.sourceText())
227 >= textSimilarityThreshold)
228 continue;
229 }
230 }
231 }
232
233 outTor.appendSorted(mv);
234 ++neww;
235 }
236
237 /*
238 "Alien" translators can be used to augment the vernacular translator.
239 */
240 for (const Translator &alf : aliens) {
241 for (TranslatorMessage mv : alf.messages()) {
242 if (mv.sourceText().isEmpty() || !mv.isTranslated())
243 continue;
244 int mvi = outTor.find(mv);
245 if (mvi >= 0) {
246 TranslatorMessage &tm = outTor.message(mvi);
247 if (tm.type() != TranslatorMessage::Finished && !tm.isTranslated()) {
248 tm.setTranslations(mv.translations());
249 --neww;
250 ++known;
251 }
252 } else {
253 /*
254 * Don't do simtex search, as the locations are likely to be
255 * completely off anyway, so we'd find nothing.
256 */
257 /*
258 * Add the unmatched messages as obsoletes, so the Linguist GUI
259 * will offer them as possible translations.
260 */
261 mv.clearReferences();
262 mv.setType(mv.type() == TranslatorMessage::Finished
263 ? TranslatorMessage::Vanished : TranslatorMessage::Obsolete);
264 outTor.appendSorted(mv);
265 ++known;
266 ++obsoleted;
267 }
268 }
269 }
270
271 /*
272 The same-text heuristic handles cases where a message has an
273 obsolete counterpart with a different context or comment.
274 */
275 int sameTextHeuristicCount = (options & HeuristicSameText) ? applySameTextHeuristic(outTor) : 0;
276
277 if (options & Verbose) {
278 int totalFound = neww + known;
279 err += QStringLiteral(" Found %1 source text(s) (%2 new and %3 already existing)\n")
280 .arg(totalFound).arg(neww).arg(known);
281
282 if (obsoleted) {
283 if (options & NoObsolete) {
284 err += QStringLiteral(" Removed %1 obsolete entries\n").arg(obsoleted);
285 } else {
286 err += QStringLiteral(" Kept %1 obsolete entries\n").arg(obsoleted);
287 }
288 }
289
290 if (sameTextHeuristicCount)
291 err += QStringLiteral(" Same-text heuristic provided %1 translation(s)\n")
292 .arg(sameTextHeuristicCount);
293 if (similarTextHeuristicCount)
294 err += QStringLiteral(" Similar-text heuristic provided %1 translation(s)\n")
295 .arg(similarTextHeuristicCount);
296 }
297 return outTor;
298}
299
300QT_END_NAMESPACE
bool isTranslated() const
LocationsType locationsType() const
Definition translator.h:124
void setLocationsType(LocationsType lt)
Definition translator.h:123
int messageCount() const
Definition translator.h:137
TranslatorMessage & message(int i)
Definition translator.h:138
int applySameTextHeuristic(Translator &tor)
Definition merge.cpp:28
Combined button and popup list for selecting options.
Translator merge(const Translator &tor, const Translator &virginTor, const QList< Translator > &aliens, UpdateOptions options, QString &err)
Definition merge.cpp:81
@ Verbose
Definition trparser.h:15
@ NoObsolete
Definition trparser.h:16
@ HeuristicSameText
Definition trparser.h:19