1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
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 The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/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 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "translator.h"
43
44 #include <QtCore/QByteArray>
45 #include <QtCore/QDebug>
46 #include <QtCore/QTextCodec>
47 #include <QtCore/QTextStream>
48
49 #include <QtXml/QXmlStreamReader>
50 #include <QtXml/QXmlStreamAttribute>
51
52 #define STRINGIFY_INTERNAL(x) #x
53 #define STRINGIFY(x) STRINGIFY_INTERNAL(x)
54 #define STRING(s) static QString str##s(QLatin1String(STRINGIFY(s)))
55
56 QT_BEGIN_NAMESPACE
57
58 /*
59 * The encodings are a total mess.
60 * A Translator has a codecForTr(). Each message's text will be passed to tr()
61 * in that encoding or as UTF-8 to trUtf8() if it is flagged as such.
62 * For ts 2.0, the file content is always uniformly in UTF-8. The file stores
63 * the codecForTr default and marks deviating messages accordingly.
64 * For ts 1.1, the file content is in mixed encoding. Each message is encoded
65 * the way it will be passed to tr() (with 8-bit characters encoded as numeric
66 * entities) or trUtf8(). The file stores the encoding and codecForTr in one
67 * attribute, for both the default and each deviating message.
68 */
69
70
operator <<(QDebug & d,const QXmlStreamAttribute & attr)71 QDebug &operator<<(QDebug &d, const QXmlStreamAttribute &attr)
72 {
73 return d << "[" << attr.name().toString() << "," << attr.value().toString() << "]";
74 }
75
76
77 class TSReader : public QXmlStreamReader
78 {
79 public:
TSReader(QIODevice & dev,ConversionData & cd)80 TSReader(QIODevice &dev, ConversionData &cd)
81 : QXmlStreamReader(&dev), m_cd(cd)
82 {}
83
84 // the "real thing"
85 bool read(Translator &translator);
86
87 private:
elementStarts(const QString & str) const88 bool elementStarts(const QString &str) const
89 {
90 return isStartElement() && name() == str;
91 }
92
isWhiteSpace() const93 bool isWhiteSpace() const
94 {
95 return isCharacters() && text().toString().trimmed().isEmpty();
96 }
97
98 // needed to expand <byte ... />
99 QString readContents();
100 // needed to join <lengthvariant>s
101 QString readTransContents();
102
103 void handleError();
104
105 ConversionData &m_cd;
106 };
107
handleError()108 void TSReader::handleError()
109 {
110 if (isComment())
111 return;
112 if (hasError() && error() == CustomError) // raised by readContents
113 return;
114
115 const QString loc = QString::fromLatin1("at %3:%1:%2")
116 .arg(lineNumber()).arg(columnNumber()).arg(m_cd.m_sourceFileName);
117
118 switch (tokenType()) {
119 case NoToken: // Cannot happen
120 default: // likewise
121 case Invalid:
122 raiseError(QString::fromLatin1("Parse error %1: %2").arg(loc, errorString()));
123 break;
124 case StartElement:
125 raiseError(QString::fromLatin1("Unexpected tag <%1> %2").arg(name().toString(), loc));
126 break;
127 case Characters:
128 {
129 QString tok = text().toString();
130 if (tok.length() > 30)
131 tok = tok.left(30) + QLatin1String("[...]");
132 raiseError(QString::fromLatin1("Unexpected characters '%1' %2").arg(tok, loc));
133 }
134 break;
135 case EntityReference:
136 raiseError(QString::fromLatin1("Unexpected entity '&%1;' %2").arg(name().toString(), loc));
137 break;
138 case ProcessingInstruction:
139 raiseError(QString::fromLatin1("Unexpected processing instruction %1").arg(loc));
140 break;
141 }
142 }
143
byteValue(QString value)144 static QString byteValue(QString value)
145 {
146 int base = 10;
147 if (value.startsWith(QLatin1String("x"))) {
148 base = 16;
149 value.remove(0, 1);
150 }
151 int n = value.toUInt(0, base);
152 return (n != 0) ? QString(QChar(n)) : QString();
153 }
154
readContents()155 QString TSReader::readContents()
156 {
157 STRING(byte);
158 STRING(value);
159
160 QString result;
161 while (!atEnd()) {
162 readNext();
163 if (isEndElement()) {
164 break;
165 } else if (isCharacters()) {
166 result += text();
167 } else if (elementStarts(strbyte)) {
168 // <byte value="...">
169 result += byteValue(attributes().value(strvalue).toString());
170 readNext();
171 if (!isEndElement()) {
172 handleError();
173 break;
174 }
175 } else {
176 handleError();
177 break;
178 }
179 }
180 //qDebug() << "TEXT: " << result;
181 return result;
182 }
183
readTransContents()184 QString TSReader::readTransContents()
185 {
186 STRING(lengthvariant);
187 STRING(variants);
188 STRING(yes);
189
190 if (attributes().value(strvariants) == stryes) {
191 QString result;
192 while (!atEnd()) {
193 readNext();
194 if (isEndElement()) {
195 break;
196 } else if (isWhiteSpace()) {
197 // ignore these, just whitespace
198 } else if (elementStarts(strlengthvariant)) {
199 if (!result.isEmpty())
200 result += QChar(Translator::BinaryVariantSeparator);
201 result += readContents();
202 } else {
203 handleError();
204 break;
205 }
206 }
207 return result;
208 } else {
209 return readContents();
210 }
211 }
212
read(Translator & translator)213 bool TSReader::read(Translator &translator)
214 {
215 STRING(both);
216 STRING(byte);
217 STRING(comment);
218 STRING(context);
219 STRING(defaultcodec);
220 STRING(encoding);
221 STRING(extracomment);
222 STRING(filename);
223 STRING(id);
224 STRING(language);
225 STRING(line);
226 STRING(location);
227 STRING(message);
228 STRING(name);
229 STRING(numerus);
230 STRING(numerusform);
231 STRING(obsolete);
232 STRING(oldcomment);
233 STRING(oldsource);
234 STRING(source);
235 STRING(sourcelanguage);
236 STRING(translation);
237 STRING(translatorcomment);
238 STRING(true);
239 STRING(TS);
240 STRING(type);
241 STRING(unfinished);
242 STRING(userdata);
243 STRING(utf8);
244 STRING(value);
245 //STRING(version);
246 STRING(yes);
247
248 static const QString strextrans(QLatin1String("extra-"));
249 static const QString strUtf8(QLatin1String("UTF-8"));
250
251 while (!atEnd()) {
252 readNext();
253 if (isStartDocument()) {
254 // <!DOCTYPE TS>
255 //qDebug() << attributes();
256 } else if (isEndDocument()) {
257 // <!DOCTYPE TS>
258 //qDebug() << attributes();
259 } else if (isDTD()) {
260 // <!DOCTYPE TS>
261 //qDebug() << tokenString();
262 } else if (elementStarts(strTS)) {
263 // <TS>
264 //qDebug() << "TS " << attributes();
265 QHash<QString, int> currentLine;
266 QString currentFile;
267 bool maybeRelative = false, maybeAbsolute = false;
268
269 QXmlStreamAttributes atts = attributes();
270 //QString version = atts.value(strversion).toString();
271 translator.setLanguageCode(atts.value(strlanguage).toString());
272 translator.setSourceLanguageCode(atts.value(strsourcelanguage).toString());
273 while (!atEnd()) {
274 readNext();
275 if (isEndElement()) {
276 // </TS> found, finish local loop
277 break;
278 } else if (isWhiteSpace()) {
279 // ignore these, just whitespace
280 } else if (elementStarts(strdefaultcodec)) {
281 // <defaultcodec>
282 const QString &codec = readElementText();
283 if (!codec.isEmpty())
284 translator.setCodecName(codec.toLatin1());
285 // </defaultcodec>
286 } else if (isStartElement()
287 && name().toString().startsWith(strextrans)) {
288 // <extra-...>
289 QString tag = name().toString();
290 translator.setExtra(tag.mid(6), readContents());
291 // </extra-...>
292 } else if (elementStarts(strcontext)) {
293 // <context>
294 QString context;
295 while (!atEnd()) {
296 readNext();
297 if (isEndElement()) {
298 // </context> found, finish local loop
299 break;
300 } else if (isWhiteSpace()) {
301 // ignore these, just whitespace
302 } else if (elementStarts(strname)) {
303 // <name>
304 context = readElementText();
305 // </name>
306 } else if (elementStarts(strmessage)) {
307 // <message>
308 TranslatorMessage::References refs;
309 QString currentMsgFile = currentFile;
310
311 TranslatorMessage msg;
312 msg.setId(attributes().value(strid).toString());
313 msg.setContext(context);
314 msg.setType(TranslatorMessage::Finished);
315 msg.setPlural(attributes().value(strnumerus) == stryes);
316 const QStringRef &utf8Attr = attributes().value(strutf8);
317 msg.setNonUtf8(utf8Attr == strboth);
318 msg.setUtf8(msg.isNonUtf8() || utf8Attr == strtrue
319 || attributes().value(strencoding) == strUtf8);
320 while (!atEnd()) {
321 readNext();
322 if (isEndElement()) {
323 // </message> found, finish local loop
324 msg.setReferences(refs);
325 translator.append(msg);
326 break;
327 } else if (isWhiteSpace()) {
328 // ignore these, just whitespace
329 } else if (elementStarts(strsource)) {
330 // <source>...</source>
331 msg.setSourceText(readContents());
332 } else if (elementStarts(stroldsource)) {
333 // <oldsource>...</oldsource>
334 msg.setOldSourceText(readContents());
335 } else if (elementStarts(stroldcomment)) {
336 // <oldcomment>...</oldcomment>
337 msg.setOldComment(readContents());
338 } else if (elementStarts(strextracomment)) {
339 // <extracomment>...</extracomment>
340 msg.setExtraComment(readContents());
341 } else if (elementStarts(strtranslatorcomment)) {
342 // <translatorcomment>...</translatorcomment>
343 msg.setTranslatorComment(readContents());
344 } else if (elementStarts(strlocation)) {
345 // <location/>
346 maybeAbsolute = true;
347 QXmlStreamAttributes atts = attributes();
348 QString fileName = atts.value(strfilename).toString();
349 if (fileName.isEmpty()) {
350 fileName = currentMsgFile;
351 maybeRelative = true;
352 } else {
353 if (refs.isEmpty())
354 currentFile = fileName;
355 currentMsgFile = fileName;
356 }
357 const QString lin = atts.value(strline).toString();
358 if (lin.isEmpty()) {
359 refs.append(TranslatorMessage::Reference(fileName, -1));
360 } else {
361 bool bOK;
362 int lineNo = lin.toInt(&bOK);
363 if (bOK) {
364 if (lin.startsWith(QLatin1Char('+')) || lin.startsWith(QLatin1Char('-'))) {
365 lineNo = (currentLine[fileName] += lineNo);
366 maybeRelative = true;
367 }
368 refs.append(TranslatorMessage::Reference(fileName, lineNo));
369 }
370 }
371 readContents();
372 } else if (elementStarts(strcomment)) {
373 // <comment>...</comment>
374 msg.setComment(readContents());
375 } else if (elementStarts(struserdata)) {
376 // <userdata>...</userdata>
377 msg.setUserData(readContents());
378 } else if (elementStarts(strtranslation)) {
379 // <translation>
380 QXmlStreamAttributes atts = attributes();
381 QStringRef type = atts.value(strtype);
382 if (type == strunfinished)
383 msg.setType(TranslatorMessage::Unfinished);
384 else if (type == strobsolete)
385 msg.setType(TranslatorMessage::Obsolete);
386 if (msg.isPlural()) {
387 QStringList translations;
388 while (!atEnd()) {
389 readNext();
390 if (isEndElement()) {
391 break;
392 } else if (isWhiteSpace()) {
393 // ignore these, just whitespace
394 } else if (elementStarts(strnumerusform)) {
395 translations.append(readTransContents());
396 } else {
397 handleError();
398 break;
399 }
400 }
401 msg.setTranslations(translations);
402 } else {
403 msg.setTranslation(readTransContents());
404 }
405 // </translation>
406 } else if (isStartElement()
407 && name().toString().startsWith(strextrans)) {
408 // <extra-...>
409 QString tag = name().toString();
410 msg.setExtra(tag.mid(6), readContents());
411 // </extra-...>
412 } else {
413 handleError();
414 }
415 }
416 // </message>
417 } else {
418 handleError();
419 }
420 }
421 // </context>
422 } else {
423 handleError();
424 }
425 translator.setLocationsType(maybeRelative ? Translator::RelativeLocations :
426 maybeAbsolute ? Translator::AbsoluteLocations :
427 Translator::NoLocations);
428 } // </TS>
429 } else {
430 handleError();
431 }
432 }
433 if (hasError()) {
434 m_cd.appendError(errorString());
435 return false;
436 }
437 return true;
438 }
439
numericEntity(int ch)440 static QString numericEntity(int ch)
441 {
442 return QString(ch <= 0x20 ? QLatin1String("<byte value=\"x%1\"/>")
443 : QLatin1String("&#x%1;")) .arg(ch, 0, 16);
444 }
445
protect(const QString & str)446 static QString protect(const QString &str)
447 {
448 QString result;
449 result.reserve(str.length() * 12 / 10);
450 for (int i = 0; i != str.size(); ++i) {
451 uint c = str.at(i).unicode();
452 switch (c) {
453 case '\"':
454 result += QLatin1String(""");
455 break;
456 case '&':
457 result += QLatin1String("&");
458 break;
459 case '>':
460 result += QLatin1String(">");
461 break;
462 case '<':
463 result += QLatin1String("<");
464 break;
465 case '\'':
466 result += QLatin1String("'");
467 break;
468 default:
469 if (c < 0x20 && c != '\r' && c != '\n' && c != '\t')
470 result += numericEntity(c);
471 else // this also covers surrogates
472 result += QChar(c);
473 }
474 }
475 return result;
476 }
477
evilBytes(const QString & str,bool isUtf8,int format,const QByteArray & codecName)478 static QString evilBytes(const QString& str,
479 bool isUtf8, int format, const QByteArray &codecName)
480 {
481 //qDebug() << "EVIL: " << str << isUtf8 << format << codecName;
482 if (isUtf8)
483 return protect(str);
484 if (format == 20)
485 return protect(str);
486 if (codecName == "UTF-8")
487 return protect(str);
488 QTextCodec *codec = QTextCodec::codecForName(codecName);
489 if (!codec)
490 return protect(str);
491 QString t = QString::fromLatin1(codec->fromUnicode(protect(str)).data());
492 int len = (int) t.length();
493 QString result;
494 // FIXME: Factor is sensible only for latin scripts, probably.
495 result.reserve(t.length() * 2);
496 for (int k = 0; k < len; k++) {
497 if (t[k].unicode() >= 0x7f)
498 result += numericEntity(t[k].unicode());
499 else
500 result += t[k];
501 }
502 return result;
503 }
504
writeExtras(QTextStream & t,const char * indent,const TranslatorMessage::ExtraData & extras,const QRegExp & drops)505 static void writeExtras(QTextStream &t, const char *indent,
506 const TranslatorMessage::ExtraData &extras, const QRegExp &drops)
507 {
508 for (Translator::ExtraData::ConstIterator it = extras.begin(); it != extras.end(); ++it) {
509 if (!drops.exactMatch(it.key())) {
510 t << indent << "<extra-" << it.key() << '>'
511 << protect(it.value())
512 << "</extra-" << it.key() << ">\n";
513 }
514 }
515 }
516
writeVariants(QTextStream & t,const char * indent,const QString & input)517 static void writeVariants(QTextStream &t, const char *indent, const QString &input)
518 {
519 int offset;
520 if ((offset = input.indexOf(QChar(Translator::BinaryVariantSeparator))) >= 0) {
521 t << " variants=\"yes\">";
522 int start = 0;
523 forever {
524 t << "\n " << indent << "<lengthvariant>"
525 << protect(input.mid(start, offset - start))
526 << "</lengthvariant>";
527 if (offset == input.length())
528 break;
529 start = offset + 1;
530 offset = input.indexOf(QChar(Translator::BinaryVariantSeparator), start);
531 if (offset < 0)
532 offset = input.length();
533 }
534 t << "\n" << indent;
535 } else {
536 t << ">" << protect(input);
537 }
538 }
539
saveTS(const Translator & translator,QIODevice & dev,ConversionData & cd,int format)540 bool saveTS(const Translator &translator, QIODevice &dev, ConversionData &cd, int format)
541 {
542 bool result = true;
543 QTextStream t(&dev);
544 t.setCodec(QTextCodec::codecForName("UTF-8"));
545 bool trIsUtf8 = (translator.codecName() == "UTF-8");
546 //qDebug() << translator.codecName();
547 bool fileIsUtf8 = (format == 20 || trIsUtf8);
548
549 // The xml prolog allows processors to easily detect the correct encoding
550 t << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n";
551
552 if (format == 11)
553 t << "<TS version=\"1.1\"";
554 else
555 t << "<TS version=\"2.0\"";
556
557 QString languageCode = translator.languageCode();
558 if (!languageCode.isEmpty() && languageCode != QLatin1String("C"))
559 t << " language=\"" << languageCode << "\"";
560 if (format == 20) {
561 languageCode = translator.sourceLanguageCode();
562 if (!languageCode.isEmpty() && languageCode != QLatin1String("C"))
563 t << " sourcelanguage=\"" << languageCode << "\"";
564 }
565 t << ">\n";
566
567 QByteArray codecName = translator.codecName();
568 if (codecName != "ISO-8859-1")
569 t << "<defaultcodec>" << codecName << "</defaultcodec>\n";
570
571 QRegExp drops(cd.dropTags().join(QLatin1String("|")));
572
573 if (format == 20)
574 writeExtras(t, " ", translator.extras(), drops);
575
576 QHash<QString, QList<TranslatorMessage> > messageOrder;
577 QList<QString> contextOrder;
578 foreach (const TranslatorMessage &msg, translator.messages()) {
579 // no need for such noise
580 if (msg.type() == TranslatorMessage::Obsolete && msg.translation().isEmpty())
581 continue;
582
583 QList<TranslatorMessage> &context = messageOrder[msg.context()];
584 if (context.isEmpty())
585 contextOrder.append(msg.context());
586 context.append(msg);
587 }
588 if (cd.sortContexts())
589 qSort(contextOrder);
590
591 QHash<QString, int> currentLine;
592 QString currentFile;
593 foreach (const QString &context, contextOrder) {
594 const TranslatorMessage &firstMsg = messageOrder[context].first();
595 t << "<context" << ((!fileIsUtf8 && firstMsg.isUtf8()) ? " encoding=\"UTF-8\"" : "") << ">\n";
596
597 t << " <name>"
598 << evilBytes(context, firstMsg.isUtf8() || fileIsUtf8, format, codecName)
599 << "</name>\n";
600 foreach (const TranslatorMessage &msg, messageOrder[context]) {
601 //msg.dump();
602
603 bool isUtf8 = msg.isUtf8();
604 bool second = false;
605 forever {
606
607 t << " <message";
608 if (!msg.id().isEmpty())
609 t << " id=\"" << msg.id() << "\"";
610 if (!trIsUtf8) {
611 if (format == 11) {
612 if (isUtf8)
613 t << " encoding=\"UTF-8\"";
614 } else {
615 if (msg.isUtf8()) {
616 if (msg.isNonUtf8())
617 t << " utf8=\"both\"";
618 else
619 t << " utf8=\"true\"";
620 }
621 }
622 }
623 if (msg.isPlural())
624 t << " numerus=\"yes\"";
625 t << ">\n";
626 if (translator.locationsType() != Translator::NoLocations) {
627 QString cfile = currentFile;
628 bool first = true;
629 foreach (const TranslatorMessage::Reference &ref, msg.allReferences()) {
630 QString fn = cd.m_targetDir.relativeFilePath(ref.fileName())
631 .replace(QLatin1Char('\\'),QLatin1Char('/'));
632 int ln = ref.lineNumber();
633 QString ld;
634 if (translator.locationsType() == Translator::RelativeLocations) {
635 if (ln != -1) {
636 int dlt = ln - currentLine[fn];
637 if (dlt >= 0)
638 ld.append(QLatin1Char('+'));
639 ld.append(QString::number(dlt));
640 currentLine[fn] = ln;
641 }
642
643 if (fn != cfile) {
644 if (first)
645 currentFile = fn;
646 cfile = fn;
647 } else {
648 fn.clear();
649 }
650 first = false;
651 } else {
652 if (ln != -1)
653 ld = QString::number(ln);
654 }
655 t << " <location";
656 if (!fn.isEmpty())
657 t << " filename=\"" << fn << "\"";
658 if (!ld.isEmpty())
659 t << " line=\"" << ld << "\"";
660 t << "/>\n";
661 }
662 }
663
664 t << " <source>"
665 << evilBytes(msg.sourceText(), isUtf8, format, codecName)
666 << "</source>\n";
667
668 if (format != 11 && !msg.oldSourceText().isEmpty())
669 t << " <oldsource>" << protect(msg.oldSourceText()) << "</oldsource>\n";
670
671 if (!msg.comment().isEmpty()) {
672 t << " <comment>"
673 << evilBytes(msg.comment(), isUtf8, format, codecName)
674 << "</comment>\n";
675 }
676
677 if (format != 11) {
678
679 if (!msg.oldComment().isEmpty())
680 t << " <oldcomment>" << protect(msg.oldComment()) << "</oldcomment>\n";
681
682 if (!msg.extraComment().isEmpty())
683 t << " <extracomment>" << protect(msg.extraComment())
684 << "</extracomment>\n";
685
686 if (!msg.translatorComment().isEmpty())
687 t << " <translatorcomment>" << protect(msg.translatorComment())
688 << "</translatorcomment>\n";
689
690 }
691
692 t << " <translation";
693 if (msg.type() == TranslatorMessage::Unfinished)
694 t << " type=\"unfinished\"";
695 else if (msg.type() == TranslatorMessage::Obsolete)
696 t << " type=\"obsolete\"";
697 if (msg.isPlural()) {
698 t << ">";
699 const QStringList &translns = msg.translations();
700 for (int j = 0; j < translns.count(); ++j) {
701 t << "\n <numerusform";
702 writeVariants(t, " ", translns[j]);
703 t << "</numerusform>";
704 }
705 t << "\n ";
706 } else {
707 writeVariants(t, " ", msg.translation());
708 }
709 t << "</translation>\n";
710
711 if (format != 11)
712 writeExtras(t, " ", msg.extras(), drops);
713
714 if (!msg.userData().isEmpty())
715 t << " <userdata>" << msg.userData() << "</userdata>\n";
716 t << " </message>\n";
717
718 if (format != 11 || second || !msg.isUtf8() || !msg.isNonUtf8())
719 break;
720 isUtf8 = false;
721 second = true;
722 }
723 }
724 t << "</context>\n";
725 }
726
727 t << "</TS>\n";
728 return result;
729 }
730
loadTS(Translator & translator,QIODevice & dev,ConversionData & cd)731 bool loadTS(Translator &translator, QIODevice &dev, ConversionData &cd)
732 {
733 TSReader reader(dev, cd);
734 return reader.read(translator);
735 }
736
saveTS11(const Translator & translator,QIODevice & dev,ConversionData & cd)737 bool saveTS11(const Translator &translator, QIODevice &dev, ConversionData &cd)
738 {
739 return saveTS(translator, dev, cd, 11);
740 }
741
saveTS20(const Translator & translator,QIODevice & dev,ConversionData & cd)742 bool saveTS20(const Translator &translator, QIODevice &dev, ConversionData &cd)
743 {
744 return saveTS(translator, dev, cd, 20);
745 }
746
initTS()747 int initTS()
748 {
749 Translator::FileFormat format;
750
751 format.extension = QLatin1String("ts11");
752 format.fileType = Translator::FileFormat::TranslationSource;
753 format.priority = -1;
754 format.description = QObject::tr("Qt translation sources (format 1.1)");
755 format.loader = &loadTS;
756 format.saver = &saveTS11;
757 Translator::registerFileFormat(format);
758
759 format.extension = QLatin1String("ts20");
760 format.fileType = Translator::FileFormat::TranslationSource;
761 format.priority = -1;
762 format.description = QObject::tr("Qt translation sources (format 2.0)");
763 format.loader = &loadTS;
764 format.saver = &saveTS20;
765 Translator::registerFileFormat(format);
766
767 // "ts" is always the latest. right now it's ts20.
768 format.extension = QLatin1String("ts");
769 format.fileType = Translator::FileFormat::TranslationSource;
770 format.priority = 0;
771 format.description = QObject::tr("Qt translation sources (latest format)");
772 format.loader = &loadTS;
773 format.saver = &saveTS20;
774 Translator::registerFileFormat(format);
775
776 return 1;
777 }
778
779 Q_CONSTRUCTOR_FUNCTION(initTS)
780
781 QT_END_NAMESPACE
782