375 QStringDecoder toUnicode(QStringConverter::Utf8, QStringDecoder::Flag::Stateless);
397 QList<QByteArray> lines;
399 lines.append(dev.readLine().trimmed());
400 lines.append(QByteArray());
402 int l = 0, lastCmtLine = -1;
403 bool qtContexts =
false;
405 for (; l != lines.size(); ++l) {
406 QByteArray line = lines.at(l);
409 if (isTranslationLine(line)) {
410 bool isObsolete = line.startsWith(
"#~ msgstr");
411 const QByteArray prefix = isObsolete ?
"#~ " :
"";
413 int idx = line.indexOf(
' ', prefix.size());
414 QByteArray str = slurpEscapedString(lines, l, idx, prefix, cd);
415 item.msgStr.append(str);
416 if (l + 1 >= lines.size() || !isTranslationLine(lines.at(l + 1)))
421 if (item.msgId.isEmpty()) {
422 QHash<QString, QByteArray> extras;
423 QList<QByteArray> hdrOrder;
424 QByteArray pluralForms;
425 for (
const QByteArray &hdr : item.msgStr.first().split(
'\n')) {
428 int idx = hdr.indexOf(
':');
430 cd.appendError(QString::fromLatin1(
"Unexpected PO header format '%1'")
431 .arg(QString::fromLatin1(hdr)));
435 QByteArray hdrName = hdr.left(idx).trimmed();
436 QByteArray hdrValue = hdr.mid(idx + 1).trimmed();
438 if (hdrName ==
"X-Language") {
439 translator.setLanguageCode(QString::fromLatin1(hdrValue));
440 }
else if (hdrName ==
"X-Source-Language") {
441 translator.setSourceLanguageCode(QString::fromLatin1(hdrValue));
442 }
else if (hdrName ==
"X-Qt-Contexts") {
443 qtContexts = (hdrValue ==
"true");
444 }
else if (hdrName ==
"Plural-Forms") {
445 pluralForms = hdrValue;
446 }
else if (hdrName ==
"MIME-Version") {
448 }
else if (hdrName ==
"Content-Type") {
449 if (!hdrValue.startsWith(
"text/plain; charset=")) {
450 cd.appendError(QString::fromLatin1(
"Unexpected Content-Type header '%1'")
451 .arg(QString::fromLatin1(hdrValue)));
454 toUnicode = QStringDecoder(QStringConverter::Latin1);
456 QByteArray cod = hdrValue.mid(20);
457 auto enc = QStringConverter::encodingForName(cod);
459 cd.appendError(QString::fromLatin1(
"Unsupported encoding '%1'")
460 .arg(QString::fromLatin1(cod)));
463 toUnicode = QStringDecoder(QStringConverter::Latin1);
465 toUnicode = QStringDecoder(*enc);
468 }
else if (hdrName ==
"Content-Transfer-Encoding") {
469 if (hdrValue !=
"8bit") {
470 cd.appendError(QString::fromLatin1(
"Unexpected Content-Transfer-Encoding '%1'")
471 .arg(QString::fromLatin1(hdrValue)));
474 }
else if (hdrName ==
"X-Virgin-Header") {
477 extras[makePoHeader(QString::fromLatin1(hdrName))] = hdrValue;
480 if (!pluralForms.isEmpty()) {
481 if (translator.languageCode().isEmpty()) {
482 extras[makePoHeader(QLatin1String(
"Plural-Forms"))] = pluralForms;
489 static const char *
const dfltHdrs[] = {
490 "MIME-Version",
"Content-Type",
"Content-Transfer-Encoding",
491 "Plural-Forms",
"X-Language",
"X-Source-Language",
"X-Qt-Contexts"
494 for (
int cho = 0; cho < hdrOrder.size(); cho++) {
496 if (cdh ==
sizeof(dfltHdrs)/
sizeof(dfltHdrs[0])) {
497 extras[QLatin1String(
"po-headers")] =
498 QByteArrayList_join(hdrOrder,
',');
501 if (hdrOrder.at(cho) == dfltHdrs[cdh]) {
508 if (lastCmtLine != -1) {
509 extras[QLatin1String(
"po-header_comment")] =
510 QByteArrayList_join(lines.mid(0, lastCmtLine + 1),
'\n');
512 for (
auto it = extras.cbegin(), end = extras.cend(); it != end; ++it)
513 translator.setExtra(it.key(), toUnicode(it.value()));
519 msg.setContext(toUnicode(item.context));
520 if (!item.references.isEmpty()) {
522 for (
const QString &ref :
523 QString(toUnicode(item.references)).split(
524 QRegularExpression(QLatin1String(
"\\s")), Qt::SkipEmptyParts)) {
525 int pos = ref.indexOf(QLatin1Char(
':'));
526 int lpos = ref.lastIndexOf(QLatin1Char(
':'));
527 if (pos != -1 && pos == lpos) {
529 int lno = ref.mid(pos + 1).toInt(&ok);
531 msg.addReference(ref.left(pos), lno);
535 if (!xrefs.isEmpty())
536 xrefs += QLatin1Char(
' ');
539 if (!xrefs.isEmpty())
540 item.extra[QLatin1String(
"po-references")] = xrefs;
542 msg.setId(toUnicode(item.id));
543 msg.setSourceText(toUnicode(item.msgId));
544 msg.setOldSourceText(toUnicode(item.oldMsgId));
545 msg.setComment(toUnicode(item.tscomment));
546 msg.setOldComment(toUnicode(item.oldTscomment));
547 msg.setExtraComment(toUnicode(item.automaticComments));
548 msg.setTranslatorComment(toUnicode(item.translatorComments));
550 QStringList translations;
551 for (
const QByteArray &bstr : std::as_const(item.msgStr)) {
552 QString str = toUnicode(bstr);
553 str.replace(Translator::TextVariantSeparator, Translator::BinaryVariantSeparator);
556 msg.setTranslations(translations);
558 if (isObsolete && isFuzzy)
573 }
else if (line.startsWith(
'#')) {
574 switch (line.size() < 2 ? 0 : line.at(1)) {
576 item.references += line.mid(3);
577 item.references +=
'\n';
581 QString::fromLatin1(line.mid(2)).split(
582 QRegularExpression(QLatin1String(
"[, ]")), Qt::SkipEmptyParts);
583 if (flags.removeOne(QLatin1String(
"fuzzy")))
585 flags.removeOne(QLatin1String(
"qt-format"));
586 const auto it = item.extra.constFind(QLatin1String(
"po-flags"));
587 if (it != item.extra.cend())
589 if (!flags.isEmpty())
590 item.extra[QLatin1String(
"po-flags")] = flags.join(QLatin1String(
", "));
594 item.translatorComments +=
'\n';
597 slurpComment(item.translatorComments, lines, l);
600 if (line.startsWith(
"#. ts-context ")) {
601 item.context = line.mid(14);
602 }
else if (line.startsWith(
"#. ts-id ")) {
603 item.id = line.mid(9);
605 item.automaticComments += line.mid(3);
610 if (line.startsWith(
"#| msgid ")) {
611 item.oldMsgId = slurpEscapedString(lines, l, 9,
"#| ", cd);
612 }
else if (line.startsWith(
"#| msgid_plural ")) {
613 QByteArray extra = slurpEscapedString(lines, l, 16,
"#| ", cd);
614 if (extra != item.oldMsgId)
615 item.extra[QLatin1String(
"po-old_msgid_plural")] =
617 }
else if (line.startsWith(
"#| msgctxt ")) {
618 item.oldTscomment = slurpEscapedString(lines, l, 11,
"#| ", cd);
620 splitContext(&item.oldTscomment, &item.context);
622 cd.appendError(QString(QLatin1String(
"PO-format parse error in line %1: '%2'"))
623 .arg(l + 1).arg(toUnicode(lines[l])));
628 if (line.startsWith(
"#~ msgid ")) {
629 item.msgId = slurpEscapedString(lines, l, 9,
"#~ ", cd);
630 }
else if (line.startsWith(
"#~ msgid_plural ")) {
631 QByteArray extra = slurpEscapedString(lines, l, 16,
"#~ ", cd);
632 if (extra != item.msgId)
633 item.extra[QLatin1String(
"po-msgid_plural")] =
636 }
else if (line.startsWith(
"#~ msgctxt ")) {
637 item.tscomment = slurpEscapedString(lines, l, 11,
"#~ ", cd);
639 splitContext(&item.tscomment, &item.context);
640 }
else if (line.startsWith(
"#~| msgid ")) {
641 item.oldMsgId = slurpEscapedString(lines, l, 10,
"#~| ", cd);
642 }
else if (line.startsWith(
"#~| msgid_plural ")) {
643 QByteArray extra = slurpEscapedString(lines, l, 17,
"#~| ", cd);
644 if (extra != item.oldMsgId)
645 item.extra[QLatin1String(
"po-old_msgid_plural")] =
647 }
else if (line.startsWith(
"#~| msgctxt ")) {
648 item.oldTscomment = slurpEscapedString(lines, l, 12,
"#~| ", cd);
650 splitContext(&item.oldTscomment, &item.context);
652 cd.appendError(QString(QLatin1String(
"PO-format parse error in line %1: '%2'"))
653 .arg(l + 1).arg(toUnicode(lines[l])));
658 cd.appendError(QString(QLatin1String(
"PO-format parse error in line %1: '%2'"))
659 .arg(l + 1).arg(toUnicode(lines[l])));
664 }
else if (line.startsWith(
"msgctxt ")) {
665 item.tscomment = slurpEscapedString(lines, l, 8, QByteArray(), cd);
667 splitContext(&item.tscomment, &item.context);
668 }
else if (line.startsWith(
"msgid ")) {
669 item.msgId = slurpEscapedString(lines, l, 6, QByteArray(), cd);
670 }
else if (line.startsWith(
"msgid_plural ")) {
671 QByteArray extra = slurpEscapedString(lines, l, 13, QByteArray(), cd);
672 if (extra != item.msgId)
673 item.extra[QLatin1String(
"po-msgid_plural")] = toUnicode(extra);
676 cd.appendError(QString(QLatin1String(
"PO-format error in line %1: '%2'"))
677 .arg(l + 1).arg(toUnicode(lines[l])));
681 return !error && cd.errors().isEmpty();
705 QString str_format = QLatin1String(
"-format");
708 QTextStream out(&dev);
710 bool qtContexts =
false;
711 for (
const TranslatorMessage &msg : translator.messages())
712 if (!msg.context().isEmpty()) {
717 QString cmt = translator.extra(QLatin1String(
"po-header_comment"));
720 out <<
"msgid \"\"\n";
722 QStringList hdrOrder = translator.extra(QLatin1String(
"po-headers")).split(
723 QLatin1Char(
','), Qt::SkipEmptyParts);
725 addPoHeader(headers, hdrOrder,
"MIME-Version", QLatin1String(
"1.0"));
726 addPoHeader(headers, hdrOrder,
"Content-Type",
727 QLatin1String(
"text/plain; charset=UTF-8"));
728 addPoHeader(headers, hdrOrder,
"Content-Transfer-Encoding", QLatin1String(
"8bit"));
729 if (!translator.languageCode().isEmpty()) {
731 QLocale::Territory c;
732 Translator::languageAndTerritory(translator.languageCode(), &l, &c);
733 const char *gettextRules;
734 if (getNumerusInfo(l, c, 0, 0, &gettextRules))
735 addPoHeader(headers, hdrOrder,
"Plural-Forms", QLatin1String(gettextRules));
736 addPoHeader(headers, hdrOrder,
"X-Language", translator.languageCode());
738 if (!translator.sourceLanguageCode().isEmpty())
739 addPoHeader(headers, hdrOrder,
"X-Source-Language", translator.sourceLanguageCode());
741 addPoHeader(headers, hdrOrder,
"X-Qt-Contexts", QLatin1String(
"true"));
743 for (
const QString &hdr : std::as_const(hdrOrder)) {
745 hdrStr += QLatin1String(
": ");
746 hdrStr += headers.value(makePoHeader(hdr));
747 hdrStr += QLatin1Char(
'\n');
749 out << poEscapedString(QString(), QString::fromLatin1(
"msgstr"),
true, hdrStr);
751 for (
const TranslatorMessage &msg : translator.messages()) {
754 if (!msg.translatorComment().isEmpty())
755 out << poEscapedLines(QLatin1String(
"#"),
true, msg.translatorComment());
757 if (!msg.extraComment().isEmpty())
758 out << poEscapedLines(QLatin1String(
"#."),
true, msg.extraComment());
760 if (!msg.id().isEmpty())
761 out << QLatin1String(
"#. ts-id ") << msg.id() <<
'\n';
763 QString xrefs = msg.extra(QLatin1String(
"po-references"));
764 if (!msg.fileName().isEmpty() || !xrefs.isEmpty()) {
766 for (
const TranslatorMessage::Reference &ref : msg.allReferences())
767 refs.append(QString(QLatin1String(
"%2:%1"))
768 .arg(ref.lineNumber()).arg(ref.fileName()));
769 if (!xrefs.isEmpty())
771 out << poWrappedEscapedLines(QLatin1String(
"#:"),
true, refs.join(QLatin1Char(
' ')));
775 bool skipFormat =
false;
777 if ((msg.type() == TranslatorMessage::Unfinished
778 || msg.type() == TranslatorMessage::Obsolete) && msg.isTranslated())
779 flags.append(QLatin1String(
"fuzzy"));
780 const auto itr = msg.extras().constFind(QLatin1String(
"po-flags"));
781 if (itr != msg.extras().cend()) {
782 const QStringList atoms = itr->split(QLatin1String(
", "));
783 for (
const QString &atom : atoms)
784 if (atom.endsWith(str_format)) {
788 if (atoms.contains(QLatin1String(
"no-wrap")))
793 QString source = msg.sourceText();
796 for (
int off = 0; (off = source.indexOf(QLatin1Char(
'%'), off)) >= 0; ) {
797 if (++off >= source.size())
799 if (source.at(off) == QLatin1Char(
'n') || source.at(off).isDigit()) {
800 flags.append(QLatin1String(
"qt-format"));
805 if (!flags.isEmpty())
806 out <<
"#, " << flags.join(QLatin1String(
", ")) <<
'\n';
808 bool isObsolete = (msg.type() == TranslatorMessage::Obsolete
809 || msg.type() == TranslatorMessage::Vanished);
810 QString prefix = QLatin1String(isObsolete ?
"#~| " :
"#| ");
811 if (!msg.oldComment().isEmpty())
812 out << poEscapedString(prefix, QLatin1String(
"msgctxt"), noWrap,
813 escapeComment(msg.oldComment(), qtContexts));
814 if (!msg.oldSourceText().isEmpty())
815 out << poEscapedString(prefix, QLatin1String(
"msgid"), noWrap, msg.oldSourceText());
816 QString plural = msg.extra(QLatin1String(
"po-old_msgid_plural"));
817 if (!plural.isEmpty())
818 out << poEscapedString(prefix, QLatin1String(
"msgid_plural"), noWrap, plural);
819 prefix = QLatin1String(isObsolete ?
"#~ " :
"");
820 if (!msg.context().isEmpty())
821 out << poEscapedString(prefix, QLatin1String(
"msgctxt"), noWrap,
822 escapeComment(msg.context(),
true) + QLatin1Char(
'|')
823 + escapeComment(msg.comment(),
true));
824 else if (!msg.comment().isEmpty())
825 out << poEscapedString(prefix, QLatin1String(
"msgctxt"), noWrap,
826 escapeComment(msg.comment(), qtContexts));
827 out << poEscapedString(prefix, QLatin1String(
"msgid"), noWrap, msg.sourceText());
828 if (!msg.isPlural()) {
829 QString transl = msg.translation();
830 transl.replace(Translator::BinaryVariantSeparator, Translator::TextVariantSeparator);
831 out << poEscapedString(prefix, QLatin1String(
"msgstr"), noWrap, transl);
833 QString plural = msg.extra(QLatin1String(
"po-msgid_plural"));
834 if (plural.isEmpty())
835 plural = msg.sourceText();
836 out << poEscapedString(prefix, QLatin1String(
"msgid_plural"), noWrap, plural);
837 const QStringList &translations = msg.translations();
838 for (
int i = 0; i != translations.size(); ++i) {
839 QString str = translations.at(i);
840 str.replace(QChar(Translator::BinaryVariantSeparator),
841 QChar(Translator::TextVariantSeparator));
842 out << poEscapedString(prefix, QString::fromLatin1(
"msgstr[%1]").arg(i), noWrap,