diff options
5 files changed, 456 insertions, 2 deletions
diff --git a/src/declarative/qml/parser/qdeclarativejslexer.cpp b/src/declarative/qml/parser/qdeclarativejslexer.cpp index cd08658..9024d50 100644 --- a/src/declarative/qml/parser/qdeclarativejslexer.cpp +++ b/src/declarative/qml/parser/qdeclarativejslexer.cpp @@ -696,7 +696,7 @@ int Lexer::lex() } else if (current == '*' && next1 == '/') { state = Start; shift(1); - if (driver) driver->addComment(startpos, tokenLength(), startlineno, startcolumn); + if (driver) driver->addComment(startpos, tokenLength()+1, startlineno, startcolumn); } break; diff --git a/tests/auto/linguist/lupdate/testdata/good/parseqml/main.qml b/tests/auto/linguist/lupdate/testdata/good/parseqml/main.qml new file mode 100644 index 0000000..172bd65 --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/parseqml/main.qml @@ -0,0 +1,97 @@ +import Qt 4.7 + +QtObject { + function translate() { + qsTr("One"); + qsTranslate("FooContext", "Two"); + + var greeting_strings = [ + QT_TR_NOOP("Hello"), + QT_TRANSLATE_NOOP("FooContext", "Goodbye") + ]; + + qsTr("One", "not the same one"); + + //: My first comment. + qsTr("See comment"); + + //: My second comment. + qsTranslate("BarContext", "See other comment"); + + //: My third comment + //: spans two lines. + qsTr("The comment explains it all"); + + //: My fourth comment + //: spans a whopping + //: three lines. + qsTranslate("BazContext", "It should be clear by now"); + + /*: C-style comment. */ + qsTr("I love C++"); + + /*: Another C-style comment. */ + qsTranslate("FooContext", "I really love C++"); + + /*: C-style comment, followed by */ + /*: another one. */ + qsTr("Qt is the best"); + + /*: Another C-style comment, followed by */ + /*: yet another one. */ + qsTranslate("BarContext", "Qt is the very best"); + + // This comment doesn't have any effect. + qsTr("The comment had no effect"); + + // This comment doesn't have any effect either. + qsTranslate("BazContext", "The comment had no effect, really"); + + /* This C-style comment doesn't have any effect. */ + qsTr("No comment to your comment"); + + /* This C-style comment doesn't have any effect either. */ + qsTranslate("FooContext", "I refuse to comment on that"); + + //= id_foo + qsTr("This string has an identifier"); + + //= id_bar + qsTranslate("BarContext", "This string also has an identifier"); + + //~ loc-blank False + qsTr("This string has meta-data"); + + //~ loc-layout_id foo_dialog + qsTranslate("BazContext", "This string also has meta-data"); + + // This comment is to be ignored. + //: This is a comment for the translator. + //= id_baz + //~ foo 123 + //~ magic-stuff This means something special. + qsTr("This string has a lot of information"); + + // This comment is also to be ignored. + //: This is another comment for the translator. + //= id_babar + //~ foo-bar Important stuff + //~ needle-in-haystack Found + //~ overflow True + qsTranslate("FooContext", "This string has even more information"); + + qsTr("This string has disambiguation", "Disambiguation"); + + qsTranslate("BarContext", "This string also has disambiguation", "Another disambiguation"); + + qsTr("This string contains plurals", "", 10); + + qsTrId("qtn_foo_bar"); + + var more_greeting_strings = [ QT_TRID_NOOP("qtn_needle"), QT_TRID_NOOP("qtn_haystack") ]; + + //: qsTrId() with comment, meta-data and plurals. + //~ well-tested True + qsTrId("qtn_bar_baz", 10); + } +} diff --git a/tests/auto/linguist/lupdate/testdata/good/parseqml/project.pro b/tests/auto/linguist/lupdate/testdata/good/parseqml/project.pro new file mode 100644 index 0000000..1040e22 --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/parseqml/project.pro @@ -0,0 +1,3 @@ +SOURCES += main.qml + +TRANSLATIONS = project.ts diff --git a/tests/auto/linguist/lupdate/testdata/good/parseqml/project.ts.result b/tests/auto/linguist/lupdate/testdata/good/parseqml/project.ts.result new file mode 100644 index 0000000..7dac8cb --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/parseqml/project.ts.result @@ -0,0 +1,195 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.0"> +<context> + <name></name> + <message id="qtn_foo_bar"> + <location filename="main.qml" line="89"/> + <source></source> + <translation type="unfinished"></translation> + </message> + <message id="qtn_needle"> + <location filename="main.qml" line="91"/> + <source></source> + <translation type="unfinished"></translation> + </message> + <message id="qtn_haystack"> + <location filename="main.qml" line="91"/> + <source></source> + <translation type="unfinished"></translation> + </message> + <message id="qtn_bar_baz" numerus="yes"> + <location filename="main.qml" line="95"/> + <source></source> + <extracomment>qsTrId() with comment, meta-data and plurals.</extracomment> + <translation type="unfinished"> + <numerusform></numerusform> + </translation> + <extra-well-tested>True</extra-well-tested> + </message> +</context> +<context> + <name>BarContext</name> + <message> + <location filename="main.qml" line="19"/> + <source>See other comment</source> + <extracomment>My second comment.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="42"/> + <source>Qt is the very best</source> + <extracomment>Another C-style comment, followed by yet another one.</extracomment> + <translation type="unfinished"></translation> + </message> + <message id="id_bar"> + <location filename="main.qml" line="60"/> + <source>This string also has an identifier</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="85"/> + <source>This string also has disambiguation</source> + <comment>Another disambiguation</comment> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>BazContext</name> + <message> + <location filename="main.qml" line="28"/> + <source>It should be clear by now</source> + <extracomment>My fourth comment spans a whopping three lines.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="48"/> + <source>The comment had no effect, really</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="66"/> + <source>This string also has meta-data</source> + <translation type="unfinished"></translation> + <extra-loc-layout_id>foo_dialog</extra-loc-layout_id> + </message> +</context> +<context> + <name>FooContext</name> + <message> + <location filename="main.qml" line="6"/> + <source>Two</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="10"/> + <source>Goodbye</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="34"/> + <source>I really love C++</source> + <extracomment>Another C-style comment.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="54"/> + <source>I refuse to comment on that</source> + <translation type="unfinished"></translation> + </message> + <message id="id_babar"> + <location filename="main.qml" line="81"/> + <source>This string has even more information</source> + <extracomment>This is another comment for the translator.</extracomment> + <translation type="unfinished"></translation> + <extra-needle-in-haystack>Found</extra-needle-in-haystack> + <extra-overflow>True</extra-overflow> + <extra-foo-bar>Important stuff</extra-foo-bar> + </message> +</context> +<context> + <name>main</name> + <message> + <location filename="main.qml" line="5"/> + <source>One</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="9"/> + <source>Hello</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="13"/> + <source>One</source> + <comment>not the same one</comment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="16"/> + <source>See comment</source> + <extracomment>My first comment.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="23"/> + <source>The comment explains it all</source> + <extracomment>My third comment spans two lines.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="31"/> + <source>I love C++</source> + <extracomment>C-style comment.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="38"/> + <source>Qt is the best</source> + <extracomment>C-style comment, followed by another one.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="45"/> + <source>The comment had no effect</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="51"/> + <source>No comment to your comment</source> + <translation type="unfinished"></translation> + </message> + <message id="id_foo"> + <location filename="main.qml" line="57"/> + <source>This string has an identifier</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="63"/> + <source>This string has meta-data</source> + <translation type="unfinished"></translation> + <extra-loc-blank>False</extra-loc-blank> + </message> + <message id="id_baz"> + <location filename="main.qml" line="73"/> + <source>This string has a lot of information</source> + <extracomment>This is a comment for the translator.</extracomment> + <translation type="unfinished"></translation> + <extra-foo>123</extra-foo> + <extra-magic-stuff>This means something special.</extra-magic-stuff> + </message> + <message> + <location filename="main.qml" line="83"/> + <source>This string has disambiguation</source> + <comment>Disambiguation</comment> + <translation type="unfinished"></translation> + </message> + <message numerus="yes"> + <location filename="main.qml" line="87"/> + <source>This string contains plurals</source> + <translation type="unfinished"> + <numerusform></numerusform> + </translation> + </message> +</context> +</TS> diff --git a/tools/linguist/lupdate/qdeclarative.cpp b/tools/linguist/lupdate/qdeclarative.cpp index 2377416..8b19140 100644 --- a/tools/linguist/lupdate/qdeclarative.cpp +++ b/tools/linguist/lupdate/qdeclarative.cpp @@ -67,6 +67,20 @@ QT_BEGIN_NAMESPACE using namespace QDeclarativeJS; +class Comment +{ +public: + Comment() : lastLine(-1) {} + QString extracomment; + QString msgid; + TranslatorMessage::ExtraData extra; + QString sourcetext; + int lastLine; + + bool isValid() const + { return !extracomment.isEmpty() || !msgid.isEmpty() || !sourcetext.isEmpty() || !extra.isEmpty(); } +}; + class FindTrCalls: protected AST::Visitor { public: @@ -78,6 +92,8 @@ public: accept(node); } + QList<Comment> comments; + protected: using AST::Visitor::visit; using AST::Visitor::endVisit; @@ -114,10 +130,23 @@ protected: plural = true; } + QString id; + QString extracomment; + TranslatorMessage::ExtraData extra; + Comment scomment = findComment(node->firstSourceLocation().startLine); + if (scomment.isValid()) { + extracomment = scomment.extracomment; + extra = scomment.extra; + id = scomment.msgid; + } + TranslatorMessage msg(m_component, source, comment, QString(), m_fileName, node->firstSourceLocation().startLine, QStringList(), TranslatorMessage::Unfinished, plural); + msg.setExtraComment(extracomment.simplified()); + msg.setId(id); + msg.setExtras(extra); m_translator->extend(msg); } } else if (idExpr->name->asString() == QLatin1String("qsTranslate") || @@ -140,6 +169,17 @@ protected: } if (!literal && m_bSource.isEmpty()) return; + + QString id; + QString extracomment; + TranslatorMessage::ExtraData extra; + Comment scomment = findComment(node->firstSourceLocation().startLine); + if (scomment.isValid()) { + extracomment = scomment.extracomment; + extra = scomment.extra; + id = scomment.msgid; + } + source = literal ? literal->value->asString() : m_bSource; AST::ArgumentList *commentNode = sourceNode->next; if (commentNode && AST::cast<AST::StringLiteral *>(commentNode->expression)) { @@ -155,15 +195,48 @@ protected: comment, QString(), m_fileName, node->firstSourceLocation().startLine, QStringList(), TranslatorMessage::Unfinished, plural); + msg.setExtraComment(extracomment.simplified()); + msg.setId(id); + msg.setExtras(extra); m_translator->extend(msg); } + } else if (idExpr->name->asString() == QLatin1String("qsTrId") || + idExpr->name->asString() == QLatin1String("QT_TRID_NOOP")) { + if (!node->arguments) + return; + AST::StringLiteral *literal = AST::cast<AST::StringLiteral *>(node->arguments->expression); + if (literal) { + + QString extracomment; + QString sourcetext; + TranslatorMessage::ExtraData extra; + Comment comment = findComment(node->firstSourceLocation().startLine); + if (comment.isValid()) { + extracomment = comment.extracomment; + sourcetext = comment.sourcetext; + extra = comment.extra; + } + + const QString id = literal->value->asString(); + bool plural = node->arguments->next; + + TranslatorMessage msg(QString(), QString(), + QString(), QString(), m_fileName, + node->firstSourceLocation().startLine, QStringList(), + TranslatorMessage::Unfinished, plural); + msg.setExtraComment(extracomment.simplified()); + msg.setId(id); + msg.setExtras(extra); + m_translator->extend(msg); + } } } } private: - bool createString(AST::BinaryExpression *b) { + bool createString(AST::BinaryExpression *b) + { if (!b || b->op != 0) return false; AST::BinaryExpression *l = AST::cast<AST::BinaryExpression *>(b->left); @@ -187,6 +260,23 @@ private: return true; } + Comment findComment(int loc) + { + if (comments.isEmpty()) + return Comment(); + + int i = 0; + int commentLoc = comments.at(i).lastLine; + while (commentLoc <= loc) { + if (commentLoc == loc) + return comments.at(i); + if (i == comments.count()-1) + break; + commentLoc = comments.at(++i).lastLine; + } + return Comment(); + } + Translator *m_translator; QString m_fileName; QString m_component; @@ -236,6 +326,54 @@ QString createErrorString(const QString &filename, const QString &code, Parser & return errorString; } +bool processComment(const QChar *chars, int length, Comment &comment) +{ + // Try to match the logic of the QtScript parser. + if (!length) + return comment.isValid(); + if (*chars == QLatin1Char(':') && chars[1].isSpace()) { + comment.extracomment += QString(chars+1, length-1); + } else if (*chars == QLatin1Char('=') && chars[1].isSpace()) { + comment.msgid = QString(chars+2, length-2).simplified(); + } else if (*chars == QLatin1Char('~') && chars[1].isSpace()) { + QString text = QString(chars+2, length-2).trimmed(); + int k = text.indexOf(QLatin1Char(' ')); + if (k > -1) + comment.extra.insert(text.left(k), text.mid(k + 1).trimmed()); + } else if (*chars == QLatin1Char('%') && chars[1].isSpace()) { + comment.sourcetext.reserve(comment.sourcetext.length() + length-2); + ushort *ptr = (ushort *)comment.sourcetext.data() + comment.sourcetext.length(); + int p = 2, c; + forever { + if (p >= length) + break; + c = chars[p++].unicode(); + if (isspace(c)) + continue; + if (c != '"') + break; + forever { + if (p >= length) + break; + c = chars[p++].unicode(); + if (c == '"') + break; + if (c == '\\') { + if (p >= length) + break; + c = chars[p++].unicode(); + if (c == '\n') + break; + *ptr++ = '\\'; + } + *ptr++ = c; + } + } + comment.sourcetext.resize(ptr - (ushort *)comment.sourcetext.data()); + } + return comment.isValid(); +} + bool loadQml(Translator &translator, const QString &filename, ConversionData &cd) { cd.m_sourceFileName = filename; @@ -260,6 +398,27 @@ bool loadQml(Translator &translator, const QString &filename, ConversionData &cd if (parser.parse()) { FindTrCalls trCalls; + + // build up a list of comments that contain translation information. + for (int i = 0; i < driver.comments().size(); ++i) { + AST::SourceLocation loc = driver.comments().at(i); + QString commentStr = code.mid(loc.offset+2, loc.length-2); + if (commentStr.endsWith(QLatin1String("*/"))) + commentStr.chop(2); + + if (trCalls.comments.isEmpty() || trCalls.comments.last().lastLine != int(loc.startLine)) { + Comment comment; + comment.lastLine = loc.startLine+1; + if (processComment(commentStr.constData(), commentStr.length(), comment)) + trCalls.comments.append(comment); + } else { + Comment &lastComment = trCalls.comments.last(); + lastComment.lastLine += 1; + processComment(commentStr.constData(), commentStr.length(), lastComment); + } + } + + //find all tr calls in the code trCalls(&translator, filename, parser.ast()); } else { QString error = createErrorString(filename, code, parser); |