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(QChar(Translator::TextVariantSeparator),
554 QChar(Translator::BinaryVariantSeparator));
557 msg.setTranslations(translations);
559 if (isObsolete && isFuzzy)
574 }
else if (line.startsWith(
'#')) {
575 switch (line.size() < 2 ? 0 : line.at(1)) {
577 item.references += line.mid(3);
578 item.references +=
'\n';
582 QString::fromLatin1(line.mid(2)).split(
583 QRegularExpression(QLatin1String(
"[, ]")), Qt::SkipEmptyParts);
584 if (flags.removeOne(QLatin1String(
"fuzzy")))
586 flags.removeOne(QLatin1String(
"qt-format"));
587 const auto it = item.extra.constFind(QLatin1String(
"po-flags"));
588 if (it != item.extra.cend())
590 if (!flags.isEmpty())
591 item.extra[QLatin1String(
"po-flags")] = flags.join(QLatin1String(
", "));
595 item.translatorComments +=
'\n';
598 slurpComment(item.translatorComments, lines, l);
601 if (line.startsWith(
"#. ts-context ")) {
602 item.context = line.mid(14);
603 }
else if (line.startsWith(
"#. ts-id ")) {
604 item.id = line.mid(9);
606 item.automaticComments += line.mid(3);
611 if (line.startsWith(
"#| msgid ")) {
612 item.oldMsgId = slurpEscapedString(lines, l, 9,
"#| ", cd);
613 }
else if (line.startsWith(
"#| msgid_plural ")) {
614 QByteArray extra = slurpEscapedString(lines, l, 16,
"#| ", cd);
615 if (extra != item.oldMsgId)
616 item.extra[QLatin1String(
"po-old_msgid_plural")] =
618 }
else if (line.startsWith(
"#| msgctxt ")) {
619 item.oldTscomment = slurpEscapedString(lines, l, 11,
"#| ", cd);
621 splitContext(&item.oldTscomment, &item.context);
623 cd.appendError(QString(QLatin1String(
"PO-format parse error in line %1: '%2'"))
624 .arg(l + 1).arg(toUnicode(lines[l])));
629 if (line.startsWith(
"#~ msgid ")) {
630 item.msgId = slurpEscapedString(lines, l, 9,
"#~ ", cd);
631 }
else if (line.startsWith(
"#~ msgid_plural ")) {
632 QByteArray extra = slurpEscapedString(lines, l, 16,
"#~ ", cd);
633 if (extra != item.msgId)
634 item.extra[QLatin1String(
"po-msgid_plural")] =
637 }
else if (line.startsWith(
"#~ msgctxt ")) {
638 item.tscomment = slurpEscapedString(lines, l, 11,
"#~ ", cd);
640 splitContext(&item.tscomment, &item.context);
641 }
else if (line.startsWith(
"#~| msgid ")) {
642 item.oldMsgId = slurpEscapedString(lines, l, 10,
"#~| ", cd);
643 }
else if (line.startsWith(
"#~| msgid_plural ")) {
644 QByteArray extra = slurpEscapedString(lines, l, 17,
"#~| ", cd);
645 if (extra != item.oldMsgId)
646 item.extra[QLatin1String(
"po-old_msgid_plural")] =
648 }
else if (line.startsWith(
"#~| msgctxt ")) {
649 item.oldTscomment = slurpEscapedString(lines, l, 12,
"#~| ", cd);
651 splitContext(&item.oldTscomment, &item.context);
653 cd.appendError(QString(QLatin1String(
"PO-format parse error in line %1: '%2'"))
654 .arg(l + 1).arg(toUnicode(lines[l])));
659 cd.appendError(QString(QLatin1String(
"PO-format parse error in line %1: '%2'"))
660 .arg(l + 1).arg(toUnicode(lines[l])));
665 }
else if (line.startsWith(
"msgctxt ")) {
666 item.tscomment = slurpEscapedString(lines, l, 8, QByteArray(), cd);
668 splitContext(&item.tscomment, &item.context);
669 }
else if (line.startsWith(
"msgid ")) {
670 item.msgId = slurpEscapedString(lines, l, 6, QByteArray(), cd);
671 }
else if (line.startsWith(
"msgid_plural ")) {
672 QByteArray extra = slurpEscapedString(lines, l, 13, QByteArray(), cd);
673 if (extra != item.msgId)
674 item.extra[QLatin1String(
"po-msgid_plural")] = toUnicode(extra);
677 cd.appendError(QString(QLatin1String(
"PO-format error in line %1: '%2'"))
678 .arg(l + 1).arg(toUnicode(lines[l])));
682 return !error && cd.errors().isEmpty();
706 QString str_format = QLatin1String(
"-format");
709 QTextStream out(&dev);
711 bool qtContexts =
false;
712 for (
const TranslatorMessage &msg : translator.messages())
713 if (!msg.context().isEmpty()) {
718 QString cmt = translator.extra(QLatin1String(
"po-header_comment"));
721 out <<
"msgid \"\"\n";
723 QStringList hdrOrder = translator.extra(QLatin1String(
"po-headers")).split(
724 QLatin1Char(
','), Qt::SkipEmptyParts);
726 addPoHeader(headers, hdrOrder,
"MIME-Version", QLatin1String(
"1.0"));
727 addPoHeader(headers, hdrOrder,
"Content-Type",
728 QLatin1String(
"text/plain; charset=UTF-8"));
729 addPoHeader(headers, hdrOrder,
"Content-Transfer-Encoding", QLatin1String(
"8bit"));
730 if (!translator.languageCode().isEmpty()) {
732 QLocale::Territory c;
733 Translator::languageAndTerritory(translator.languageCode(), &l, &c);
734 const char *gettextRules;
735 if (getNumerusInfo(l, c, 0, 0, &gettextRules))
736 addPoHeader(headers, hdrOrder,
"Plural-Forms", QLatin1String(gettextRules));
737 addPoHeader(headers, hdrOrder,
"X-Language", translator.languageCode());
739 if (!translator.sourceLanguageCode().isEmpty())
740 addPoHeader(headers, hdrOrder,
"X-Source-Language", translator.sourceLanguageCode());
742 addPoHeader(headers, hdrOrder,
"X-Qt-Contexts", QLatin1String(
"true"));
744 for (
const QString &hdr : std::as_const(hdrOrder)) {
746 hdrStr += QLatin1String(
": ");
747 hdrStr += headers.value(makePoHeader(hdr));
748 hdrStr += QLatin1Char(
'\n');
750 out << poEscapedString(QString(), QString::fromLatin1(
"msgstr"),
true, hdrStr);
752 for (
const TranslatorMessage &msg : translator.messages()) {
755 if (!msg.translatorComment().isEmpty())
756 out << poEscapedLines(QLatin1String(
"#"),
true, msg.translatorComment());
758 if (!msg.extraComment().isEmpty())
759 out << poEscapedLines(QLatin1String(
"#."),
true, msg.extraComment());
761 if (!msg.id().isEmpty())
762 out << QLatin1String(
"#. ts-id ") << msg.id() <<
'\n';
764 QString xrefs = msg.extra(QLatin1String(
"po-references"));
765 if (!msg.fileName().isEmpty() || !xrefs.isEmpty()) {
767 for (
const TranslatorMessage::Reference &ref : msg.allReferences())
768 refs.append(QString(QLatin1String(
"%2:%1"))
769 .arg(ref.lineNumber()).arg(ref.fileName()));
770 if (!xrefs.isEmpty())
772 out << poWrappedEscapedLines(QLatin1String(
"#:"),
true, refs.join(QLatin1Char(
' ')));
776 bool skipFormat =
false;
778 if ((msg.type() == TranslatorMessage::Unfinished
779 || msg.type() == TranslatorMessage::Obsolete) && msg.isTranslated())
780 flags.append(QLatin1String(
"fuzzy"));
781 const auto itr = msg.extras().constFind(QLatin1String(
"po-flags"));
782 if (itr != msg.extras().cend()) {
783 const QStringList atoms = itr->split(QLatin1String(
", "));
784 for (
const QString &atom : atoms)
785 if (atom.endsWith(str_format)) {
789 if (atoms.contains(QLatin1String(
"no-wrap")))
794 QString source = msg.sourceText();
797 for (
int off = 0; (off = source.indexOf(QLatin1Char(
'%'), off)) >= 0; ) {
798 if (++off >= source.size())
800 if (source.at(off) == QLatin1Char(
'n') || source.at(off).isDigit()) {
801 flags.append(QLatin1String(
"qt-format"));
806 if (!flags.isEmpty())
807 out <<
"#, " << flags.join(QLatin1String(
", ")) <<
'\n';
809 bool isObsolete = (msg.type() == TranslatorMessage::Obsolete
810 || msg.type() == TranslatorMessage::Vanished);
811 QString prefix = QLatin1String(isObsolete ?
"#~| " :
"#| ");
812 if (!msg.oldComment().isEmpty())
813 out << poEscapedString(prefix, QLatin1String(
"msgctxt"), noWrap,
814 escapeComment(msg.oldComment(), qtContexts));
815 if (!msg.oldSourceText().isEmpty())
816 out << poEscapedString(prefix, QLatin1String(
"msgid"), noWrap, msg.oldSourceText());
817 QString plural = msg.extra(QLatin1String(
"po-old_msgid_plural"));
818 if (!plural.isEmpty())
819 out << poEscapedString(prefix, QLatin1String(
"msgid_plural"), noWrap, plural);
820 prefix = QLatin1String(isObsolete ?
"#~ " :
"");
821 if (!msg.context().isEmpty())
822 out << poEscapedString(prefix, QLatin1String(
"msgctxt"), noWrap,
823 escapeComment(msg.context(),
true) + QLatin1Char(
'|')
824 + escapeComment(msg.comment(),
true));
825 else if (!msg.comment().isEmpty())
826 out << poEscapedString(prefix, QLatin1String(
"msgctxt"), noWrap,
827 escapeComment(msg.comment(), qtContexts));
828 out << poEscapedString(prefix, QLatin1String(
"msgid"), noWrap, msg.sourceText());
829 if (!msg.isPlural()) {
830 QString transl = msg.translation();
831 transl.replace(QChar(Translator::BinaryVariantSeparator),
832 QChar(Translator::TextVariantSeparator));
833 out << poEscapedString(prefix, QLatin1String(
"msgstr"), noWrap, transl);
835 QString plural = msg.extra(QLatin1String(
"po-msgid_plural"));
836 if (plural.isEmpty())
837 plural = msg.sourceText();
838 out << poEscapedString(prefix, QLatin1String(
"msgid_plural"), noWrap, plural);
839 const QStringList &translations = msg.translations();
840 for (
int i = 0; i != translations.size(); ++i) {
841 QString str = translations.at(i);
842 str.replace(QChar(Translator::BinaryVariantSeparator),
843 QChar(Translator::TextVariantSeparator));
844 out << poEscapedString(prefix, QString::fromLatin1(
"msgstr[%1]").arg(i), noWrap,