Changeset 846 for trunk/tools/linguist/shared/po.cpp
- Timestamp:
- May 5, 2011, 5:36:53 AM (14 years ago)
- Location:
- trunk
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk
- Property svn:mergeinfo changed
/branches/vendor/nokia/qt/4.7.2 (added) merged: 845 /branches/vendor/nokia/qt/current merged: 844 /branches/vendor/nokia/qt/4.6.3 removed
- Property svn:mergeinfo changed
-
trunk/tools/linguist/shared/po.cpp
r651 r846 1 1 /**************************************************************************** 2 2 ** 3 ** Copyright (C) 201 0Nokia Corporation and/or its subsidiary(-ies).3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). 4 4 ** All rights reserved. 5 5 ** Contact: Nokia Corporation (qt-info@nokia.com) … … 46 46 #include <QtCore/QHash> 47 47 #include <QtCore/QString> 48 #include <QtCore/QTextCodec> 48 49 #include <QtCore/QTextStream> 49 50 50 51 #include <ctype.h> 51 52 #define MAGIC_OBSOLETE_REFERENCE "Obsolete_PO_entries"53 52 54 53 // Uncomment if you wish to hard wrap long lines in .po files. Note that this … … 202 201 203 202 public: 204 Q Stringid;205 Q Stringcontext;206 Q Stringtscomment;207 Q StringoldTscomment;208 Q StringlineNumber;209 Q StringfileName;210 Q Stringreferences;211 Q StringtranslatorComments;212 Q StringautomaticComments;213 Q StringmsgId;214 Q StringoldMsgId;215 Q StringListmsgStr;203 QByteArray id; 204 QByteArray context; 205 QByteArray tscomment; 206 QByteArray oldTscomment; 207 QByteArray lineNumber; 208 QByteArray fileName; 209 QByteArray references; 210 QByteArray translatorComments; 211 QByteArray automaticComments; 212 QByteArray msgId; 213 QByteArray oldMsgId; 214 QList<QByteArray> msgStr; 216 215 bool isPlural; 217 216 bool isFuzzy; … … 220 219 221 220 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; 221 static bool isTranslationLine(const QByteArray &line) 222 { 223 return line.startsWith("#~ msgstr") || line.startsWith("msgstr"); 224 } 225 226 static QByteArray slurpEscapedString(const QList<QByteArray> &lines, int &l, 227 int offset, const QByteArray &prefix, ConversionData &cd) 228 { 229 QByteArray msg; 232 230 int stoff; 233 231 234 232 for (; l < lines.size(); ++l) { 235 const Q String&line = lines.at(l);233 const QByteArray &line = lines.at(l); 236 234 if (line.isEmpty() || !line.startsWith(prefix)) 237 235 break; 238 while ( line[offset].isSpace()) // No length check, as string has no trailing spaces.236 while (isspace(line[offset])) // No length check, as string has no trailing spaces. 239 237 offset++; 240 if (line[offset] .unicode()!= '"')238 if (line[offset] != '"') 241 239 break; 242 240 offset++; … … 244 242 if (offset == line.length()) 245 243 goto premature_eol; 246 u short c = line[offset++].unicode();244 uchar c = line[offset++]; 247 245 if (c == '"') { 248 246 if (offset == line.length()) 249 247 break; 250 while ( line[offset].isSpace())248 while (isspace(line[offset])) 251 249 offset++; 252 if (line[offset++] .unicode()!= '"') {250 if (line[offset++] != '"') { 253 251 cd.appendError(QString::fromLatin1( 254 252 "PO parsing error: extra characters on line %1.") … … 261 259 if (offset == line.length()) 262 260 goto premature_eol; 263 c = line[offset++] .unicode();261 c = line[offset++]; 264 262 switch (c) { 265 263 case 'r': 266 msg += QLatin1Char('\r'); // Maybe just throw it away?264 msg += '\r'; // Maybe just throw it away? 267 265 break; 268 266 case 'n': 269 msg += QLatin1Char('\n');267 msg += '\n'; 270 268 break; 271 269 case 't': 272 msg += QLatin1Char('\t');270 msg += '\t'; 273 271 break; 274 272 case 'v': 275 msg += QLatin1Char('\v');273 msg += '\v'; 276 274 break; 277 275 case 'a': 278 msg += QLatin1Char('\a');276 msg += '\a'; 279 277 break; 280 278 case 'b': 281 msg += QLatin1Char('\b');279 msg += '\b'; 282 280 break; 283 281 case 'f': 284 msg += QLatin1Char('\f');282 msg += '\f'; 285 283 break; 286 284 case '"': 287 msg += QLatin1Char('"');285 msg += '"'; 288 286 break; 289 287 case '\\': 290 msg += QLatin1Char('\\');288 msg += '\\'; 291 289 break; 292 290 case '0': … … 299 297 case '7': 300 298 stoff = offset - 1; 301 while ((c = line[offset] .unicode()) >= '0' && c <= '7')299 while ((c = line[offset]) >= '0' && c <= '7') 302 300 if (++offset == line.length()) 303 301 goto premature_eol; 304 msg += QChar(line.mid(stoff, offset - stoff).toUInt(0, 8));302 msg += line.mid(stoff, offset - stoff).toUInt(0, 8); 305 303 break; 306 304 case 'x': 307 305 stoff = offset; 308 while (isxdigit(line[offset] .unicode()))306 while (isxdigit(line[offset])) 309 307 if (++offset == line.length()) 310 308 goto premature_eol; 311 msg += QChar(line.mid(stoff, offset - stoff).toUInt(0, 16));309 msg += line.mid(stoff, offset - stoff).toUInt(0, 16); 312 310 break; 313 311 default: 314 312 cd.appendError(QString::fromLatin1( 315 313 "PO parsing error: invalid escape '\\%1' (line %2).") 316 .arg(QChar( c)).arg(l + 1));317 msg += QLatin1Char('\\');318 msg += QChar(c);314 .arg(QChar((uint)c)).arg(l + 1)); 315 msg += '\\'; 316 msg += c; 319 317 break; 320 318 } 321 319 } else { 322 msg += QChar(c);320 msg += c; 323 321 } 324 322 } … … 331 329 cd.appendError(QString::fromLatin1( 332 330 "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); 331 return QByteArray(); 332 } 333 334 static void slurpComment(QByteArray &msg, const QList<QByteArray> &lines, int & l) 335 { 336 QByteArray prefix = lines.at(l); 340 337 for (int i = 1; ; i++) { 341 if (prefix.at(i) .unicode()!= ' ') {338 if (prefix.at(i) != ' ') { 342 339 prefix.truncate(i); 343 340 break; … … 345 342 } 346 343 for (; l < lines.size(); ++l) { 347 const Q String&line = lines.at(l);344 const QByteArray &line = lines.at(l); 348 345 if (line.startsWith(prefix)) 349 346 msg += line.mid(prefix.size()); 350 else if (line != QLatin1String("#"))351 break; 352 msg += newline;347 else if (line != "#") 348 break; 349 msg += '\n'; 353 350 } 354 351 --l; 355 352 } 356 353 354 static void splitContext(QByteArray *comment, QByteArray *context) 355 { 356 char *data = comment->data(); 357 int len = comment->size(); 358 int sep = -1, j = 0; 359 360 for (int i = 0; i < len; i++, j++) { 361 if (data[i] == '~' && i + 1 < len) 362 i++; 363 else if (data[i] == '|') 364 sep = j; 365 data[j] = data[i]; 366 } 367 if (sep >= 0) { 368 QByteArray tmp = comment->mid(sep + 1, j - sep - 1); 369 comment->truncate(sep); 370 *context = *comment; 371 *comment = tmp; 372 } else { 373 comment->truncate(j); 374 } 375 } 376 377 static QString makePoHeader(const QString &str) 378 { 379 return QLatin1String("po-header-") + str.toLower().replace(QLatin1Char('-'), QLatin1Char('_')); 380 } 381 382 static QByteArray QByteArrayList_join(const QList<QByteArray> &that, char sep) 383 { 384 int totalLength = 0; 385 const int size = that.size(); 386 387 for (int i = 0; i < size; ++i) 388 totalLength += that.at(i).size(); 389 390 if (size > 0) 391 totalLength += size - 1; 392 393 QByteArray res; 394 if (totalLength == 0) 395 return res; 396 res.reserve(totalLength); 397 for (int i = 0; i < that.size(); ++i) { 398 if (i) 399 res += sep; 400 res += that.at(i); 401 } 402 return res; 403 } 404 357 405 bool loadPO(Translator &translator, QIODevice &dev, ConversionData &cd) 358 406 { 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); 407 QTextCodec *codec = QTextCodec::codecForName( 408 cd.m_codecForSource.isEmpty() ? QByteArray("UTF-8") : cd.m_codecForSource); 363 409 bool error = false; 364 410 … … 381 427 382 428 // 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; 429 QList<QByteArray> lines; 430 while (!dev.atEnd()) 431 lines.append(dev.readLine().trimmed()); 432 lines.append(QByteArray()); 433 434 int l = 0, lastCmtLine = -1; 435 bool qtContexts = false; 389 436 PoItem item; 390 437 for (; l != lines.size(); ++l) { 391 Q Stringline = lines.at(l);438 QByteArray line = lines.at(l); 392 439 if (line.isEmpty()) 393 440 continue; 394 441 if (isTranslationLine(line)) { 395 bool isObsolete = line.startsWith( QLatin1String("#~ msgstr"));396 const Q String prefix = QLatin1String(isObsolete ? "#~ " : "");442 bool isObsolete = line.startsWith("#~ msgstr"); 443 const QByteArray prefix = isObsolete ? "#~ " : ""; 397 444 while (true) { 398 int idx = line.indexOf(QLatin1Char(' '), prefix.length()); 399 QString str = slurpEscapedString(lines, l, idx, prefix, cd); 445 int idx = line.indexOf(' ', prefix.length()); 446 QByteArray str = slurpEscapedString(lines, l, idx, prefix, cd); 447 item.msgStr.append(str); 448 if (l + 1 >= lines.size() || !isTranslationLine(lines.at(l + 1))) 449 break; 450 ++l; 451 line = lines.at(l); 452 } 453 if (item.msgId.isEmpty()) { 454 QHash<QString, QByteArray> extras; 455 QList<QByteArray> hdrOrder; 456 QByteArray pluralForms; 457 foreach (const QByteArray &hdr, item.msgStr.first().split('\n')) { 458 if (hdr.isEmpty()) 459 continue; 460 int idx = hdr.indexOf(':'); 461 if (idx < 0) { 462 cd.appendError(QString::fromLatin1("Unexpected PO header format '%1'") 463 .arg(QString::fromLatin1(hdr))); 464 error = true; 465 break; 466 } 467 QByteArray hdrName = hdr.left(idx).trimmed(); 468 QByteArray hdrValue = hdr.mid(idx + 1).trimmed(); 469 hdrOrder << hdrName; 470 if (hdrName == "X-Language") { 471 translator.setLanguageCode(QString::fromLatin1(hdrValue)); 472 } else if (hdrName == "X-Source-Language") { 473 translator.setSourceLanguageCode(QString::fromLatin1(hdrValue)); 474 } else if (hdrName == "X-Qt-Contexts") { 475 qtContexts = (hdrValue == "true"); 476 } else if (hdrName == "Plural-Forms") { 477 pluralForms = hdrValue; 478 } else if (hdrName == "MIME-Version") { 479 // just assume it is 1.0 480 } else if (hdrName == "Content-Type") { 481 if (cd.m_codecForSource.isEmpty()) { 482 if (!hdrValue.startsWith("text/plain; charset=")) { 483 cd.appendError(QString::fromLatin1("Unexpected Content-Type header '%1'") 484 .arg(QString::fromLatin1(hdrValue))); 485 error = true; 486 // This will avoid a flood of conversion errors. 487 codec = QTextCodec::codecForName("latin1"); 488 } else { 489 QByteArray cod = hdrValue.mid(20); 490 QTextCodec *cdc = QTextCodec::codecForName(cod); 491 if (!cdc) { 492 cd.appendError(QString::fromLatin1("Unsupported codec '%1'") 493 .arg(QString::fromLatin1(cod))); 494 error = true; 495 // This will avoid a flood of conversion errors. 496 codec = QTextCodec::codecForName("latin1"); 497 } else { 498 codec = cdc; 499 } 500 } 501 } 502 } else if (hdrName == "Content-Transfer-Encoding") { 503 if (hdrValue != "8bit") { 504 cd.appendError(QString::fromLatin1("Unexpected Content-Transfer-Encoding '%1'") 505 .arg(QString::fromLatin1(hdrValue))); 506 return false; 507 } 508 } else if (hdrName == "X-Virgin-Header") { 509 // legacy 510 } else { 511 extras[makePoHeader(QString::fromLatin1(hdrName))] = hdrValue; 512 } 513 } 514 if (!pluralForms.isEmpty()) { 515 if (translator.languageCode().isEmpty()) { 516 extras[makePoHeader(QLatin1String("Plural-Forms"))] = pluralForms; 517 } else { 518 // FIXME: have fun with making a consistency check ... 519 } 520 } 521 // Eliminate the field if only headers we added are present in standard order. 522 // Keep in sync with savePO 523 static const char * const dfltHdrs[] = { 524 "MIME-Version", "Content-Type", "Content-Transfer-Encoding", 525 "Plural-Forms", "X-Language", "X-Source-Language", "X-Qt-Contexts" 526 }; 527 uint cdh = 0; 528 for (int cho = 0; cho < hdrOrder.length(); cho++) { 529 for (;; cdh++) { 530 if (cdh == sizeof(dfltHdrs)/sizeof(dfltHdrs[0])) { 531 extras[QLatin1String("po-headers")] = 532 QByteArrayList_join(hdrOrder, ','); 533 goto doneho; 534 } 535 if (hdrOrder.at(cho) == dfltHdrs[cdh]) { 536 cdh++; 537 break; 538 } 539 } 540 } 541 doneho: 542 if (lastCmtLine != -1) 543 extras[QLatin1String("po-header_comment")] = 544 QByteArrayList_join(lines.mid(0, lastCmtLine + 1), '\n'); 545 for (QHash<QString, QByteArray>::ConstIterator it = extras.constBegin(), 546 end = extras.constEnd(); 547 it != end; ++it) 548 translator.setExtra(it.key(), codec->toUnicode(it.value())); 549 item = PoItem(); 550 continue; 551 } 552 // build translator message 553 TranslatorMessage msg; 554 msg.setContext(codec->toUnicode(item.context)); 555 if (!item.references.isEmpty()) { 556 QString xrefs; 557 foreach (const QString &ref, 558 codec->toUnicode(item.references).split( 559 QRegExp(QLatin1String("\\s")), QString::SkipEmptyParts)) { 560 int pos = ref.indexOf(QLatin1Char(':')); 561 int lpos = ref.lastIndexOf(QLatin1Char(':')); 562 if (pos != -1 && pos == lpos) { 563 bool ok; 564 int lno = ref.mid(pos + 1).toInt(&ok); 565 if (ok) { 566 msg.addReference(ref.left(pos), lno); 567 continue; 568 } 569 } 570 if (!xrefs.isEmpty()) 571 xrefs += QLatin1Char(' '); 572 xrefs += ref; 573 } 574 if (!xrefs.isEmpty()) 575 item.extra[QLatin1String("po-references")] = xrefs; 576 } 577 msg.setId(codec->toUnicode(item.id)); 578 msg.setSourceText(codec->toUnicode(item.msgId)); 579 msg.setOldSourceText(codec->toUnicode(item.oldMsgId)); 580 msg.setComment(codec->toUnicode(item.tscomment)); 581 msg.setOldComment(codec->toUnicode(item.oldTscomment)); 582 msg.setExtraComment(codec->toUnicode(item.automaticComments)); 583 msg.setTranslatorComment(codec->toUnicode(item.translatorComments)); 584 msg.setPlural(item.isPlural || item.msgStr.size() > 1); 585 QStringList translations; 586 foreach (const QByteArray &bstr, item.msgStr) { 587 QString str = codec->toUnicode(bstr); 400 588 str.replace(QChar(Translator::TextVariantSeparator), 401 589 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); 590 translations << str; 591 } 592 msg.setTranslations(translations); 450 593 if (isObsolete) 451 594 msg.setType(TranslatorMessage::Obsolete); 452 else if (item.isFuzzy )595 else if (item.isFuzzy || (!msg.sourceText().isEmpty() && !msg.isTranslated())) 453 596 msg.setType(TranslatorMessage::Unfinished); 454 597 else … … 461 604 translator.append(msg); 462 605 item = PoItem(); 463 } else if (line.startsWith( QLatin1Char('#'))) {464 switch (line.size() < 2 ? 0 : line.at(1).unicode()) {606 } else if (line.startsWith('#')) { 607 switch (line.size() < 2 ? 0 : line.at(1)) { 465 608 case ':': 466 609 item.references += line.mid(3); 467 item.references += newline;610 item.references += '\n'; 468 611 break; 469 612 case ',': { 470 613 QStringList flags = 471 line.mid(2).split(QRegExp(QLatin1String("[, ]")),472 614 QString::fromLatin1(line.mid(2)).split( 615 QRegExp(QLatin1String("[, ]")), QString::SkipEmptyParts); 473 616 if (flags.removeOne(QLatin1String("fuzzy"))) 474 617 item.isFuzzy = true; 618 flags.removeOne(QLatin1String("qt-format")); 475 619 TranslatorMessage::ExtraData::const_iterator it = 476 620 item.extra.find(QLatin1String("po-flags")); … … 482 626 } 483 627 case 0: 484 item.translatorComments += newline;628 item.translatorComments += '\n'; 485 629 break; 486 630 case ' ': … … 488 632 break; 489 633 case '.': 490 if (line.startsWith( QLatin1String("#. ts-context "))) {634 if (line.startsWith("#. ts-context ")) { // legacy 491 635 item.context = line.mid(14); 492 } else if (line.startsWith( QLatin1String("#. ts-id "))) {636 } else if (line.startsWith("#. ts-id ")) { 493 637 item.id = line.mid(9); 494 638 } else { 495 639 item.automaticComments += line.mid(3); 496 item.automaticComments += newline;640 item.automaticComments += '\n'; 497 641 } 498 642 break; 499 643 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 Q String extra = slurpEscapedString(lines, l, 16, QLatin1String("#| "), cd);644 if (line.startsWith("#| msgid ")) { 645 item.oldMsgId = slurpEscapedString(lines, l, 9, "#| ", cd); 646 } else if (line.startsWith("#| msgid_plural ")) { 647 QByteArray extra = slurpEscapedString(lines, l, 16, "#| ", cd); 504 648 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); 649 item.extra[QLatin1String("po-old_msgid_plural")] = 650 codec->toUnicode(extra); 651 } else if (line.startsWith("#| msgctxt ")) { 652 item.oldTscomment = slurpEscapedString(lines, l, 11, "#| ", cd); 653 if (qtContexts) 654 splitContext(&item.oldTscomment, &item.context); 508 655 } else { 509 cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2' \n"))510 .arg(l + 1).arg( lines[l]));656 cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'")) 657 .arg(l + 1).arg(codec->toUnicode(lines[l]))); 511 658 error = true; 512 659 } 513 660 break; 514 661 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 Q String extra = slurpEscapedString(lines, l, 16, QLatin1String("#~ "), cd);662 if (line.startsWith("#~ msgid ")) { 663 item.msgId = slurpEscapedString(lines, l, 9, "#~ ", cd); 664 } else if (line.startsWith("#~ msgid_plural ")) { 665 QByteArray extra = slurpEscapedString(lines, l, 16, "#~ ", cd); 519 666 if (extra != item.msgId) 520 item.extra[QLatin1String("po-msgid_plural")] = extra; 667 item.extra[QLatin1String("po-msgid_plural")] = 668 codec->toUnicode(extra); 521 669 item.isPlural = true; 522 } else if (line.startsWith(QLatin1String("#~ msgctxt "))) { 523 item.tscomment = slurpEscapedString(lines, l, 11, QLatin1String("#~ "), cd); 670 } else if (line.startsWith("#~ msgctxt ")) { 671 item.tscomment = slurpEscapedString(lines, l, 11, "#~ ", cd); 672 if (qtContexts) 673 splitContext(&item.tscomment, &item.context); 524 674 } else { 525 cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2' \n"))526 .arg(l + 1).arg( lines[l]));675 cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'")) 676 .arg(l + 1).arg(codec->toUnicode(lines[l]))); 527 677 error = true; 528 678 } 529 679 break; 530 680 default: 531 cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2' \n"))532 .arg(l + 1).arg( lines[l]));681 cd.appendError(QString(QLatin1String("PO-format parse error in line %1: '%2'")) 682 .arg(l + 1).arg(codec->toUnicode(lines[l]))); 533 683 error = true; 534 684 break; 535 685 } 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); 686 lastCmtLine = l; 687 } else if (line.startsWith("msgctxt ")) { 688 item.tscomment = slurpEscapedString(lines, l, 8, QByteArray(), cd); 689 if (qtContexts) 690 splitContext(&item.tscomment, &item.context); 691 } else if (line.startsWith("msgid ")) { 692 item.msgId = slurpEscapedString(lines, l, 6, QByteArray(), cd); 693 } else if (line.startsWith("msgid_plural ")) { 694 QByteArray extra = slurpEscapedString(lines, l, 13, QByteArray(), cd); 542 695 if (extra != item.msgId) 543 item.extra[QLatin1String("po-msgid_plural")] = extra;696 item.extra[QLatin1String("po-msgid_plural")] = codec->toUnicode(extra); 544 697 item.isPlural = true; 545 698 } else { 546 cd.appendError(QString(QLatin1String("PO-format error in line %1: '%2' \n"))547 .arg(l + 1).arg( lines[l]));699 cd.appendError(QString(QLatin1String("PO-format error in line %1: '%2'")) 700 .arg(l + 1).arg(codec->toUnicode(lines[l]))); 548 701 error = true; 549 702 } … … 552 705 } 553 706 707 static void addPoHeader(Translator::ExtraData &headers, QStringList &hdrOrder, 708 const char *name, const QString &value) 709 { 710 QString qName = QLatin1String(name); 711 if (!hdrOrder.contains(qName)) 712 hdrOrder << qName; 713 headers[makePoHeader(qName)] = value; 714 } 715 716 static QString escapeComment(const QString &in, bool escape) 717 { 718 QString out = in; 719 if (escape) { 720 out.replace(QLatin1Char('~'), QLatin1String("~~")); 721 out.replace(QLatin1Char('|'), QLatin1String("~|")); 722 } 723 return out; 724 } 725 554 726 bool savePO(const Translator &translator, QIODevice &dev, ConversionData &cd) 555 727 { 728 QString str_format = QLatin1String("-format"); 729 556 730 bool ok = true; 557 731 QTextStream out(&dev); 558 732 out.setCodec(cd.m_outputCodec.isEmpty() ? QByteArray("UTF-8") : cd.m_outputCodec); 559 733 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 } 734 bool qtContexts = false; 735 foreach (const TranslatorMessage &msg, translator.messages()) 736 if (!msg.context().isEmpty()) { 737 qtContexts = true; 738 break; 739 } 740 741 QString cmt = translator.extra(QLatin1String("po-header_comment")); 742 if (!cmt.isEmpty()) 743 out << cmt << '\n'; 744 out << "msgid \"\"\n"; 745 Translator::ExtraData headers = translator.extras(); 746 QStringList hdrOrder = translator.extra(QLatin1String("po-headers")).split( 747 QLatin1Char(','), QString::SkipEmptyParts); 748 // Keep in sync with loadPO 749 addPoHeader(headers, hdrOrder, "MIME-Version", QLatin1String("1.0")); 750 addPoHeader(headers, hdrOrder, "Content-Type", 751 QLatin1String("text/plain; charset=" + out.codec()->name())); 752 addPoHeader(headers, hdrOrder, "Content-Transfer-Encoding", QLatin1String("8bit")); 753 if (!translator.languageCode().isEmpty()) { 754 QLocale::Language l; 755 QLocale::Country c; 756 Translator::languageAndCountry(translator.languageCode(), &l, &c); 757 const char *gettextRules; 758 if (getNumerusInfo(l, c, 0, 0, &gettextRules)) 759 addPoHeader(headers, hdrOrder, "Plural-Forms", QLatin1String(gettextRules)); 760 addPoHeader(headers, hdrOrder, "X-Language", translator.languageCode()); 761 } 762 if (!translator.sourceLanguageCode().isEmpty()) 763 addPoHeader(headers, hdrOrder, "X-Source-Language", translator.sourceLanguageCode()); 764 if (qtContexts) 765 addPoHeader(headers, hdrOrder, "X-Qt-Contexts", QLatin1String("true")); 766 QString hdrStr; 767 foreach (const QString &hdr, hdrOrder) { 768 hdrStr += hdr; 769 hdrStr += QLatin1String(": "); 770 hdrStr += headers.value(makePoHeader(hdr)); 771 hdrStr += QLatin1Char('\n'); 772 } 773 out << poEscapedString(QString(), QString::fromLatin1("msgstr"), true, hdrStr); 774 578 775 foreach (const TranslatorMessage &msg, translator.messages()) { 579 if (!first) 580 out << endl; 776 out << endl; 581 777 582 778 if (!msg.translatorComment().isEmpty()) … … 586 782 out << poEscapedLines(QLatin1String("#."), true, msg.extraComment()); 587 783 588 if (!msg.context().isEmpty())589 out << QLatin1String("#. ts-context ") << msg.context() << '\n';590 784 if (!msg.id().isEmpty()) 591 785 out << QLatin1String("#. ts-id ") << msg.id() << '\n'; 592 786 593 if (!msg.fileName().isEmpty() && msg.fileName() != QLatin1String(MAGIC_OBSOLETE_REFERENCE)) { 787 QString xrefs = msg.extra(QLatin1String("po-references")); 788 if (!msg.fileName().isEmpty() || !xrefs.isEmpty()) { 594 789 QStringList refs; 595 790 foreach (const TranslatorMessage::Reference &ref, msg.allReferences()) 596 791 refs.append(QString(QLatin1String("%2:%1")) 597 792 .arg(ref.lineNumber()).arg(ref.fileName())); 793 if (!xrefs.isEmpty()) 794 refs << xrefs; 598 795 out << poWrappedEscapedLines(QLatin1String("#:"), true, refs.join(QLatin1String(" "))); 599 796 } 600 797 601 798 bool noWrap = false; 799 bool skipFormat = false; 602 800 QStringList flags; 603 if (msg.type() == TranslatorMessage::Unfinished )801 if (msg.type() == TranslatorMessage::Unfinished && msg.isTranslated()) 604 802 flags.append(QLatin1String("fuzzy")); 605 803 TranslatorMessage::ExtraData::const_iterator itr = 606 804 msg.extras().find(QLatin1String("po-flags")); 607 805 if (itr != msg.extras().end()) { 608 if (itr->split(QLatin1String(", ")).contains(QLatin1String("no-wrap"))) 806 QStringList atoms = itr->split(QLatin1String(", ")); 807 foreach (const QString &atom, atoms) 808 if (atom.endsWith(str_format)) { 809 skipFormat = true; 810 break; 811 } 812 if (atoms.contains(QLatin1String("no-wrap"))) 609 813 noWrap = true; 610 814 flags.append(*itr); 611 815 } 816 if (!skipFormat) { 817 QString source = msg.sourceText(); 818 // This is fuzzy logic, as we don't know whether the string is 819 // actually used with QString::arg(). 820 for (int off = 0; (off = source.indexOf(QLatin1Char('%'), off)) >= 0; ) { 821 if (++off >= source.length()) 822 break; 823 if (source.at(off) == QLatin1Char('n') || source.at(off).isDigit()) { 824 flags.append(QLatin1String("qt-format")); 825 break; 826 } 827 } 828 } 612 829 if (!flags.isEmpty()) 613 830 out << "#, " << flags.join(QLatin1String(", ")) << '\n'; … … 615 832 QString prefix = QLatin1String("#| "); 616 833 if (!msg.oldComment().isEmpty()) 617 out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap, msg.oldComment()); 834 out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap, 835 escapeComment(msg.oldComment(), qtContexts)); 618 836 if (!msg.oldSourceText().isEmpty()) 619 837 out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.oldSourceText()); … … 622 840 out << poEscapedString(prefix, QLatin1String("msgid_plural"), noWrap, plural); 623 841 prefix = QLatin1String((msg.type() == TranslatorMessage::Obsolete) ? "#~ " : ""); 624 if (!msg.comment().isEmpty()) 625 out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap, msg.comment()); 842 if (!msg.context().isEmpty()) 843 out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap, 844 escapeComment(msg.context(), true) + QLatin1Char('|') 845 + escapeComment(msg.comment(), true)); 846 else if (!msg.comment().isEmpty()) 847 out << poEscapedString(prefix, QLatin1String("msgctxt"), noWrap, 848 escapeComment(msg.comment(), qtContexts)); 626 849 out << poEscapedString(prefix, QLatin1String("msgid"), noWrap, msg.sourceText()); 627 850 if (!msg.isPlural()) { 628 851 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 } 852 transl.replace(QChar(Translator::BinaryVariantSeparator), 853 QChar(Translator::TextVariantSeparator)); 634 854 out << poEscapedString(prefix, QLatin1String("msgstr"), noWrap, transl); 635 855 } else { … … 647 867 } 648 868 } 649 first = false;650 869 } 651 870 return ok; 871 } 872 873 static bool savePOT(const Translator &translator, QIODevice &dev, ConversionData &cd) 874 { 875 Translator ttor = translator; 876 ttor.dropTranslations(); 877 return savePO(ttor, dev, cd); 652 878 } 653 879 … … 662 888 format.priority = 1; 663 889 Translator::registerFileFormat(format); 890 format.extension = QLatin1String("pot"); 891 format.description = QObject::tr("GNU Gettext localization template files"); 892 format.loader = &loadPO; 893 format.saver = &savePOT; 894 format.fileType = Translator::FileFormat::TranslationSource; 895 format.priority = -1; 896 Translator::registerFileFormat(format); 664 897 return 1; 665 898 }
Note:
See TracChangeset
for help on using the changeset viewer.