199 : isPlural(false), isFuzzy(false) |
200 : isPlural(false), isFuzzy(false) |
200 {} |
201 {} |
201 |
202 |
202 |
203 |
203 public: |
204 public: |
204 QString id; |
205 QByteArray id; |
205 QString context; |
206 QByteArray context; |
206 QString tscomment; |
207 QByteArray tscomment; |
207 QString oldTscomment; |
208 QByteArray oldTscomment; |
208 QString lineNumber; |
209 QByteArray lineNumber; |
209 QString fileName; |
210 QByteArray fileName; |
210 QString references; |
211 QByteArray references; |
211 QString translatorComments; |
212 QByteArray translatorComments; |
212 QString automaticComments; |
213 QByteArray automaticComments; |
213 QString msgId; |
214 QByteArray msgId; |
214 QString oldMsgId; |
215 QByteArray oldMsgId; |
215 QStringList msgStr; |
216 QList<QByteArray> msgStr; |
216 bool isPlural; |
217 bool isPlural; |
217 bool isFuzzy; |
218 bool isFuzzy; |
218 QHash<QString, QString> extra; |
219 QHash<QString, QString> extra; |
219 }; |
220 }; |
220 |
221 |
221 |
222 |
222 static bool isTranslationLine(const QString &line) |
223 static bool isTranslationLine(const QByteArray &line) |
223 { |
224 { |
224 return line.startsWith(QLatin1String("#~ msgstr")) |
225 return line.startsWith("#~ msgstr") || line.startsWith("msgstr"); |
225 || line.startsWith(QLatin1String("msgstr")); |
226 } |
226 } |
227 |
227 |
228 static QByteArray slurpEscapedString(const QList<QByteArray> &lines, int &l, |
228 static QString slurpEscapedString(const QStringList &lines, int & l, |
229 int offset, const QByteArray &prefix, ConversionData &cd) |
229 int offset, const QString &prefix, ConversionData &cd) |
230 { |
230 { |
231 QByteArray msg; |
231 QString msg; |
|
232 int stoff; |
232 int stoff; |
233 |
233 |
234 for (; l < lines.size(); ++l) { |
234 for (; l < lines.size(); ++l) { |
235 const QString &line = lines.at(l); |
235 const QByteArray &line = lines.at(l); |
236 if (line.isEmpty() || !line.startsWith(prefix)) |
236 if (line.isEmpty() || !line.startsWith(prefix)) |
237 break; |
237 break; |
238 while (line[offset].isSpace()) // No length check, as string has no trailing spaces. |
238 while (isspace(line[offset])) // No length check, as string has no trailing spaces. |
239 offset++; |
239 offset++; |
240 if (line[offset].unicode() != '"') |
240 if (line[offset] != '"') |
241 break; |
241 break; |
242 offset++; |
242 offset++; |
243 forever { |
243 forever { |
244 if (offset == line.length()) |
244 if (offset == line.length()) |
245 goto premature_eol; |
245 goto premature_eol; |
246 ushort c = line[offset++].unicode(); |
246 uchar c = line[offset++]; |
247 if (c == '"') { |
247 if (c == '"') { |
248 if (offset == line.length()) |
248 if (offset == line.length()) |
249 break; |
249 break; |
250 while (line[offset].isSpace()) |
250 while (isspace(line[offset])) |
251 offset++; |
251 offset++; |
252 if (line[offset++].unicode() != '"') { |
252 if (line[offset++] != '"') { |
253 cd.appendError(QString::fromLatin1( |
253 cd.appendError(QString::fromLatin1( |
254 "PO parsing error: extra characters on line %1.") |
254 "PO parsing error: extra characters on line %1.") |
255 .arg(l + 1)); |
255 .arg(l + 1)); |
256 break; |
256 break; |
257 } |
257 } |
258 continue; |
258 continue; |
259 } |
259 } |
260 if (c == '\\') { |
260 if (c == '\\') { |
261 if (offset == line.length()) |
261 if (offset == line.length()) |
262 goto premature_eol; |
262 goto premature_eol; |
263 c = line[offset++].unicode(); |
263 c = line[offset++]; |
264 switch (c) { |
264 switch (c) { |
265 case 'r': |
265 case 'r': |
266 msg += QLatin1Char('\r'); // Maybe just throw it away? |
266 msg += '\r'; // Maybe just throw it away? |
267 break; |
267 break; |
268 case 'n': |
268 case 'n': |
269 msg += QLatin1Char('\n'); |
269 msg += '\n'; |
270 break; |
270 break; |
271 case 't': |
271 case 't': |
272 msg += QLatin1Char('\t'); |
272 msg += '\t'; |
273 break; |
273 break; |
274 case 'v': |
274 case 'v': |
275 msg += QLatin1Char('\v'); |
275 msg += '\v'; |
276 break; |
276 break; |
277 case 'a': |
277 case 'a': |
278 msg += QLatin1Char('\a'); |
278 msg += '\a'; |
279 break; |
279 break; |
280 case 'b': |
280 case 'b': |
281 msg += QLatin1Char('\b'); |
281 msg += '\b'; |
282 break; |
282 break; |
283 case 'f': |
283 case 'f': |
284 msg += QLatin1Char('\f'); |
284 msg += '\f'; |
285 break; |
285 break; |
286 case '"': |
286 case '"': |
287 msg += QLatin1Char('"'); |
287 msg += '"'; |
288 break; |
288 break; |
289 case '\\': |
289 case '\\': |
290 msg += QLatin1Char('\\'); |
290 msg += '\\'; |
291 break; |
291 break; |
292 case '0': |
292 case '0': |
293 case '1': |
293 case '1': |
294 case '2': |
294 case '2': |
295 case '3': |
295 case '3': |
296 case '4': |
296 case '4': |
297 case '5': |
297 case '5': |
298 case '6': |
298 case '6': |
299 case '7': |
299 case '7': |
300 stoff = offset - 1; |
300 stoff = offset - 1; |
301 while ((c = line[offset].unicode()) >= '0' && c <= '7') |
301 while ((c = line[offset]) >= '0' && c <= '7') |
302 if (++offset == line.length()) |
302 if (++offset == line.length()) |
303 goto premature_eol; |
303 goto premature_eol; |
304 msg += QChar(line.mid(stoff, offset - stoff).toUInt(0, 8)); |
304 msg += line.mid(stoff, offset - stoff).toUInt(0, 8); |
305 break; |
305 break; |
306 case 'x': |
306 case 'x': |
307 stoff = offset; |
307 stoff = offset; |
308 while (isxdigit(line[offset].unicode())) |
308 while (isxdigit(line[offset])) |
309 if (++offset == line.length()) |
309 if (++offset == line.length()) |
310 goto premature_eol; |
310 goto premature_eol; |
311 msg += QChar(line.mid(stoff, offset - stoff).toUInt(0, 16)); |
311 msg += line.mid(stoff, offset - stoff).toUInt(0, 16); |
312 break; |
312 break; |
313 default: |
313 default: |
314 cd.appendError(QString::fromLatin1( |
314 cd.appendError(QString::fromLatin1( |
315 "PO parsing error: invalid escape '\\%1' (line %2).") |
315 "PO parsing error: invalid escape '\\%1' (line %2).") |
316 .arg(QChar(c)).arg(l + 1)); |
316 .arg(QChar((uint)c)).arg(l + 1)); |
317 msg += QLatin1Char('\\'); |
317 msg += '\\'; |
318 msg += QChar(c); |
318 msg += c; |
319 break; |
319 break; |
320 } |
320 } |
321 } else { |
321 } else { |
322 msg += QChar(c); |
322 msg += c; |
323 } |
323 } |
324 } |
324 } |
325 offset = prefix.size(); |
325 offset = prefix.size(); |
326 } |
326 } |
327 --l; |
327 --l; |
328 return msg; |
328 return msg; |
329 |
329 |
330 premature_eol: |
330 premature_eol: |
331 cd.appendError(QString::fromLatin1( |
331 cd.appendError(QString::fromLatin1( |
332 "PO parsing error: premature end of line %1.").arg(l + 1)); |
332 "PO parsing error: premature end of line %1.").arg(l + 1)); |
333 return QString(); |
333 return QByteArray(); |
334 } |
334 } |
335 |
335 |
336 static void slurpComment(QString &msg, const QStringList &lines, int & l) |
336 static void slurpComment(QByteArray &msg, const QList<QByteArray> &lines, int & l) |
337 { |
337 { |
338 const QChar newline = QLatin1Char('\n'); |
338 QByteArray prefix = lines.at(l); |
339 QString prefix = lines.at(l); |
|
340 for (int i = 1; ; i++) { |
339 for (int i = 1; ; i++) { |
341 if (prefix.at(i).unicode() != ' ') { |
340 if (prefix.at(i) != ' ') { |
342 prefix.truncate(i); |
341 prefix.truncate(i); |
343 break; |
342 break; |
344 } |
343 } |
345 } |
344 } |
346 for (; l < lines.size(); ++l) { |
345 for (; l < lines.size(); ++l) { |
347 const QString &line = lines.at(l); |
346 const QByteArray &line = lines.at(l); |
348 if (line.startsWith(prefix)) |
347 if (line.startsWith(prefix)) |
349 msg += line.mid(prefix.size()); |
348 msg += line.mid(prefix.size()); |
350 else if (line != QLatin1String("#")) |
349 else if (line != "#") |
351 break; |
350 break; |
352 msg += newline; |
351 msg += '\n'; |
353 } |
352 } |
354 --l; |
353 --l; |
355 } |
354 } |
356 |
355 |
|
356 static QString makePoHeader(const QString &str) |
|
357 { |
|
358 return QLatin1String("po-header-") + str.toLower().replace(QLatin1Char('-'), QLatin1Char('_')); |
|
359 } |
|
360 |
|
361 static QByteArray QByteArrayList_join(const QList<QByteArray> &that, char sep) |
|
362 { |
|
363 int totalLength = 0; |
|
364 const int size = that.size(); |
|
365 |
|
366 for (int i = 0; i < size; ++i) |
|
367 totalLength += that.at(i).size(); |
|
368 |
|
369 if (size > 0) |
|
370 totalLength += size - 1; |
|
371 |
|
372 QByteArray res; |
|
373 if (totalLength == 0) |
|
374 return res; |
|
375 res.reserve(totalLength); |
|
376 for (int i = 0; i < that.size(); ++i) { |
|
377 if (i) |
|
378 res += sep; |
|
379 res += that.at(i); |
|
380 } |
|
381 return res; |
|
382 } |
|
383 |
357 bool loadPO(Translator &translator, QIODevice &dev, ConversionData &cd) |
384 bool loadPO(Translator &translator, QIODevice &dev, ConversionData &cd) |
358 { |
385 { |
359 const QChar quote = QLatin1Char('"'); |
386 QTextCodec *codec = QTextCodec::codecForName( |
360 const QChar newline = QLatin1Char('\n'); |
387 cd.m_codecForSource.isEmpty() ? QByteArray("UTF-8") : cd.m_codecForSource); |
361 QTextStream in(&dev); |
|
362 in.setCodec(cd.m_codecForSource.isEmpty() ? QByteArray("UTF-8") : cd.m_codecForSource); |
|
363 bool error = false; |
388 bool error = false; |
364 |
389 |
365 // format of a .po file entry: |
390 // format of a .po file entry: |
366 // white-space |
391 // white-space |
367 // # translator-comments |
392 // # translator-comments |
378 // msgid_plural untranslated-string-plural |
403 // msgid_plural untranslated-string-plural |
379 // msgstr[0] translated-string |
404 // msgstr[0] translated-string |
380 // ... |
405 // ... |
381 |
406 |
382 // we need line based lookahead below. |
407 // we need line based lookahead below. |
383 QStringList lines; |
408 QList<QByteArray> lines; |
384 while (!in.atEnd()) |
409 while (!dev.atEnd()) |
385 lines.append(in.readLine().trimmed()); |
410 lines.append(dev.readLine().trimmed()); |
386 lines.append(QString()); |
411 lines.append(QByteArray()); |
387 |
412 |
388 int l = 0; |
413 int l = 0, lastCmtLine = -1; |
389 PoItem item; |
414 PoItem item; |
390 for (; l != lines.size(); ++l) { |
415 for (; l != lines.size(); ++l) { |
391 QString line = lines.at(l); |
416 QByteArray line = lines.at(l); |
392 if (line.isEmpty()) |
417 if (line.isEmpty()) |
393 continue; |
418 continue; |
394 if (isTranslationLine(line)) { |
419 if (isTranslationLine(line)) { |
395 bool isObsolete = line.startsWith(QLatin1String("#~ msgstr")); |
420 bool isObsolete = line.startsWith("#~ msgstr"); |
396 const QString prefix = QLatin1String(isObsolete ? "#~ " : ""); |
421 const QByteArray prefix = isObsolete ? "#~ " : ""; |
397 while (true) { |
422 while (true) { |
398 int idx = line.indexOf(QLatin1Char(' '), prefix.length()); |
423 int idx = line.indexOf(' ', prefix.length()); |
399 QString str = slurpEscapedString(lines, l, idx, prefix, cd); |
424 QByteArray str = slurpEscapedString(lines, l, idx, prefix, cd); |
400 str.replace(QChar(Translator::TextVariantSeparator), |
|
401 QChar(Translator::BinaryVariantSeparator)); |
|
402 item.msgStr.append(str); |
425 item.msgStr.append(str); |
403 if (l + 1 >= lines.size() || !isTranslationLine(lines.at(l + 1))) |
426 if (l + 1 >= lines.size() || !isTranslationLine(lines.at(l + 1))) |
404 break; |
427 break; |
405 ++l; |
428 ++l; |
406 line = lines.at(l); |
429 line = lines.at(l); |
407 } |
430 } |
408 if (item.msgId.isEmpty()) { |
431 if (item.msgId.isEmpty()) { |
409 QRegExp rx(QLatin1String("\\bX-Language: ([^\n]*)\n")); |
432 QHash<QString, QByteArray> extras; |
410 int idx = rx.indexIn(item.msgStr.first()); |
433 QList<QByteArray> hdrOrder; |
411 if (idx >= 0) { |
434 QByteArray pluralForms; |
412 translator.setLanguageCode(rx.cap(1)); |
435 foreach (const QByteArray &hdr, item.msgStr.first().split('\n')) { |
413 item.msgStr.first().remove(idx, rx.matchedLength()); |
436 if (hdr.isEmpty()) |
414 } |
437 continue; |
415 QRegExp rx2(QLatin1String("\\bX-Source-Language: ([^\n]*)\n")); |
438 int idx = hdr.indexOf(':'); |
416 int idx2 = rx2.indexIn(item.msgStr.first()); |
439 if (idx < 0) { |
417 if (idx2 >= 0) { |
440 cd.appendError(QString::fromLatin1("Unexpected PO header format '%1'\n") |
418 translator.setSourceLanguageCode(rx2.cap(1)); |
441 .arg(QString::fromLatin1(hdr))); |
419 item.msgStr.first().remove(idx2, rx2.matchedLength()); |
442 error = true; |
420 } |
443 break; |
421 if (item.msgStr.first().indexOf( |
444 } |
422 QRegExp(QLatin1String("\\bX-Virgin-Header:[^\n]*\n"))) >= 0) { |
445 QByteArray hdrName = hdr.left(idx).trimmed(); |
423 item = PoItem(); |
446 QByteArray hdrValue = hdr.mid(idx + 1).trimmed(); |
424 continue; |
447 hdrOrder << hdrName; |
425 } |
448 if (hdrName == "X-Language") { |
|
449 translator.setLanguageCode(QString::fromLatin1(hdrValue)); |
|
450 } else if (hdrName == "X-Source-Language") { |
|
451 translator.setSourceLanguageCode(QString::fromLatin1(hdrValue)); |
|
452 } else if (hdrName == "Plural-Forms") { |
|
453 pluralForms = hdrValue; |
|
454 } else if (hdrName == "MIME-Version") { |
|
455 // just assume it is 1.0 |
|
456 } else if (hdrName == "Content-Type") { |
|
457 if (cd.m_codecForSource.isEmpty()) { |
|
458 if (!hdrValue.startsWith("text/plain; charset=")) { |
|
459 cd.appendError(QString::fromLatin1("Unexpected Content-Type header '%1'\n") |
|
460 .arg(QString::fromLatin1(hdrValue))); |
|
461 error = true; |
|
462 // This will avoid a flood of conversion errors. |
|
463 codec = QTextCodec::codecForName("latin1"); |
|
464 } else { |
|
465 QByteArray cod = hdrValue.mid(20); |
|
466 QTextCodec *cdc = QTextCodec::codecForName(cod); |
|
467 if (!cdc) { |
|
468 cd.appendError(QString::fromLatin1("Unsupported codec '%1'\n") |
|
469 .arg(QString::fromLatin1(cod))); |
|
470 error = true; |
|
471 // This will avoid a flood of conversion errors. |
|
472 codec = QTextCodec::codecForName("latin1"); |
|
473 } else { |
|
474 codec = cdc; |
|
475 } |
|
476 } |
|
477 } |
|
478 } else if (hdrName == "Content-Transfer-Encoding") { |
|
479 if (hdrValue != "8bit") { |
|
480 cd.appendError(QString::fromLatin1("Unexpected Content-Transfer-Encoding '%1'\n") |
|
481 .arg(QString::fromLatin1(hdrValue))); |
|
482 return false; |
|
483 } |
|
484 } else if (hdrName == "X-Virgin-Header") { |
|
485 // legacy |
|
486 } else { |
|
487 extras[makePoHeader(QString::fromLatin1(hdrName))] = hdrValue; |
|
488 } |
|
489 } |
|
490 if (!pluralForms.isEmpty()) { |
|
491 if (translator.languageCode().isEmpty()) { |
|
492 extras[makePoHeader(QLatin1String("Plural-Forms"))] = pluralForms; |
|
493 } else { |
|
494 // FIXME: have fun with making a consistency check ... |
|
495 } |
|
496 } |
|
497 // Eliminate the field if only headers we added are present in standard order. |
|
498 // Keep in sync with savePO |
|
499 static const char * const dfltHdrs[] = { |
|
500 "MIME-Version", "Content-Type", "Content-Transfer-Encoding", |
|
501 "Plural-Forms", "X-Language", "X-Source-Language" |
|
502 }; |
|
503 uint cdh = 0; |
|
504 for (int cho = 0; cho < hdrOrder.length(); cho++) { |
|
505 for (;; cdh++) { |
|
506 if (cdh == sizeof(dfltHdrs)/sizeof(dfltHdrs[0])) { |
|
507 extras[QLatin1String("po-headers")] = |
|
508 QByteArrayList_join(hdrOrder, ','); |
|
509 goto doneho; |
|
510 } |
|
511 if (hdrOrder.at(cho) == dfltHdrs[cdh]) { |
|
512 cdh++; |
|
513 break; |
|
514 } |
|
515 } |
|
516 } |
|
517 doneho: |
|
518 if (lastCmtLine != -1) |
|
519 extras[QLatin1String("po-header_comment")] = |
|
520 QByteArrayList_join(lines.mid(0, lastCmtLine + 1), '\n'); |
|
521 for (QHash<QString, QByteArray>::ConstIterator it = extras.constBegin(), |
|
522 end = extras.constEnd(); |
|
523 it != end; ++it) |
|
524 translator.setExtra(it.key(), codec->toUnicode(it.value())); |
|
525 item = PoItem(); |
|
526 continue; |
426 } |
527 } |
427 // build translator message |
528 // build translator message |
428 TranslatorMessage msg; |
529 TranslatorMessage msg; |
429 msg.setContext(item.context); |
530 msg.setContext(codec->toUnicode(item.context)); |
430 if (!item.references.isEmpty()) { |
531 if (!item.references.isEmpty()) { |
431 foreach (const QString &ref, |
532 foreach (const QString &ref, |
432 item.references.split(QRegExp(QLatin1String("\\s")), |
533 codec->toUnicode(item.references).split( |
433 QString::SkipEmptyParts)) { |
534 QRegExp(QLatin1String("\\s")), QString::SkipEmptyParts)) { |
434 int pos = ref.lastIndexOf(QLatin1Char(':')); |
535 int pos = ref.lastIndexOf(QLatin1Char(':')); |
435 if (pos != -1) |
536 if (pos != -1) |
436 msg.addReference(ref.left(pos), ref.mid(pos + 1).toInt()); |
537 msg.addReference(ref.left(pos), ref.mid(pos + 1).toInt()); |
437 } |
538 } |
438 } else if (isObsolete) { |
539 } else if (isObsolete) { |
439 msg.setFileName(QLatin1String(MAGIC_OBSOLETE_REFERENCE)); |
540 msg.setFileName(QLatin1String(MAGIC_OBSOLETE_REFERENCE)); |
440 } |
541 } |
441 msg.setId(item.id); |
542 msg.setId(codec->toUnicode(item.id)); |
442 msg.setSourceText(item.msgId); |
543 msg.setSourceText(codec->toUnicode(item.msgId)); |
443 msg.setOldSourceText(item.oldMsgId); |
544 msg.setOldSourceText(codec->toUnicode(item.oldMsgId)); |
444 msg.setComment(item.tscomment); |
545 msg.setComment(codec->toUnicode(item.tscomment)); |
445 msg.setOldComment(item.oldTscomment); |
546 msg.setOldComment(codec->toUnicode(item.oldTscomment)); |
446 msg.setExtraComment(item.automaticComments); |
547 msg.setExtraComment(codec->toUnicode(item.automaticComments)); |
447 msg.setTranslatorComment(item.translatorComments); |
548 msg.setTranslatorComment(codec->toUnicode(item.translatorComments)); |
448 msg.setPlural(item.isPlural || item.msgStr.size() > 1); |
549 msg.setPlural(item.isPlural || item.msgStr.size() > 1); |
449 msg.setTranslations(item.msgStr); |
550 QStringList translations; |
|
551 foreach (const QByteArray &bstr, item.msgStr) { |
|
552 QString str = codec->toUnicode(bstr); |
|
553 str.replace(QChar(Translator::TextVariantSeparator), |
|
554 QChar(Translator::BinaryVariantSeparator)); |
|
555 translations << str; |
|
556 } |
|
557 msg.setTranslations(translations); |
450 if (isObsolete) |
558 if (isObsolete) |
451 msg.setType(TranslatorMessage::Obsolete); |
559 msg.setType(TranslatorMessage::Obsolete); |
452 else if (item.isFuzzy) |
560 else if (item.isFuzzy || (!msg.sourceText().isEmpty() && !msg.isTranslated())) |
453 msg.setType(TranslatorMessage::Unfinished); |
561 msg.setType(TranslatorMessage::Unfinished); |
454 else |
562 else |
455 msg.setType(TranslatorMessage::Finished); |
563 msg.setType(TranslatorMessage::Finished); |
456 msg.setExtras(item.extra); |
564 msg.setExtras(item.extra); |
457 |
565 |
458 //qDebug() << "WRITE: " << context; |
566 //qDebug() << "WRITE: " << context; |
459 //qDebug() << "SOURCE: " << msg.sourceText(); |
567 //qDebug() << "SOURCE: " << msg.sourceText(); |
460 //qDebug() << flags << msg.m_extra; |
568 //qDebug() << flags << msg.m_extra; |
461 translator.append(msg); |
569 translator.append(msg); |
462 item = PoItem(); |
570 item = PoItem(); |
463 } else if (line.startsWith(QLatin1Char('#'))) { |
571 } else if (line.startsWith('#')) { |
464 switch(line.size() < 2 ? 0 : line.at(1).unicode()) { |
572 switch (line.size() < 2 ? 0 : line.at(1)) { |
465 case ':': |
573 case ':': |
466 item.references += line.mid(3); |
574 item.references += line.mid(3); |
467 item.references += newline; |
575 item.references += '\n'; |
468 break; |
576 break; |
469 case ',': { |
577 case ',': { |
470 QStringList flags = |
578 QStringList flags = |
471 line.mid(2).split(QRegExp(QLatin1String("[, ]")), |
579 QString::fromLatin1(line.mid(2)).split( |
472 QString::SkipEmptyParts); |
580 QRegExp(QLatin1String("[, ]")), QString::SkipEmptyParts); |
473 if (flags.removeOne(QLatin1String("fuzzy"))) |
581 if (flags.removeOne(QLatin1String("fuzzy"))) |
474 item.isFuzzy = true; |
582 item.isFuzzy = true; |
|
583 flags.removeOne(QLatin1String("qt-format")); |
475 TranslatorMessage::ExtraData::const_iterator it = |
584 TranslatorMessage::ExtraData::const_iterator it = |
476 item.extra.find(QLatin1String("po-flags")); |
585 item.extra.find(QLatin1String("po-flags")); |
477 if (it != item.extra.end()) |
586 if (it != item.extra.end()) |
478 flags.prepend(*it); |
587 flags.prepend(*it); |
479 if (!flags.isEmpty()) |
588 if (!flags.isEmpty()) |
480 item.extra[QLatin1String("po-flags")] = flags.join(QLatin1String(", ")); |
589 item.extra[QLatin1String("po-flags")] = flags.join(QLatin1String(", ")); |
481 break; |
590 break; |
482 } |
591 } |
483 case 0: |
592 case 0: |
484 item.translatorComments += newline; |
593 item.translatorComments += '\n'; |
485 break; |
594 break; |
486 case ' ': |
595 case ' ': |
487 slurpComment(item.translatorComments, lines, l); |
596 slurpComment(item.translatorComments, lines, l); |
488 break; |
597 break; |
489 case '.': |
598 case '.': |
490 if (line.startsWith(QLatin1String("#. ts-context "))) { |
599 if (line.startsWith("#. ts-context ")) { |
491 item.context = line.mid(14); |
600 item.context = line.mid(14); |
492 } else if (line.startsWith(QLatin1String("#. ts-id "))) { |
601 } else if (line.startsWith("#. ts-id ")) { |
493 item.id = line.mid(9); |
602 item.id = line.mid(9); |
494 } else { |
603 } else { |
495 item.automaticComments += line.mid(3); |
604 item.automaticComments += line.mid(3); |
496 item.automaticComments += newline; |
605 item.automaticComments += '\n'; |
497 } |
606 } |
498 break; |
607 break; |
499 case '|': |
608 case '|': |
500 if (line.startsWith(QLatin1String("#| msgid "))) { |
609 if (line.startsWith("#| msgid ")) { |
501 item.oldMsgId = slurpEscapedString(lines, l, 9, QLatin1String("#| "), cd); |
610 item.oldMsgId = slurpEscapedString(lines, l, 9, "#| ", cd); |
502 } else if (line.startsWith(QLatin1String("#| msgid_plural "))) { |
611 } else if (line.startsWith("#| msgid_plural ")) { |
503 QString extra = slurpEscapedString(lines, l, 16, QLatin1String("#| "), cd); |
612 QByteArray extra = slurpEscapedString(lines, l, 16, "#| ", cd); |
504 if (extra != item.oldMsgId) |
613 if (extra != item.oldMsgId) |
505 item.extra[QLatin1String("po-old_msgid_plural")] = extra; |
614 item.extra[QLatin1String("po-old_msgid_plural")] = |
506 } else if (line.startsWith(QLatin1String("#| msgctxt "))) { |
615 codec->toUnicode(extra); |
507 item.oldTscomment = slurpEscapedString(lines, l, 11, QLatin1String("#| "), cd); |
616 } else if (line.startsWith("#| msgctxt ")) { |
|
617 item.oldTscomment = slurpEscapedString(lines, l, 11, "#| ", cd); |
508 } else { |
618 } else { |
509 cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n")) |
619 cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n")) |
510 .arg(l + 1).arg(lines[l])); |
620 .arg(l + 1).arg(codec->toUnicode(lines[l]))); |
511 error = true; |
621 error = true; |
512 } |
622 } |
513 break; |
623 break; |
514 case '~': |
624 case '~': |
515 if (line.startsWith(QLatin1String("#~ msgid "))) { |
625 if (line.startsWith("#~ msgid ")) { |
516 item.msgId = slurpEscapedString(lines, l, 9, QLatin1String("#~ "), cd); |
626 item.msgId = slurpEscapedString(lines, l, 9, "#~ ", cd); |
517 } else if (line.startsWith(QLatin1String("#~ msgid_plural "))) { |
627 } else if (line.startsWith("#~ msgid_plural ")) { |
518 QString extra = slurpEscapedString(lines, l, 16, QLatin1String("#~ "), cd); |
628 QByteArray extra = slurpEscapedString(lines, l, 16, "#~ ", cd); |
519 if (extra != item.msgId) |
629 if (extra != item.msgId) |
520 item.extra[QLatin1String("po-msgid_plural")] = extra; |
630 item.extra[QLatin1String("po-msgid_plural")] = |
|
631 codec->toUnicode(extra); |
521 item.isPlural = true; |
632 item.isPlural = true; |
522 } else if (line.startsWith(QLatin1String("#~ msgctxt "))) { |
633 } else if (line.startsWith("#~ msgctxt ")) { |
523 item.tscomment = slurpEscapedString(lines, l, 11, QLatin1String("#~ "), cd); |
634 item.tscomment = slurpEscapedString(lines, l, 11, "#~ ", cd); |
524 } else { |
635 } else { |
525 cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n")) |
636 cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n")) |
526 .arg(l + 1).arg(lines[l])); |
637 .arg(l + 1).arg(codec->toUnicode(lines[l]))); |
527 error = true; |
638 error = true; |
528 } |
639 } |
529 break; |
640 break; |
530 default: |
641 default: |
531 cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n")) |
642 cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'\n")) |
532 .arg(l + 1).arg(lines[l])); |
643 .arg(l + 1).arg(codec->toUnicode(lines[l]))); |
533 error = true; |
644 error = true; |
534 break; |
645 break; |
535 } |
646 } |
536 } else if (line.startsWith(QLatin1String("msgctxt "))) { |
647 lastCmtLine = l; |
537 item.tscomment = slurpEscapedString(lines, l, 8, QString(), cd); |
648 } else if (line.startsWith("msgctxt ")) { |
538 } else if (line.startsWith(QLatin1String("msgid "))) { |
649 item.tscomment = slurpEscapedString(lines, l, 8, QByteArray(), cd); |
539 item.msgId = slurpEscapedString(lines, l, 6, QString(), cd); |
650 } else if (line.startsWith("msgid ")) { |
540 } else if (line.startsWith(QLatin1String("msgid_plural "))) { |
651 item.msgId = slurpEscapedString(lines, l, 6, QByteArray(), cd); |
541 QString extra = slurpEscapedString(lines, l, 13, QString(), cd); |
652 } else if (line.startsWith("msgid_plural ")) { |
|
653 QByteArray extra = slurpEscapedString(lines, l, 13, QByteArray(), cd); |
542 if (extra != item.msgId) |
654 if (extra != item.msgId) |
543 item.extra[QLatin1String("po-msgid_plural")] = extra; |
655 item.extra[QLatin1String("po-msgid_plural")] = codec->toUnicode(extra); |
544 item.isPlural = true; |
656 item.isPlural = true; |
545 } else { |
657 } else { |
546 cd.appendError(QString(QLatin1String("PO-format error in line %1: '%2'\n")) |
658 cd.appendError(QString(QLatin1String("PO-format error in line %1: '%2'\n")) |
547 .arg(l + 1).arg(lines[l])); |
659 .arg(l + 1).arg(codec->toUnicode(lines[l]))); |
548 error = true; |
660 error = true; |
549 } |
661 } |
550 } |
662 } |
551 return !error && cd.errors().isEmpty(); |
663 return !error && cd.errors().isEmpty(); |
552 } |
664 } |
553 |
665 |
|
666 static void addPoHeader(Translator::ExtraData &headers, QStringList &hdrOrder, |
|
667 const char *name, const QString &value) |
|
668 { |
|
669 QString qName = QLatin1String(name); |
|
670 if (!hdrOrder.contains(qName)) |
|
671 hdrOrder << qName; |
|
672 headers[makePoHeader(qName)] = value; |
|
673 } |
|
674 |
554 bool savePO(const Translator &translator, QIODevice &dev, ConversionData &cd) |
675 bool savePO(const Translator &translator, QIODevice &dev, ConversionData &cd) |
555 { |
676 { |
|
677 QString str_format = QLatin1String("-format"); |
|
678 |
556 bool ok = true; |
679 bool ok = true; |
557 QTextStream out(&dev); |
680 QTextStream out(&dev); |
558 out.setCodec(cd.m_outputCodec.isEmpty() ? QByteArray("UTF-8") : cd.m_outputCodec); |
681 out.setCodec(cd.m_outputCodec.isEmpty() ? QByteArray("UTF-8") : cd.m_outputCodec); |
559 |
682 |
560 bool first = true; |
683 QString cmt = translator.extra(QLatin1String("po-header_comment")); |
561 if (translator.messages().isEmpty() || !translator.messages().first().sourceText().isEmpty()) { |
684 if (!cmt.isEmpty()) |
562 out << |
685 out << cmt << '\n'; |
563 "# SOME DESCRIPTIVE TITLE.\n" |
686 out << "msgid \"\"\n"; |
564 "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n" |
687 Translator::ExtraData headers = translator.extras(); |
565 "# This file is distributed under the same license as the PACKAGE package.\n" |
688 QStringList hdrOrder = translator.extra(QLatin1String("po-headers")).split( |
566 "# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n" |
689 QLatin1Char(','), QString::SkipEmptyParts); |
567 "#\n" |
690 // Keep in sync with loadPO |
568 "#, fuzzy\n" |
691 addPoHeader(headers, hdrOrder, "MIME-Version", QLatin1String("1.0")); |
569 "msgid \"\"\n" |
692 addPoHeader(headers, hdrOrder, "Content-Type", |
570 "msgstr \"\"\n" |
693 QLatin1String("text/plain; charset=" + out.codec()->name())); |
571 "\"X-Virgin-Header: remove this line if you change anything in the header.\\n\"\n"; |
694 addPoHeader(headers, hdrOrder, "Content-Transfer-Encoding", QLatin1String("8bit")); |
572 if (!translator.languageCode().isEmpty()) |
695 if (!translator.languageCode().isEmpty()) { |
573 out << "\"X-Language: " << translator.languageCode() << "\\n\"\n"; |
696 QLocale::Language l; |
574 if (!translator.sourceLanguageCode().isEmpty()) |
697 QLocale::Country c; |
575 out << "\"X-Source-Language: " << translator.sourceLanguageCode() << "\\n\"\n"; |
698 Translator::languageAndCountry(translator.languageCode(), &l, &c); |
576 first = false; |
699 const char *gettextRules; |
577 } |
700 if (getNumerusInfo(l, c, 0, 0, &gettextRules)) |
|
701 addPoHeader(headers, hdrOrder, "Plural-Forms", QLatin1String(gettextRules)); |
|
702 addPoHeader(headers, hdrOrder, "X-Language", translator.languageCode()); |
|
703 } |
|
704 if (!translator.sourceLanguageCode().isEmpty()) |
|
705 addPoHeader(headers, hdrOrder, "X-Source-Language", translator.sourceLanguageCode()); |
|
706 QString hdrStr; |
|
707 foreach (const QString &hdr, hdrOrder) { |
|
708 hdrStr += hdr; |
|
709 hdrStr += QLatin1String(": "); |
|
710 hdrStr += headers.value(makePoHeader(hdr)); |
|
711 hdrStr += QLatin1Char('\n'); |
|
712 } |
|
713 out << poEscapedString(QString(), QString::fromLatin1("msgstr"), true, hdrStr); |
|
714 |
578 foreach (const TranslatorMessage &msg, translator.messages()) { |
715 foreach (const TranslatorMessage &msg, translator.messages()) { |
579 if (!first) |
716 out << endl; |
580 out << endl; |
|
581 |
717 |
582 if (!msg.translatorComment().isEmpty()) |
718 if (!msg.translatorComment().isEmpty()) |
583 out << poEscapedLines(QLatin1String("#"), true, msg.translatorComment()); |
719 out << poEscapedLines(QLatin1String("#"), true, msg.translatorComment()); |
584 |
720 |
585 if (!msg.extraComment().isEmpty()) |
721 if (!msg.extraComment().isEmpty()) |