diff options
author | Takumi ASAKI <asaki@sra.co.jp> | 2015-12-15 13:46:09 +0900 |
---|---|---|
committer | Oswald Buddenhagen <oswald.buddenhagen@qt.io> | 2016-10-25 09:01:07 +0000 |
commit | f2ebd51d96ad49eb826a4e37e67d506fffcbd40c (patch) | |
tree | e95097a54d14d577251f2f6a3247db81a9efc716 | |
parent | 8e7e60dbdea04c943bc6d50290db12d3fefd39f2 (diff) |
lupdate: Add qrc resource file support
lupdate parses qrc files specified by the RESOURCES qmake variable or by
command line options, and adds files in them as source files.
[ChangeLog][lupdate] Added qrc resource file support
Task-number: QTBUG-53206
Change-Id: Id7f952487ab11e062db9c5a419fb2ee3c4716740
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@qt.io>
13 files changed, 294 insertions, 57 deletions
diff --git a/src/linguist/linguist/doc/src/linguist-manual.qdoc b/src/linguist/linguist/doc/src/linguist-manual.qdoc index 722b853a0..308bc9f1c 100644 --- a/src/linguist/linguist/doc/src/linguist-manual.qdoc +++ b/src/linguist/linguist/doc/src/linguist-manual.qdoc @@ -847,7 +847,8 @@ The \c lupdate tool extracts user interface strings from your application. It reads the application .pro file to identify which source files contain text to be translated. This means your source files must be listed - in the \c SOURCES or \c HEADERS entry in the .pro file. If your files are + in the \c SOURCES or \c HEADERS entry in the .pro file, or in resource + files listed in the \c RESOURCE entry. If your files are not listed, the text in them will not be found. An example of a complete \c .pro file with four translation source @@ -870,37 +871,6 @@ \snippet doc_src_linguist-manual.cpp 3 - \section2 Use a Conditional to Hide QML Source From the Compiler - - The SOURCES variable is intended for C++ source files. If you list QML - or JavaScript source files there, the compiler tries to build them as though - they are C++ files. As a workaround, you can use an \c lupdate_only{...} - conditional statement so the \c lupdate tool sees the .qml files but the C++ - compiler ignores them. - - For example, the following .pro file snippet specifies two .qml files in - the application: - - \code - lupdate_only { - SOURCES = main.qml \ - MainPage.qml - } - \endcode - - You can also specify the .qml source files with a wildcard match. The - search is not recursive so you need to specify each directory where there - are user interface strings in the source code: - - \code - lupdate_only { - SOURCES = *.qml \ - *.js \ - content/*.qml \ - content/*.js - } - \endcode - \section1 Internationalizing Applications Design your application so that it can be adapted to various languages and diff --git a/src/linguist/lupdate/main.cpp b/src/linguist/lupdate/main.cpp index 9fc3d8c80..dd73a9e5b 100644 --- a/src/linguist/lupdate/main.cpp +++ b/src/linguist/lupdate/main.cpp @@ -49,6 +49,7 @@ #include <QtCore/QStringList> #include <QtCore/QTranslator> #include <QtCore/QLibraryInfo> +#include <QtCore/QXmlStreamReader> #include <iostream> @@ -395,6 +396,73 @@ public: static EvalHandler evalHandler; +static bool isSupportedExtension(const QString &ext) +{ + return ext == QLatin1String("qml") + || ext == QLatin1String("js") || ext == QLatin1String("qs") + || ext == QLatin1String("ui") || ext == QLatin1String("jui"); +} + +static QStringList getResources(const QString &resourceFile, QMakeVfs *vfs) +{ + Q_ASSERT(vfs); + if (!vfs->exists(resourceFile)) + return QStringList(); + QString content; + QString errStr; + if (!vfs->readFile(resourceFile, &content, &errStr)) { + printErr(LU::tr("lupdate error: Can not read %1: %2\n").arg(resourceFile, errStr)); + return QStringList(); + } + QStringList fileList; + QString dirPath = QFileInfo(resourceFile).path(); + QXmlStreamReader reader(content); + bool isFileTag = false; + QStringList tagStack; + tagStack << QLatin1String("RCC") << QLatin1String("qresource") << QLatin1String("file"); + int curDepth = 0; + while (!reader.atEnd()) { + QXmlStreamReader::TokenType t = reader.readNext(); + switch (t) { + case QXmlStreamReader::StartElement: + if (curDepth >= tagStack.count() || reader.name() != tagStack.at(curDepth)) { + printErr(LU::tr("unexpected <%1> tag\n").arg(reader.name().toString())); + continue; + } + if (++curDepth == tagStack.count()) + isFileTag = true; + break; + + case QXmlStreamReader::EndElement: + isFileTag = false; + if (curDepth == 0 || reader.name() != tagStack.at(curDepth - 1)) { + printErr(LU::tr("unexpected closing <%1> tag\n").arg(reader.name().toString())); + continue; + } + --curDepth; + break; + + case QXmlStreamReader::Characters: + if (isFileTag) { + QString fn = reader.text().toString(); + if (!QFileInfo(fn).isAbsolute()) + fn = dirPath + QLatin1Char('/') + fn; + QFileInfo cfi(fn); + if (isSupportedExtension(cfi.suffix())) + fileList << cfi.filePath(); + } + break; + + default: + break; + } + } + if (reader.error() != QXmlStreamReader::NoError) + printErr(LU::tr("lupdate error: %1:%2: %3\n") + .arg(resourceFile, QString::number(reader.lineNumber()), reader.errorString())); + return fileList; +} + static QStringList getSources(const char *var, const char *vvar, const QStringList &baseVPaths, const QString &projectDir, const ProFileEvaluator &visitor) { @@ -405,7 +473,7 @@ static QStringList getSources(const char *var, const char *vvar, const QStringLi } static QStringList getSources(const ProFileEvaluator &visitor, const QString &projectDir, - const QStringList &excludes) + const QStringList &excludes, QMakeVfs *vfs) { QStringList baseVPaths; baseVPaths += visitor.absolutePathValues(QLatin1String("VPATH"), projectDir); @@ -420,6 +488,10 @@ static QStringList getSources(const ProFileEvaluator &visitor, const QString &pr sourceFiles += getSources("FORMS", "VPATH_FORMS", baseVPaths, projectDir, visitor); + QStringList resourceFiles = getSources("RESOURCES", "VPATH_RESOURCES", baseVPaths, projectDir, visitor); + foreach (const QString &resource, resourceFiles) + sourceFiles += getResources(resource, vfs); + QStringList installs = visitor.values(QLatin1String("INSTALLS")) + visitor.values(QLatin1String("DEPLOYMENT")); installs.removeDuplicates(); @@ -445,12 +517,8 @@ static QStringList getSources(const ProFileEvaluator &visitor, const QString &pr while (iterator.hasNext()) { iterator.next(); QFileInfo cfi = iterator.fileInfo(); - QString ext = cfi.suffix(); - if (ext == QLatin1String("qml") - || ext == QLatin1String("js") || ext == QLatin1String("qs") - || ext == QLatin1String("ui") || ext == QLatin1String("jui")) { + if (isSupportedExtension(cfi.suffix())) sourceFiles << cfi.filePath(); - } } } } @@ -612,7 +680,7 @@ static void processProject( cd.m_sourceIsUtf16 = options & SourceIsUtf16; cd.m_includePath = visitor.absolutePathValues(QLatin1String("INCLUDEPATH"), proPath); cd.m_excludes = getExcludes(visitor, proPath); - QStringList sourceFiles = getSources(visitor, proPath, cd.m_excludes); + QStringList sourceFiles = getSources(visitor, proPath, cd.m_excludes, vfs); QSet<QString> sourceDirs; sourceDirs.insert(proPath + QLatin1Char('/')); foreach (const QString &sf, sourceFiles) @@ -723,7 +791,7 @@ int main(int argc, char **argv) #endif // Q_OS_WIN32 #endif - m_defaultExtensions = QLatin1String("java,jui,ui,c,c++,cc,cpp,cxx,ch,h,h++,hh,hpp,hxx,js,qs,qml"); + m_defaultExtensions = QLatin1String("java,jui,ui,c,c++,cc,cpp,cxx,ch,h,h++,hh,hpp,hxx,js,qs,qml,qrc"); QStringList args = app.arguments(); QStringList tsFileNames; @@ -733,6 +801,7 @@ int main(int argc, char **argv) QMultiHash<QString, QString> allCSources; QSet<QString> projectRoots; QStringList sourceFiles; + QStringList resourceFiles; QStringList includePath; QStringList alienFiles; QString targetLanguage; @@ -986,25 +1055,33 @@ int main(int argc, char **argv) int scanRootLen = dir.absolutePath().length(); foreach (const QFileInfo &fi, fileinfolist) { QString fn = QDir::cleanPath(fi.absoluteFilePath()); - sourceFiles << fn; - - if (!fn.endsWith(QLatin1String(".java")) - && !fn.endsWith(QLatin1String(".jui")) - && !fn.endsWith(QLatin1String(".ui")) - && !fn.endsWith(QLatin1String(".js")) - && !fn.endsWith(QLatin1String(".qs")) - && !fn.endsWith(QLatin1String(".qml"))) { - int offset = 0; - int depth = 0; - do { - offset = fn.lastIndexOf(QLatin1Char('/'), offset - 1); - QString ffn = fn.mid(offset + 1); - allCSources.insert(ffn, fn); - } while (++depth < 3 && offset > scanRootLen); + if (fn.endsWith(QLatin1String(".qrc"), Qt::CaseInsensitive)) { + resourceFiles << fn; + } else { + sourceFiles << fn; + + if (!fn.endsWith(QLatin1String(".java")) + && !fn.endsWith(QLatin1String(".jui")) + && !fn.endsWith(QLatin1String(".ui")) + && !fn.endsWith(QLatin1String(".js")) + && !fn.endsWith(QLatin1String(".qs")) + && !fn.endsWith(QLatin1String(".qml"))) { + int offset = 0; + int depth = 0; + do { + offset = fn.lastIndexOf(QLatin1Char('/'), offset - 1); + QString ffn = fn.mid(offset + 1); + allCSources.insert(ffn, fn); + } while (++depth < 3 && offset > scanRootLen); + } } } } else { - sourceFiles << QDir::cleanPath(fi.absoluteFilePath());; + QString fn = QDir::cleanPath(fi.absoluteFilePath()); + if (fn.endsWith(QLatin1String(".qrc"), Qt::CaseInsensitive)) + resourceFiles << fn; + else + sourceFiles << fn; projectRoots.insert(fi.absolutePath() + QLatin1Char('/')); } } @@ -1034,11 +1111,16 @@ int main(int argc, char **argv) cd.m_projectRoots = projectRoots; cd.m_includePath = includePath; cd.m_allCSources = allCSources; + if (!resourceFiles.isEmpty()) { + QMakeVfs vfs; + foreach (const QString &resource, resourceFiles) + sourceFiles << getResources(resource, &vfs); + } processSources(fetchedTor, sourceFiles, cd); updateTsFiles(fetchedTor, tsFileNames, alienFiles, sourceLanguage, targetLanguage, options, &fail); } else { - if (!sourceFiles.isEmpty() || !includePath.isEmpty()) { + if (!sourceFiles.isEmpty() || !resourceFiles.isEmpty() || !includePath.isEmpty()) { printErr(LU::tr("lupdate error:" " Both project and source files / include paths specified.\n")); return 1; diff --git a/tests/auto/linguist/lupdate/testdata/good/parseqrc/main.cpp b/tests/auto/linguist/lupdate/testdata/good/parseqrc/main.cpp new file mode 100644 index 000000000..7108f6784 --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/parseqrc/main.cpp @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// IMPORTANT!!!! If you want to add testdata to this file, +// always add it to the end in order to not change the linenumbers of translations!!! + + +void func1() { + QApplication::tr("Hello world"); +} + + diff --git a/tests/auto/linguist/lupdate/testdata/good/parseqrc/main.js b/tests/auto/linguist/lupdate/testdata/good/parseqrc/main.js new file mode 100644 index 000000000..b2e1dd9e0 --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/parseqrc/main.js @@ -0,0 +1 @@ +qsTr("From JavaScript file"); diff --git a/tests/auto/linguist/lupdate/testdata/good/parseqrc/main.qml b/tests/auto/linguist/lupdate/testdata/good/parseqrc/main.qml new file mode 100644 index 000000000..ea2258343 --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/parseqrc/main.qml @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 1.0 + +QtObject { + function translate() { + qsTr("From QML file in root"); + } +} diff --git a/tests/auto/linguist/lupdate/testdata/good/parseqrc/project.pro b/tests/auto/linguist/lupdate/testdata/good/parseqrc/project.pro new file mode 100644 index 000000000..5000c7396 --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/parseqrc/project.pro @@ -0,0 +1,7 @@ + +SOURCES += main.cpp + +RESOURCES += project.qrc +RESOURCES += main.qml + +TRANSLATIONS = project.ts diff --git a/tests/auto/linguist/lupdate/testdata/good/parseqrc/project.qrc b/tests/auto/linguist/lupdate/testdata/good/parseqrc/project.qrc new file mode 100644 index 000000000..87bacf228 --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/parseqrc/project.qrc @@ -0,0 +1,5 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/"> + <file>main.js</file> +</qresource> +</RCC> diff --git a/tests/auto/linguist/lupdate/testdata/good/parseqrc/project.ts.result b/tests/auto/linguist/lupdate/testdata/good/parseqrc/project.ts.result new file mode 100644 index 000000000..6dcd8c2a5 --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/parseqrc/project.ts.result @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +<context> + <name>QApplication</name> + <message> + <location filename="main.cpp" line="39"/> + <source>Hello world</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>main</name> + <message> + <location filename="main.js" line="1"/> + <source>From JavaScript file</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.qml" line="38"/> + <source>From QML file in root</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/lupdatecmd b/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/lupdatecmd new file mode 100644 index 000000000..f3709944b --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/lupdatecmd @@ -0,0 +1 @@ +lupdate project.qrc -ts project.ts diff --git a/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/main.js b/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/main.js new file mode 100644 index 000000000..b2e1dd9e0 --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/main.js @@ -0,0 +1 @@ +qsTr("From JavaScript file"); diff --git a/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/main.qml b/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/main.qml new file mode 100644 index 000000000..ea2258343 --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/main.qml @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 1.0 + +QtObject { + function translate() { + qsTr("From QML file in root"); + } +} diff --git a/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/project.qrc b/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/project.qrc new file mode 100644 index 000000000..c5bf15067 --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/project.qrc @@ -0,0 +1,6 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/"> + <file>main.qml</file> + <file>main.js</file> +</qresource> +</RCC> diff --git a/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/project.ts.result b/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/project.ts.result new file mode 100644 index 000000000..82719c026 --- /dev/null +++ b/tests/auto/linguist/lupdate/testdata/good/resources_cmdline/project.ts.result @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +<context> + <name>main</name> + <message> + <location filename="main.qml" line="38"/> + <source>From QML file in root</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="main.js" line="1"/> + <source>From JavaScript file</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> |