tools/linguist/shared/po.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the Qt Linguist of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "translator.h"
       
    43 
       
    44 #include <QtCore/QDebug>
       
    45 #include <QtCore/QIODevice>
       
    46 #include <QtCore/QHash>
       
    47 #include <QtCore/QString>
       
    48 #include <QtCore/QTextStream>
       
    49 
       
    50 #include <ctype.h>
       
    51 
       
    52 #define MAGIC_OBSOLETE_REFERENCE "Obsolete_PO_entries"
       
    53 
       
    54 // Uncomment if you wish to hard wrap long lines in .po files. Note that this
       
    55 // affects only msg strings, not comments.
       
    56 //#define HARD_WRAP_LONG_WORDS
       
    57 
       
    58 QT_BEGIN_NAMESPACE
       
    59 
       
    60 static const int MAX_LEN = 79;
       
    61 
       
    62 static QString poEscapedString(const QString &prefix, const QString &keyword,
       
    63                                bool noWrap, const QString &ba)
       
    64 {
       
    65     QStringList lines;
       
    66     int off = 0;
       
    67     QString res;
       
    68     while (off < ba.length()) {
       
    69         ushort c = ba[off++].unicode();
       
    70         switch (c) {
       
    71         case '\n':
       
    72             res += QLatin1String("\\n");
       
    73             lines.append(res);
       
    74             res.clear();
       
    75             break;
       
    76         case '\r':
       
    77             res += QLatin1String("\\r");
       
    78             break;
       
    79         case '\t':
       
    80             res += QLatin1String("\\t");
       
    81             break;
       
    82         case '\v':
       
    83             res += QLatin1String("\\v");
       
    84             break;
       
    85         case '\a':
       
    86             res += QLatin1String("\\a");
       
    87             break;
       
    88         case '\b':
       
    89             res += QLatin1String("\\b");
       
    90             break;
       
    91         case '\f':
       
    92             res += QLatin1String("\\f");
       
    93             break;
       
    94         case '"':
       
    95             res += QLatin1String("\\\"");
       
    96             break;
       
    97         case '\\':
       
    98             res += QLatin1String("\\\\");
       
    99             break;
       
   100         default:
       
   101             if (c < 32) {
       
   102                 res += QLatin1String("\\x");
       
   103                 res += QString::number(c, 16);
       
   104                 if (off < ba.length() && isxdigit(ba[off].unicode()))
       
   105                     res += QLatin1String("\"\"");
       
   106             } else {
       
   107                 res += QChar(c);
       
   108             }
       
   109             break;
       
   110         }
       
   111     }
       
   112     if (!res.isEmpty())
       
   113         lines.append(res);
       
   114     if (!lines.isEmpty()) {
       
   115         if (!noWrap) {
       
   116             if (lines.count() != 1 ||
       
   117                 lines.first().length() > MAX_LEN - keyword.length() - prefix.length() - 3)
       
   118             {
       
   119                 QStringList olines = lines;
       
   120                 lines = QStringList(QString());
       
   121                 const int maxlen = MAX_LEN - prefix.length() - 2;
       
   122                 foreach (const QString &line, olines) {
       
   123                     int off = 0;
       
   124                     while (off + maxlen < line.length()) {
       
   125                         int idx = line.lastIndexOf(QLatin1Char(' '), off + maxlen - 1) + 1;
       
   126                         if (idx == off) {
       
   127 #ifdef HARD_WRAP_LONG_WORDS
       
   128                             // This doesn't seem too nice, but who knows ...
       
   129                             idx = off + maxlen;
       
   130 #else
       
   131                             idx = line.indexOf(QLatin1Char(' '), off + maxlen) + 1;
       
   132                             if (!idx)
       
   133                                 break;
       
   134 #endif
       
   135                         }
       
   136                         lines.append(line.mid(off, idx - off));
       
   137                         off = idx;
       
   138                     }
       
   139                     lines.append(line.mid(off));
       
   140                 }
       
   141             }
       
   142         } else if (lines.count() > 1) {
       
   143             lines.prepend(QString());
       
   144         }
       
   145     }
       
   146     return prefix + keyword + QLatin1String(" \"") +
       
   147            lines.join(QLatin1String("\"\n") + prefix + QLatin1Char('"')) +
       
   148            QLatin1String("\"\n");
       
   149 }
       
   150 
       
   151 static QString poEscapedLines(const QString &prefix, bool addSpace, const QStringList &lines)
       
   152 {
       
   153     QString out;
       
   154     foreach (const QString &line, lines) {
       
   155         out += prefix;
       
   156         if (addSpace && !line.isEmpty())
       
   157             out += QLatin1Char(' ' );
       
   158         out += line;
       
   159         out += QLatin1Char('\n');
       
   160     }
       
   161     return out;
       
   162 }
       
   163 
       
   164 static QString poEscapedLines(const QString &prefix, bool addSpace, const QString &in0)
       
   165 {
       
   166     QString in = in0;
       
   167     if (in.endsWith(QLatin1Char('\n')))
       
   168         in.chop(1);
       
   169     return poEscapedLines(prefix, addSpace, in.split(QLatin1Char('\n')));
       
   170 }
       
   171 
       
   172 static QString poWrappedEscapedLines(const QString &prefix, bool addSpace, const QString &line)
       
   173 {
       
   174     const int maxlen = MAX_LEN - prefix.length();
       
   175     QStringList lines;
       
   176     int off = 0;
       
   177     while (off + maxlen < line.length()) {
       
   178         int idx = line.lastIndexOf(QLatin1Char(' '), off + maxlen - 1);
       
   179         if (idx < off) {
       
   180 #if 0 //def HARD_WRAP_LONG_WORDS
       
   181             // This cannot work without messing up semantics, so do not even try.
       
   182 #else
       
   183             idx = line.indexOf(QLatin1Char(' '), off + maxlen);
       
   184             if (idx < 0)
       
   185                 break;
       
   186 #endif
       
   187         }
       
   188         lines.append(line.mid(off, idx - off));
       
   189         off = idx + 1;
       
   190     }
       
   191     lines.append(line.mid(off));
       
   192     return poEscapedLines(prefix, addSpace, lines);
       
   193 }
       
   194 
       
   195 struct PoItem
       
   196 {
       
   197 public:
       
   198     PoItem()
       
   199       : isPlural(false), isFuzzy(false)
       
   200     {}
       
   201 
       
   202 
       
   203 public:
       
   204     QString id;
       
   205     QString context;
       
   206     QString tscomment;
       
   207     QString oldTscomment;
       
   208     QString lineNumber;
       
   209     QString fileName;
       
   210     QString references;
       
   211     QString translatorComments;
       
   212     QString automaticComments;
       
   213     QString msgId;
       
   214     QString oldMsgId;
       
   215     QStringList msgStr;
       
   216     bool isPlural;
       
   217     bool isFuzzy;
       
   218     QHash<QString, QString> extra;
       
   219 };
       
   220 
       
   221 
       
   222 static bool isTranslationLine(const QString &line)
       
   223 {
       
   224     return line.startsWith(QLatin1String("#~ msgstr"))
       
   225            || line.startsWith(QLatin1String("msgstr"));
       
   226 }
       
   227 
       
   228 static QString slurpEscapedString(const QStringList &lines, int & l,
       
   229         int offset, const QString &prefix, ConversionData &cd)
       
   230 {
       
   231     QString msg;
       
   232     int stoff;
       
   233 
       
   234     for (; l < lines.size(); ++l) {
       
   235         const QString &line = lines.at(l);
       
   236         if (line.isEmpty() || !line.startsWith(prefix))
       
   237             break;
       
   238         while (line[offset].isSpace()) // No length check, as string has no trailing spaces.
       
   239             offset++;
       
   240         if (line[offset].unicode() != '"')
       
   241             break;
       
   242         offset++;
       
   243         forever {
       
   244             if (offset == line.length())
       
   245                 goto premature_eol;
       
   246             ushort c = line[offset++].unicode();
       
   247             if (c == '"') {
       
   248                 if (offset == line.length())
       
   249                     break;
       
   250                 while (line[offset].isSpace())
       
   251                     offset++;
       
   252                 if (line[offset++].unicode() != '"') {
       
   253                     cd.appendError(QString::fromLatin1(
       
   254                             "PO parsing error: extra characters on line %1.")
       
   255                             .arg(l + 1));
       
   256                     break;
       
   257                 }
       
   258                 continue;
       
   259             }
       
   260             if (c == '\\') {
       
   261                 if (offset == line.length())
       
   262                     goto premature_eol;
       
   263                 c = line[offset++].unicode();
       
   264                 switch (c) {
       
   265                 case 'r':
       
   266                     msg += QLatin1Char('\r'); // Maybe just throw it away?
       
   267                     break;
       
   268                 case 'n':
       
   269                     msg += QLatin1Char('\n');
       
   270                     break;
       
   271                 case 't':
       
   272                     msg += QLatin1Char('\t');
       
   273                     break;
       
   274                 case 'v':
       
   275                     msg += QLatin1Char('\v');
       
   276                     break;
       
   277                 case 'a':
       
   278                     msg += QLatin1Char('\a');
       
   279                     break;
       
   280                 case 'b':
       
   281                     msg += QLatin1Char('\b');
       
   282                     break;
       
   283                 case 'f':
       
   284                     msg += QLatin1Char('\f');
       
   285                     break;
       
   286                 case '"':
       
   287                     msg += QLatin1Char('"');
       
   288                     break;
       
   289                 case '\\':
       
   290                     msg += QLatin1Char('\\');
       
   291                     break;
       
   292                 case '0':
       
   293                 case '1':
       
   294                 case '2':
       
   295                 case '3':
       
   296                 case '4':
       
   297                 case '5':
       
   298                 case '6':
       
   299                 case '7':
       
   300                     stoff = offset - 1;
       
   301                     while ((c = line[offset].unicode()) >= '0' && c <= '7')
       
   302                         if (++offset == line.length())
       
   303                             goto premature_eol;
       
   304                     msg += QChar(line.mid(stoff, offset - stoff).toUInt(0, 8));
       
   305                     break;
       
   306                 case 'x':
       
   307                     stoff = offset;
       
   308                     while (isxdigit(line[offset].unicode()))
       
   309                         if (++offset == line.length())
       
   310                             goto premature_eol;
       
   311                     msg += QChar(line.mid(stoff, offset - stoff).toUInt(0, 16));
       
   312                     break;
       
   313                 default:
       
   314                     cd.appendError(QString::fromLatin1(
       
   315                             "PO parsing error: invalid escape '\\%1' (line %2).")
       
   316                             .arg(QChar(c)).arg(l + 1));
       
   317                     msg += QLatin1Char('\\');
       
   318                     msg += QChar(c);
       
   319                     break;
       
   320                 }
       
   321             } else {
       
   322                 msg += QChar(c);
       
   323             }
       
   324         }
       
   325         offset = prefix.size();
       
   326     }
       
   327     --l;
       
   328     return msg;
       
   329 
       
   330 premature_eol:
       
   331     cd.appendError(QString::fromLatin1(
       
   332             "PO parsing error: premature end of line %1.").arg(l + 1));
       
   333     return QString();
       
   334 }
       
   335 
       
   336 static void slurpComment(QString &msg, const QStringList &lines, int & l)
       
   337 {
       
   338     const QChar newline = QLatin1Char('\n');
       
   339     QString prefix = lines.at(l);
       
   340     for (int i = 1; ; i++) {
       
   341         if (prefix.at(i).unicode() != ' ') {
       
   342             prefix.truncate(i);
       
   343             break;
       
   344         }
       
   345     }
       
   346     for (; l < lines.size(); ++l) {
       
   347         const QString &line = lines.at(l);
       
   348         if (line.startsWith(prefix))
       
   349             msg += line.mid(prefix.size());
       
   350         else if (line != QLatin1String("#"))
       
   351             break;
       
   352         msg += newline;
       
   353     }
       
   354     --l;
       
   355 }
       
   356 
       
   357 bool loadPO(Translator &translator, QIODevice &dev, ConversionData &cd)
       
   358 {
       
   359     const QChar quote = QLatin1Char('"');
       
   360     const QChar newline = QLatin1Char('\n');
       
   361     QTextStream in(&dev);
       
   362     in.setCodec(cd.m_codecForSource.isEmpty() ? QByteArray("UTF-8") : cd.m_codecForSource);
       
   363     bool error = false;
       
   364 
       
   365     // format of a .po file entry:
       
   366     // white-space
       
   367     // #  translator-comments
       
   368     // #. automatic-comments
       
   369     // #: reference...
       
   370     // #, flag...
       
   371     // #~ msgctxt, msgid*, msgstr - used for obsoleted messages
       
   372     // #| msgctxt, msgid* previous untranslated-string - for fuzzy message
       
   373     // msgctx string-context
       
   374     // msgid untranslated-string
       
   375     // -- For singular:
       
   376     // msgstr translated-string
       
   377     // -- For plural:
       
   378     // msgid_plural untranslated-string-plural
       
   379     // msgstr[0] translated-string
       
   380     // ...
       
   381 
       
   382     // we need line based lookahead below.
       
   383     QStringList lines;
       
   384     while (!in.atEnd())
       
   385         lines.append(in.readLine().trimmed());
       
   386     lines.append(QString());
       
   387 
       
   388     int l = 0;
       
   389     PoItem item;
       
   390     for (; l != lines.size(); ++l) {
       
   391         QString line = lines.at(l);
       
   392         if (line.isEmpty())
       
   393            continue;
       
   394         if (isTranslationLine(line)) {
       
   395             bool isObsolete = line.startsWith(QLatin1String("#~ msgstr"));
       
   396             const QString prefix = QLatin1String(isObsolete ? "#~ " : "");
       
   397             while (true) {
       
   398                 int idx = line.indexOf(QLatin1Char(' '), prefix.length());
       
   399                 QString str = slurpEscapedString(lines, l, idx, prefix, cd);
       
   400                 str.replace(QChar(Translator::TextVariantSeparator),
       
   401                             QChar(Translator::BinaryVariantSeparator));
       
   402                 item.msgStr.append(str);
       
   403                 if (l + 1 >= lines.size() || !isTranslationLine(lines.at(l + 1)))
       
   404                     break;
       
   405                 ++l;
       
   406                 line = lines.at(l);
       
   407             }
       
   408             if (item.msgId.isEmpty()) {
       
   409                 QRegExp rx(QLatin1String("\\bX-Language: ([^\n]*)\n"));
       
   410                 int idx = rx.indexIn(item.msgStr.first());
       
   411                 if (idx >= 0) {
       
   412                     translator.setLanguageCode(rx.cap(1));
       
   413                     item.msgStr.first().remove(idx, rx.matchedLength());
       
   414                 }
       
   415                 QRegExp rx2(QLatin1String("\\bX-Source-Language: ([^\n]*)\n"));
       
   416                 int idx2 = rx2.indexIn(item.msgStr.first());
       
   417                 if (idx2 >= 0) {
       
   418                     translator.setSourceLanguageCode(rx2.cap(1));
       
   419                     item.msgStr.first().remove(idx2, rx2.matchedLength());
       
   420                 }
       
   421                 if (item.msgStr.first().indexOf(
       
   422                         QRegExp(QLatin1String("\\bX-Virgin-Header:[^\n]*\n"))) >= 0) {
       
   423                     item = PoItem();
       
   424                     continue;
       
   425                 }
       
   426             }
       
   427             // build translator message
       
   428             TranslatorMessage msg;
       
   429             msg.setContext(item.context);
       
   430             if (!item.references.isEmpty()) {
       
   431                 foreach (const QString &ref,
       
   432                          item.references.split(QRegExp(QLatin1String("\\s")),
       
   433                                                QString::SkipEmptyParts)) {
       
   434                     int pos = ref.lastIndexOf(QLatin1Char(':'));
       
   435                     if (pos != -1)
       
   436                         msg.addReference(ref.left(pos), ref.mid(pos + 1).toInt());
       
   437                 }
       
   438             } else if (isObsolete) {
       
   439                 msg.setFileName(QLatin1String(MAGIC_OBSOLETE_REFERENCE));
       
   440             }
       
   441             msg.setId(item.id);
       
   442             msg.setSourceText(item.msgId);
       
   443             msg.setOldSourceText(item.oldMsgId);
       
   444             msg.setComment(item.tscomment);
       
   445             msg.setOldComment(item.oldTscomment);
       
   446             msg.setExtraComment(item.automaticComments);
       
   447             msg.setTranslatorComment(item.translatorComments);
       
   448             msg.setPlural(item.isPlural || item.msgStr.size() > 1);
       
   449             msg.setTranslations(item.msgStr);
       
   450             if (isObsolete)
       
   451                 msg.setType(TranslatorMessage::Obsolete);
       
   452             else if (item.isFuzzy)
       
   453                 msg.setType(TranslatorMessage::Unfinished);
       
   454             else
       
   455                 msg.setType(TranslatorMessage::Finished);
       
   456             msg.setExtras(item.extra);
       
   457 
       
   458             //qDebug() << "WRITE: " << context;
       
   459             //qDebug() << "SOURCE: " << msg.sourceText();
       
   460             //qDebug() << flags << msg.m_extra;
       
   461             translator.append(msg);
       
   462             item = PoItem();
       
   463         } else if (line.startsWith(QLatin1Char('#'))) {
       
   464             switch(line.size() < 2 ? 0 : line.at(1).unicode()) {
       
   465                 case ':':
       
   466                     item.references += line.mid(3);
       
   467                     item.references += newline;
       
   468                     break;
       
   469                 case ',': {
       
   470                     QStringList flags =
       
   471                             line.mid(2).split(QRegExp(QLatin1String("[, ]")),
       
   472                                               QString::SkipEmptyParts);
       
   473                     if (flags.removeOne(QLatin1String("fuzzy")))
       
   474                         item.isFuzzy = true;
       
   475                     TranslatorMessage::ExtraData::const_iterator it =
       
   476                             item.extra.find(QLatin1String("po-flags"));
       
   477                     if (it != item.extra.end())
       
   478                         flags.prepend(*it);
       
   479                     if (!flags.isEmpty())
       
   480                         item.extra[QLatin1String("po-flags")] = flags.join(QLatin1String(", "));
       
   481                     break;
       
   482                 }
       
   483                 case 0:
       
   484                     item.translatorComments += newline;
       
   485                     break;
       
   486                 case ' ':
       
   487                     slurpComment(item.translatorComments, lines, l);
       
   488                     break;
       
   489                 case '.':
       
   490                     if (line.startsWith(QLatin1String("#. ts-context "))) {
       
   491                         item.context = line.mid(14);
       
   492                     } else if (line.startsWith(QLatin1String("#. ts-id "))) {
       
   493                         item.id = line.mid(9);
       
   494                     } else {
       
   495                         item.automaticComments += line.mid(3);
       
   496                         item.automaticComments += newline;
       
   497                     }
       
   498                     break;
       
   499                 case '|':
       
   500                     if (line.startsWith(QLatin1String("#| msgid "))) {
       
   501                         item.oldMsgId = slurpEscapedString(lines, l, 9, QLatin1String("#| "), cd);
       
   502                     } else if (line.startsWith(QLatin1String("#| msgid_plural "))) {
       
   503                         QString extra = slurpEscapedString(lines, l, 16, QLatin1String("#| "), cd);
       
   504                         if (extra != item.oldMsgId)
       
   505                             item.extra[QLatin1String("po-old_msgid_plural")] = extra;
       
   506                     } else if (line.startsWith(QLatin1String("#| msgctxt "))) {
       
   507                         item.oldTscomment = slurpEscapedString(lines, l, 11, QLatin1String("#| "), cd);
       
   508                     } else {
       
   509                         cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n"))
       
   510                             .arg(l + 1).arg(lines[l]));
       
   511                         error = true;
       
   512                     }
       
   513                     break;
       
   514                 case '~':
       
   515                     if (line.startsWith(QLatin1String("#~ msgid "))) {
       
   516                         item.msgId = slurpEscapedString(lines, l, 9, QLatin1String("#~ "), cd);
       
   517                     } else if (line.startsWith(QLatin1String("#~ msgid_plural "))) {
       
   518                         QString extra = slurpEscapedString(lines, l, 16, QLatin1String("#~ "), cd);
       
   519                         if (extra != item.msgId)
       
   520                             item.extra[QLatin1String("po-msgid_plural")] = extra;
       
   521                         item.isPlural = true;
       
   522                     } else if (line.startsWith(QLatin1String("#~ msgctxt "))) {
       
   523                         item.tscomment = slurpEscapedString(lines, l, 11, QLatin1String("#~ "), cd);
       
   524                     } else {
       
   525                         cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n"))
       
   526                             .arg(l + 1).arg(lines[l]));
       
   527                         error = true;
       
   528                     }
       
   529                     break;
       
   530                 default:
       
   531                     cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n"))
       
   532                         .arg(l + 1).arg(lines[l]));
       
   533                     error = true;
       
   534                     break;
       
   535             }
       
   536         } else if (line.startsWith(QLatin1String("msgctxt "))) {
       
   537             item.tscomment = slurpEscapedString(lines, l, 8, QString(), cd);
       
   538         } else if (line.startsWith(QLatin1String("msgid "))) {
       
   539             item.msgId = slurpEscapedString(lines, l, 6, QString(), cd);
       
   540         } else if (line.startsWith(QLatin1String("msgid_plural "))) {
       
   541             QString extra = slurpEscapedString(lines, l, 13, QString(), cd);
       
   542             if (extra != item.msgId)
       
   543                 item.extra[QLatin1String("po-msgid_plural")] = extra;
       
   544             item.isPlural = true;
       
   545         } else {
       
   546             cd.appendError(QString(QLatin1String("PO-format error in line %1: '%2'\n"))
       
   547                 .arg(l + 1).arg(lines[l]));
       
   548             error = true;
       
   549         }
       
   550     }
       
   551     return !error && cd.errors().isEmpty();
       
   552 }
       
   553 
       
   554 bool savePO(const Translator &translator, QIODevice &dev, ConversionData &cd)
       
   555 {
       
   556     bool ok = true;
       
   557     QTextStream out(&dev);
       
   558     out.setCodec(cd.m_outputCodec.isEmpty() ? QByteArray("UTF-8") : cd.m_outputCodec);
       
   559 
       
   560     bool first = true;
       
   561     if (translator.messages().isEmpty() || !translator.messages().first().sourceText().isEmpty()) {
       
   562         out <<
       
   563             "# SOME DESCRIPTIVE TITLE.\n"
       
   564             "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n"
       
   565             "# This file is distributed under the same license as the PACKAGE package.\n"
       
   566             "# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n"
       
   567             "#\n"
       
   568             "#, fuzzy\n"
       
   569             "msgid \"\"\n"
       
   570             "msgstr \"\"\n"
       
   571             "\"X-Virgin-Header: remove this line if you change anything in the header.\\n\"\n";
       
   572         if (!translator.languageCode().isEmpty())
       
   573             out << "\"X-Language: " << translator.languageCode() << "\\n\"\n";
       
   574         if (!translator.sourceLanguageCode().isEmpty())
       
   575             out << "\"X-Source-Language: " << translator.sourceLanguageCode() << "\\n\"\n";
       
   576         first = false;
       
   577     }
       
   578     foreach (const TranslatorMessage &msg, translator.messages()) {
       
   579         if (!first)
       
   580             out << endl;
       
   581 
       
   582         if (!msg.translatorComment().isEmpty())
       
   583             out << poEscapedLines(QLatin1String("#"), true, msg.translatorComment());
       
   584 
       
   585         if (!msg.extraComment().isEmpty())
       
   586             out << poEscapedLines(QLatin1String("#."), true, msg.extraComment());
       
   587 
       
   588         if (!msg.context().isEmpty())
       
   589             out << QLatin1String("#. ts-context ") << msg.context() << '\n';
       
   590         if (!msg.id().isEmpty())
       
   591             out << QLatin1String("#. ts-id ") << msg.id() << '\n';
       
   592 
       
   593         if (!msg.fileName().isEmpty() && msg.fileName() != QLatin1String(MAGIC_OBSOLETE_REFERENCE)) {
       
   594             QStringList refs;
       
   595             foreach (const TranslatorMessage::Reference &ref, msg.allReferences())
       
   596                 refs.append(QString(QLatin1String("%2:%1"))
       
   597                                     .arg(ref.lineNumber()).arg(ref.fileName()));
       
   598             out << poWrappedEscapedLines(QLatin1String("#:"), true, refs.join(QLatin1String(" ")));
       
   599         }
       
   600 
       
   601         bool noWrap = false;
       
   602         QStringList flags;
       
   603         if (msg.type() == TranslatorMessage::Unfinished)
       
   604             flags.append(QLatin1String("fuzzy"));
       
   605         TranslatorMessage::ExtraData::const_iterator itr =
       
   606                 msg.extras().find(QLatin1String("po-flags"));
       
   607         if (itr != msg.extras().end()) {
       
   608             if (itr->split(QLatin1String(", ")).contains(QLatin1String("no-wrap")))
       
   609                 noWrap = true;
       
   610             flags.append(*itr);
       
   611         }
       
   612         if (!flags.isEmpty())
       
   613             out << "#, " << flags.join(QLatin1String(", ")) << '\n';
       
   614 
       
   615         QString prefix = QLatin1String("#| ");
       
   616         if (!msg.oldComment().isEmpty())
       
   617             out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap, msg.oldComment());
       
   618         if (!msg.oldSourceText().isEmpty())
       
   619             out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.oldSourceText());
       
   620         QString plural = msg.extra(QLatin1String("po-old_msgid_plural"));
       
   621         if (!plural.isEmpty())
       
   622             out << poEscapedString(prefix, QLatin1String("msgid_plural"), noWrap, plural);
       
   623         prefix = QLatin1String((msg.type() == TranslatorMessage::Obsolete) ? "#~ " : "");
       
   624         if (!msg.comment().isEmpty())
       
   625             out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap, msg.comment());
       
   626         out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.sourceText());
       
   627         if (!msg.isPlural()) {
       
   628             QString transl = msg.translation();
       
   629             if (first) {
       
   630                 transl.remove(QRegExp(QLatin1String("\\bX-Language:[^\n]*\n")));
       
   631                 if (!translator.languageCode().isEmpty())
       
   632                     transl += QLatin1String("X-Language: ") + translator.languageCode() + QLatin1Char('\n');
       
   633             }
       
   634             out << poEscapedString(prefix, QLatin1String("msgstr"), noWrap, transl);
       
   635         } else {
       
   636             QString plural = msg.extra(QLatin1String("po-msgid_plural"));
       
   637             if (plural.isEmpty())
       
   638                 plural = msg.sourceText();
       
   639             out << poEscapedString(prefix, QLatin1String("msgid_plural"), noWrap, plural);
       
   640             const QStringList &translations = msg.translations();
       
   641             for (int i = 0; i != translations.size(); ++i) {
       
   642                 QString str = translations.at(i);
       
   643                 str.replace(QChar(Translator::BinaryVariantSeparator),
       
   644                             QChar(Translator::TextVariantSeparator));
       
   645                 out << poEscapedString(prefix, QString::fromLatin1("msgstr[%1]").arg(i), noWrap,
       
   646                                        str);
       
   647             }
       
   648         }
       
   649         first = false;
       
   650     }
       
   651     return ok;
       
   652 }
       
   653 
       
   654 int initPO()
       
   655 {
       
   656     Translator::FileFormat format;
       
   657     format.extension = QLatin1String("po");
       
   658     format.description = QObject::tr("GNU Gettext localization files");
       
   659     format.loader = &loadPO;
       
   660     format.saver = &savePO;
       
   661     format.fileType = Translator::FileFormat::TranslationSource;
       
   662     format.priority = 1;
       
   663     Translator::registerFileFormat(format);
       
   664     return 1;
       
   665 }
       
   666 
       
   667 Q_CONSTRUCTOR_FUNCTION(initPO)
       
   668 
       
   669 QT_END_NAMESPACE