376 QStringDecoder toUnicode(QStringConverter::Utf8, QStringDecoder::Flag::Stateless);
398 QList<QByteArray> lines;
400 lines.append(dev.readLine().trimmed());
401 lines.append(QByteArray());
403 int l = 0, lastCmtLine = -1;
404 bool qtContexts =
false;
406 for (; l != lines.size(); ++l) {
407 QByteArray line = lines.at(l);
410 if (isTranslationLine(line)) {
411 bool isObsolete = line.startsWith(
"#~ msgstr");
412 const QByteArray prefix = isObsolete ?
"#~ " :
"";
414 int idx = line.indexOf(
' ', prefix.size());
415 QByteArray str = slurpEscapedString(lines, l, idx, prefix, cd);
416 item.msgStr.append(str);
417 if (l + 1 >= lines.size() || !isTranslationLine(lines.at(l + 1)))
422 if (item.msgId.isEmpty()) {
423 QHash<QString, QByteArray> extras;
424 QList<QByteArray> hdrOrder;
425 QByteArray pluralForms;
426 for (
const QByteArray &hdr : item.msgStr.first().split(
'\n')) {
429 int idx = hdr.indexOf(
':');
431 cd.appendError(QString::fromLatin1(
"Unexpected PO header format '%1'")
432 .arg(QString::fromLatin1(hdr)));
436 QByteArray hdrName = hdr.left(idx).trimmed();
437 QByteArray hdrValue = hdr.mid(idx + 1).trimmed();
439 if (hdrName ==
"X-Language") {
440 translator.setLanguageCode(QString::fromLatin1(hdrValue));
441 }
else if (hdrName ==
"X-Source-Language") {
442 translator.setSourceLanguageCode(QString::fromLatin1(hdrValue));
443 }
else if (hdrName ==
"X-Qt-Contexts") {
444 qtContexts = (hdrValue ==
"true");
445 }
else if (hdrName ==
"Plural-Forms") {
446 pluralForms = hdrValue;
447 }
else if (hdrName ==
"MIME-Version") {
449 }
else if (hdrName ==
"Content-Type") {
450 if (!hdrValue.startsWith(
"text/plain; charset=")) {
451 cd.appendError(QString::fromLatin1(
"Unexpected Content-Type header '%1'")
452 .arg(QString::fromLatin1(hdrValue)));
455 toUnicode = QStringDecoder(QStringConverter::Latin1);
457 QByteArray cod = hdrValue.mid(20);
458 auto enc = QStringConverter::encodingForName(cod);
460 cd.appendError(QString::fromLatin1(
"Unsupported encoding '%1'")
461 .arg(QString::fromLatin1(cod)));
464 toUnicode = QStringDecoder(QStringConverter::Latin1);
466 toUnicode = QStringDecoder(*enc);
469 }
else if (hdrName ==
"Content-Transfer-Encoding") {
470 if (hdrValue !=
"8bit") {
471 cd.appendError(QString::fromLatin1(
"Unexpected Content-Transfer-Encoding '%1'")
472 .arg(QString::fromLatin1(hdrValue)));
475 }
else if (hdrName ==
"X-Virgin-Header") {
478 extras[makePoHeader(QString::fromLatin1(hdrName))] = hdrValue;
481 if (!pluralForms.isEmpty()) {
482 if (translator.languageCode().isEmpty()) {
483 extras[makePoHeader(
"Plural-Forms"_L1)] = pluralForms;
490 static const char *
const dfltHdrs[] = {
491 "MIME-Version",
"Content-Type",
"Content-Transfer-Encoding",
492 "Plural-Forms",
"X-Language",
"X-Source-Language",
"X-Qt-Contexts"
495 for (
int cho = 0; cho < hdrOrder.size(); cho++) {
497 if (cdh ==
sizeof(dfltHdrs)/
sizeof(dfltHdrs[0])) {
498 extras[
"po-headers"_L1] = QByteArrayList_join(hdrOrder,
',');
501 if (hdrOrder.at(cho) == dfltHdrs[cdh]) {
508 if (lastCmtLine != -1) {
509 extras[
"po-header_comment"_L1] =
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))
524 .split(QRegularExpression(
"\\s"_L1), Qt::SkipEmptyParts)) {
525 int pos = ref.indexOf(u':');
526 int lpos = ref.lastIndexOf(u':');
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())
539 if (!xrefs.isEmpty())
540 item.extra[
"po-references"_L1] = 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))
582 .split(QRegularExpression(
"[, ]"_L1), Qt::SkipEmptyParts);
583 if (flags.removeOne(
"fuzzy"_L1))
585 flags.removeOne(
"qt-format"_L1);
586 const auto it = item.extra.constFind(
"po-flags"_L1);
587 if (it != item.extra.cend())
589 if (!flags.isEmpty())
590 item.extra[
"po-flags"_L1] = flags.join(
", "_L1);
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[
"po-old_msgid_plural"_L1] = toUnicode(extra);
616 }
else if (line.startsWith(
"#| msgctxt ")) {
617 item.oldTscomment = slurpEscapedString(lines, l, 11,
"#| ", cd);
619 splitContext(&item.oldTscomment, &item.context);
621 cd.appendError(QString(
"PO-format parse error in line %1: '%2'"_L1)
623 .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[
"po-msgid_plural"_L1] = toUnicode(extra);
635 }
else if (line.startsWith(
"#~ msgctxt ")) {
636 item.tscomment = slurpEscapedString(lines, l, 11,
"#~ ", cd);
638 splitContext(&item.tscomment, &item.context);
639 }
else if (line.startsWith(
"#~| msgid ")) {
640 item.oldMsgId = slurpEscapedString(lines, l, 10,
"#~| ", cd);
641 }
else if (line.startsWith(
"#~| msgid_plural ")) {
642 QByteArray extra = slurpEscapedString(lines, l, 17,
"#~| ", cd);
643 if (extra != item.oldMsgId)
644 item.extra[
"po-old_msgid_plural"_L1] = toUnicode(extra);
645 }
else if (line.startsWith(
"#~| msgctxt ")) {
646 item.oldTscomment = slurpEscapedString(lines, l, 12,
"#~| ", cd);
648 splitContext(&item.oldTscomment, &item.context);
650 cd.appendError(QString(
"PO-format parse error in line %1: '%2'"_L1)
652 .arg(toUnicode(lines[l])));
657 cd.appendError(QString(
"PO-format parse error in line %1: '%2'"_L1)
659 .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[
"po-msgid_plural"_L1] = toUnicode(extra);
676 cd.appendError(QString(
"PO-format error in line %1: '%2'"_L1)
678 .arg(toUnicode(lines[l])));
682 return !error && cd.errors().isEmpty();
706 QString str_format =
"-format"_L1;
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(
"po-header_comment"_L1);
721 out <<
"msgid \"\"\n";
723 QStringList hdrOrder = translator.extra(
"po-headers"_L1).split(u',', Qt::SkipEmptyParts);
725 addPoHeader(headers, hdrOrder,
"MIME-Version",
"1.0"_L1);
726 addPoHeader(headers, hdrOrder,
"Content-Type",
"text/plain; charset=UTF-8"_L1);
727 addPoHeader(headers, hdrOrder,
"Content-Transfer-Encoding",
"8bit"_L1);
728 if (!translator.languageCode().isEmpty()) {
730 QLocale::Territory c;
731 Translator::languageAndTerritory(translator.languageCode(), &l, &c);
732 const char *gettextRules;
733 if (getNumerusInfo(l, c, 0, 0, &gettextRules))
734 addPoHeader(headers, hdrOrder,
"Plural-Forms", QLatin1String(gettextRules));
735 addPoHeader(headers, hdrOrder,
"X-Language", translator.languageCode());
737 if (!translator.sourceLanguageCode().isEmpty())
738 addPoHeader(headers, hdrOrder,
"X-Source-Language", translator.sourceLanguageCode());
740 addPoHeader(headers, hdrOrder,
"X-Qt-Contexts",
"true"_L1);
742 for (
const QString &hdr : std::as_const(hdrOrder)) {
745 hdrStr += headers.value(makePoHeader(hdr));
748 out << poEscapedString(QString(), QString::fromLatin1(
"msgstr"),
true, hdrStr);
750 for (
const TranslatorMessage &msg : translator.messages()) {
753 if (!msg.translatorComment().isEmpty())
754 out << poEscapedLines(
"#"_L1,
true, msg.translatorComment());
756 if (!msg.extraComment().isEmpty())
757 out << poEscapedLines(
"#."_L1,
true, msg.extraComment());
759 if (!msg.id().isEmpty())
760 out <<
"#. ts-id "_L1 << msg.id() <<
'\n';
762 QString xrefs = msg.extra(
"po-references"_L1);
763 if (!msg.fileName().isEmpty() || !xrefs.isEmpty()) {
765 for (
const TranslatorMessage::Reference &ref : msg.allReferences())
766 refs.append(QString(
"%2:%1"_L1).arg(ref.lineNumber()).arg(ref.fileName()));
767 if (!xrefs.isEmpty())
769 out << poWrappedEscapedLines(
"#:"_L1,
true, refs.join(u' '));
773 bool skipFormat =
false;
775 if ((msg.type() == TranslatorMessage::Unfinished
776 || msg.type() == TranslatorMessage::Obsolete) && msg.isTranslated())
777 flags.append(
"fuzzy"_L1);
778 const auto itr = msg.extras().constFind(
"po-flags"_L1);
779 if (itr != msg.extras().cend()) {
780 const QStringList atoms = itr->split(
", "_L1);
781 for (
const QString &atom : atoms)
782 if (atom.endsWith(str_format)) {
786 if (atoms.contains(
"no-wrap"_L1))
791 QString source = msg.sourceText();
794 for (
int off = 0; (off = source.indexOf(u'%', off)) >= 0;) {
795 if (++off >= source.size())
797 if (source.at(off) == u'n' || source.at(off).isDigit()) {
798 flags.append(
"qt-format"_L1);
803 if (!flags.isEmpty())
804 out <<
"#, " << flags.join(
", "_L1) <<
'\n';
806 bool isObsolete = (msg.type() == TranslatorMessage::Obsolete
807 || msg.type() == TranslatorMessage::Vanished);
808 QString prefix = QLatin1String(isObsolete ?
"#~| " :
"#| ");
809 if (!msg.oldComment().isEmpty())
810 out << poEscapedString(prefix,
"msgctxt"_L1, noWrap,
811 escapeComment(msg.oldComment(), qtContexts));
812 if (!msg.oldSourceText().isEmpty())
813 out << poEscapedString(prefix,
"msgid"_L1, noWrap, msg.oldSourceText());
814 QString plural = msg.extra(
"po-old_msgid_plural"_L1);
815 if (!plural.isEmpty())
816 out << poEscapedString(prefix,
"msgid_plural"_L1, noWrap, plural);
817 prefix = QLatin1String(isObsolete ?
"#~ " :
"");
818 if (!msg.context().isEmpty())
819 out << poEscapedString(prefix,
"msgctxt"_L1, noWrap,
820 escapeComment(msg.context(),
true) + u'|'
821 + escapeComment(msg.comment(),
true));
822 else if (!msg.comment().isEmpty())
823 out << poEscapedString(prefix,
"msgctxt"_L1, noWrap,
824 escapeComment(msg.comment(), qtContexts));
825 out << poEscapedString(prefix,
"msgid"_L1, noWrap, msg.sourceText());
826 if (!msg.isPlural()) {
827 QString transl = msg.translation();
828 transl.replace(Translator::BinaryVariantSeparator, Translator::TextVariantSeparator);
829 out << poEscapedString(prefix,
"msgstr"_L1, noWrap, transl);
831 QString plural = msg.extra(
"po-msgid_plural"_L1);
832 if (plural.isEmpty())
833 plural = msg.sourceText();
834 out << poEscapedString(prefix,
"msgid_plural"_L1, noWrap, plural);
835 const QStringList &translations = msg.translations();
836 for (
int i = 0; i != translations.size(); ++i) {
837 QString str = translations.at(i);
838 str.replace(QChar(Translator::BinaryVariantSeparator),
839 QChar(Translator::TextVariantSeparator));
840 out << poEscapedString(prefix, QString::fromLatin1(
"msgstr[%1]").arg(i), noWrap,