aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOlivier De Cannière <olivier.decanniere@qt.io>2024-01-17 17:41:29 +0100
committerOlivier De Cannière <olivier.decanniere@qt.io>2024-04-03 15:11:43 +0200
commit8e4a2a888b3bb9be65503646768cef5a0a32129e (patch)
tree2b3d10572d2fd1cf6861f2ee2bb41e0478c58a06
parent681f8addc1ee984da69797c2ac18f39ab89470de (diff)
QQmlEngine: Set translationsDirectory in loadFromModule
In the process, loadFromModule had to be split in two parts. The type resolution has to happen so that the translationDirectory can be set and the translations have to be loaded before loading the actual module. Pick-to: 6.5 Fixes: QTBUG-116589 Change-Id: Ife7999f418ba35bfb0eed9050198e5a886fa74ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> (cherry picked from commit 986b4d155bd39836b1aa3c3da332ef07d2a949fd) Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> (cherry picked from commit 8206c3d86c27327e31bcefaef6a6606e207c9b18)
-rw-r--r--src/qml/qml/qqmlapplicationengine.cpp31
-rw-r--r--src/qml/qml/qqmlcomponent.cpp53
-rw-r--r--src/qml/qml/qqmlcomponent_p.h5
-rw-r--r--tests/auto/qml/qqmlapplicationengine/CMakeLists.txt7
-rw-r--r--tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/CMakeLists.txt30
-rw-r--r--tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/Main.qml5
-rw-r--r--tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/i18n/qml_es.qmbin0 -> 92 bytes
-rw-r--r--tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/i18n/qml_es.ts12
-rw-r--r--tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/main.cpp26
-rw-r--r--tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/CMakeLists.txt30
-rw-r--r--tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/Main.qml13
-rw-r--r--tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/i18n/qml_fr.qmbin0 -> 91 bytes
-rw-r--r--tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/i18n/qml_fr.ts12
-rw-r--r--tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/main.cpp23
-rw-r--r--tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp49
15 files changed, 270 insertions, 26 deletions
diff --git a/src/qml/qml/qqmlapplicationengine.cpp b/src/qml/qml/qqmlapplicationengine.cpp
index 1147748a16..e2d9dceb16 100644
--- a/src/qml/qml/qqmlapplicationengine.cpp
+++ b/src/qml/qml/qqmlapplicationengine.cpp
@@ -7,10 +7,10 @@
#include <QQmlComponent>
#include "qqmlapplicationengine.h"
#include "qqmlapplicationengine_p.h"
+#include <QtQml/private/qqmlcomponent_p.h>
+#include <QtQml/private/qqmldirdata_p.h>
#include <QtQml/private/qqmlfileselector_p.h>
-#include <memory>
-
QT_BEGIN_NAMESPACE
QQmlApplicationEnginePrivate::QQmlApplicationEnginePrivate(QQmlEngine *e)
@@ -109,15 +109,36 @@ void QQmlApplicationEnginePrivate::startLoad(const QUrl &url, const QByteArray &
ensureLoadingFinishes(c);
}
-void QQmlApplicationEnginePrivate::startLoad(QAnyStringView uri, QAnyStringView type)
+void QQmlApplicationEnginePrivate::startLoad(QAnyStringView uri, QAnyStringView typeName)
{
Q_Q(QQmlApplicationEngine);
- _q_loadTranslations(); //Translations must be loaded before the QML file is
QQmlComponent *c = new QQmlComponent(q, q);
ensureInitialized();
- c->loadFromModule(uri, type);
+
+ auto *componentPriv = QQmlComponentPrivate::get(c);
+ const auto [status, type] = componentPriv->prepareLoadFromModule(uri, typeName);
+
+ if (type.sourceUrl().isValid()) {
+ const auto qmlDirData = typeLoader.getQmldir(type.sourceUrl());
+ const QUrl url = qmlDirData->finalUrl();
+ if (url.scheme() == QLatin1String("file") || url.scheme() == QLatin1String("qrc")) {
+ QFileInfo fi(QQmlFile::urlToLocalFileOrQrc(url));
+ translationsDirectory = fi.path() + QLatin1String("/i18n");
+ } else {
+ translationsDirectory.clear();
+ }
+ }
+
+ /* Translations must be loaded before the QML file. They require translationDirectory to
+ * already be resolved. But, in order to resolve the translationDirectory, the type of the
+ * module to load needs to be known. Therefore, loadFromModule is split into resolution and
+ * loading because the translation directory needs to be set in between.
+ */
+ _q_loadTranslations();
+ componentPriv->completeLoadFromModule(uri, typeName, type, status);
+
ensureLoadingFinishes(c);
}
diff --git a/src/qml/qml/qqmlcomponent.cpp b/src/qml/qml/qqmlcomponent.cpp
index 6ad3b905ef..21beecbedc 100644
--- a/src/qml/qml/qqmlcomponent.cpp
+++ b/src/qml/qml/qqmlcomponent.cpp
@@ -1334,35 +1334,48 @@ void QQmlComponent::loadFromModule(QAnyStringView uri, QAnyStringView typeName,
QQmlComponent::CompilationMode mode)
{
Q_D(QQmlComponent);
+ auto [status, type] = d->prepareLoadFromModule(uri, typeName);
+ d->completeLoadFromModule(uri, typeName, type, status, mode);
+}
- auto enginePriv = QQmlEnginePrivate::get(d->engine);
+LoadHelper::ResolveTypeResult QQmlComponentPrivate::prepareLoadFromModule(QAnyStringView uri,
+ QAnyStringView typeName)
+{
+ auto enginePriv = QQmlEnginePrivate::get(engine);
// LoadHelper must be on the Heap as it derives from QQmlRefCount
auto loadHelper = QQml::makeRefPointer<LoadHelper>(&enginePriv->typeLoader, uri);
- auto [moduleStatus, type] = loadHelper->resolveType(typeName);
+ return loadHelper->resolveType(typeName);
+}
+
+void QQmlComponentPrivate::completeLoadFromModule(QAnyStringView uri, QAnyStringView typeName, QQmlType type,
+ LoadHelper::ResolveTypeResult::Status moduleStatus,
+ QQmlComponent::CompilationMode mode)
+{
+ Q_Q(QQmlComponent);
+
auto reportError = [&](QString msg) {
QQmlError error;
error.setDescription(msg);
- d->state.errors.push_back(std::move(error));
- emit statusChanged(Error);
+ state.errors.push_back(std::move(error));
+ emit q->statusChanged(q->Error);
};
if (moduleStatus == LoadHelper::ResolveTypeResult::NoSuchModule) {
- reportError(QLatin1String(R"(No module named "%1" found)")
- .arg(uri.toString()));
+ reportError(QLatin1String(R"(No module named "%1" found)").arg(uri.toString()));
} else if (!type.isValid()) {
reportError(QLatin1String(R"(Module "%1" contains no type named "%2")")
.arg(uri.toString(), typeName.toString()));
} else if (type.isCreatable()) {
- d->clear();
+ clear();
// mimic the progressChanged behavior from loadUrl
- if (d->progress != 0) {
- d->progress = 0;
- emit progressChanged(0);
+ if (progress != 0) {
+ progress = 0;
+ emit q->progressChanged(0);
}
- d->loadedType = type;
- d->progress = 1;
- emit progressChanged(1);
- emit statusChanged(status());
+ loadedType = type;
+ progress = 1;
+ emit q->progressChanged(1);
+ emit q->statusChanged(q->status());
} else if (type.isComposite()) {
loadUrl(type.sourceUrl(), mode);
@@ -1370,16 +1383,14 @@ void QQmlComponent::loadFromModule(QAnyStringView uri, QAnyStringView typeName,
auto baseUrl = type.sourceUrl();
baseUrl.setFragment(QString());
loadUrl(baseUrl, mode);
- if (!isError()) {
- d->inlineComponentName = std::make_unique<QString>(type.elementName());
- Q_ASSERT(!d->inlineComponentName->isEmpty());
+ if (!q->isError()) {
+ inlineComponentName = std::make_unique<QString>(type.elementName());
+ Q_ASSERT(!inlineComponentName->isEmpty());
}
} else if (type.isSingleton() || type.isCompositeSingleton()) {
- reportError(QLatin1String(R"(%1 is a singleton, and cannot be loaded)")
- .arg(typeName.toString()));
+ reportError(QLatin1String(R"(%1 is a singleton, and cannot be loaded)").arg(typeName.toString()));
} else {
- reportError(QLatin1String("Could not load %1, as the type is uncreatable")
- .arg(typeName.toString()));
+ reportError(QLatin1String("Could not load %1, as the type is uncreatable").arg(typeName.toString()));
}
}
diff --git a/src/qml/qml/qqmlcomponent_p.h b/src/qml/qml/qqmlcomponent_p.h
index 1d3f1d03aa..a8e94ac613 100644
--- a/src/qml/qml/qqmlcomponent_p.h
+++ b/src/qml/qml/qqmlcomponent_p.h
@@ -168,6 +168,11 @@ public:
QQmlContext *context, CreateBehavior behavior = CreateDefault);
bool isBound() const { return compilationUnit && (compilationUnit->componentsAreBound()); }
+ LoadHelper::ResolveTypeResult prepareLoadFromModule(QAnyStringView uri,
+ QAnyStringView typeName);
+ void completeLoadFromModule(QAnyStringView uri, QAnyStringView typeName, QQmlType type,
+ LoadHelper::ResolveTypeResult::Status moduleStatus,
+ QQmlComponent::CompilationMode mode = QQmlComponent::PreferSynchronous);
};
QQmlComponentPrivate::ConstructionState::~ConstructionState()
diff --git a/tests/auto/qml/qqmlapplicationengine/CMakeLists.txt b/tests/auto/qml/qqmlapplicationengine/CMakeLists.txt
index 0570b9a95c..9900afedd6 100644
--- a/tests/auto/qml/qqmlapplicationengine/CMakeLists.txt
+++ b/tests/auto/qml/qqmlapplicationengine/CMakeLists.txt
@@ -57,5 +57,12 @@ qt_internal_extend_target(tst_qqmlapplicationengine CONDITION NOT ANDROID AND NO
DEFINES
QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data"
)
+
add_subdirectory(testapp)
add_subdirectory(androidassets)
+add_dependencies(tst_qqmlapplicationengine testapp)
+
+add_subdirectory(loadFromModuleTranslationsQmlType)
+add_subdirectory(loadFromModuleTranslationsCppType)
+add_dependencies(tst_qqmlapplicationengine i18nLoadFromModuleQmlType)
+add_dependencies(tst_qqmlapplicationengine i18nLoadFromModuleCppType)
diff --git a/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/CMakeLists.txt b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/CMakeLists.txt
new file mode 100644
index 0000000000..813e7339d3
--- /dev/null
+++ b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_standard_project_setup(REQUIRES 6.5)
+
+qt_internal_add_executable(i18nLoadFromModuleCppType
+ OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/"
+ SOURCES
+ main.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Qml
+)
+
+qt_add_qml_module(i18nLoadFromModuleCppType
+ URI TranslatedCpp
+ QML_FILES Main.qml
+)
+
+qt_internal_extend_target(i18nLoadFromModuleCppType
+ ENABLE_AUTOGEN_TOOLS
+ uic
+)
+
+qt_add_resources(i18nLoadFromModuleCppType "loadFromModuleCppTypeQmFile"
+ PREFIX
+ /qt/qml/TranslatedCpp/
+ FILES
+ i18n/qml_es.qm
+)
diff --git a/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/Main.qml b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/Main.qml
new file mode 100644
index 0000000000..f1d2010837
--- /dev/null
+++ b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/Main.qml
@@ -0,0 +1,5 @@
+import QtQml
+
+QtObject {
+ Component.onCompleted: Qt.exit(0)
+}
diff --git a/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/i18n/qml_es.qm b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/i18n/qml_es.qm
new file mode 100644
index 0000000000..e35ee63f89
--- /dev/null
+++ b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/i18n/qml_es.qm
Binary files differ
diff --git a/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/i18n/qml_es.ts b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/i18n/qml_es.ts
new file mode 100644
index 0000000000..f8d478f056
--- /dev/null
+++ b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/i18n/qml_es.ts
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="es_ES">
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../main.cpp" line="17"/>
+ <source>Hello</source>
+ <translation>Hola</translation>
+ </message>
+</context>
+</TS>
diff --git a/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/main.cpp b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/main.cpp
new file mode 100644
index 0000000000..fc6d2aeda4
--- /dev/null
+++ b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsCppType/main.cpp
@@ -0,0 +1,26 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+
+int main(int argc, char *argv[])
+{
+ QLocale::setDefault(QLocale(QLocale::Language(qEnvironmentVariableIntValue("qtlang"))));
+ QGuiApplication app(argc, argv);
+ QQmlApplicationEngine engine;
+
+ QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
+ &app, []() { QCoreApplication::exit(-1); },
+ Qt::QueuedConnection);
+ engine.loadFromModule("TranslatedCpp", "Main");
+ app.exec();
+
+ QString expected = qgetenv("LOADFROMMODULE_TEST_EXPECTED_OUTPUT");
+ QString actual = QObject::tr("Hello");
+
+ if (actual == expected)
+ return 0;
+
+ return actual[0].toLatin1();
+}
diff --git a/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/CMakeLists.txt b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/CMakeLists.txt
new file mode 100644
index 0000000000..a1cdd600cd
--- /dev/null
+++ b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_standard_project_setup(REQUIRES 6.5)
+
+qt_internal_add_executable(i18nLoadFromModuleQmlType
+ OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/"
+ SOURCES
+ main.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::Qml
+)
+
+qt_add_qml_module(i18nLoadFromModuleQmlType
+ URI TranslatedQml
+ QML_FILES Main.qml
+)
+
+qt_internal_extend_target(i18nLoadFromModuleQmlType
+ ENABLE_AUTOGEN_TOOLS
+ uic
+)
+
+qt_add_resources(i18nLoadFromModuleQmlType "loadFromModuleQmlTypeQmFile"
+ PREFIX
+ /qt/qml/TranslatedQml/
+ FILES
+ i18n/qml_fr.qm
+)
diff --git a/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/Main.qml b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/Main.qml
new file mode 100644
index 0000000000..9f8e1984e9
--- /dev/null
+++ b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/Main.qml
@@ -0,0 +1,13 @@
+import QtQml
+
+QtObject {
+ property string expected: "placeholder"
+ property string actual: qsTr("Hello")
+
+ function f() {
+ if (expected === actual)
+ Qt.exit(0)
+ else
+ Qt.exit(actual.charCodeAt(0))
+ }
+}
diff --git a/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/i18n/qml_fr.qm b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/i18n/qml_fr.qm
new file mode 100644
index 0000000000..a53cf121a2
--- /dev/null
+++ b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/i18n/qml_fr.qm
Binary files differ
diff --git a/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/i18n/qml_fr.ts b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/i18n/qml_fr.ts
new file mode 100644
index 0000000000..87b46be9ca
--- /dev/null
+++ b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/i18n/qml_fr.ts
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="fr_FR">
+<context>
+ <name>Main</name>
+ <message>
+ <location filename="../Main.qml" line="5"/>
+ <source>Hello</source>
+ <translation>Salut</translation>
+ </message>
+</context>
+</TS>
diff --git a/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/main.cpp b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/main.cpp
new file mode 100644
index 0000000000..a6ef0a6675
--- /dev/null
+++ b/tests/auto/qml/qqmlapplicationengine/loadFromModuleTranslationsQmlType/main.cpp
@@ -0,0 +1,23 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+
+int main(int argc, char *argv[])
+{
+ QLocale::setDefault(QLocale(QLocale::Language(qEnvironmentVariableIntValue("qtlang"))));
+ QGuiApplication app(argc, argv);
+ QQmlApplicationEngine engine;
+
+ QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
+ &app, []() { QCoreApplication::exit(-1); },
+ Qt::QueuedConnection);
+ engine.loadFromModule("TranslatedQml", "Main");
+
+ QString expected = qgetenv("LOADFROMMODULE_TEST_EXPECTED_OUTPUT");
+ auto *root = engine.rootObjects().first();
+ root->setProperty("expected", expected);
+ root->metaObject()->invokeMethod(root, "f");
+ return app.exec();
+}
diff --git a/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp b/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp
index 5774b68c32..1b7d719d0a 100644
--- a/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp
+++ b/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp
@@ -28,6 +28,8 @@ private slots:
void removeObjectsWhenDestroyed();
void loadTranslation_data();
void loadTranslation();
+ void loadFromModuleTranslation_data();
+ void loadFromModuleTranslation();
void translationChange();
void setInitialProperties();
void failureToLoadTriggersWarningSignal();
@@ -268,6 +270,53 @@ void tst_qqmlapplicationengine::loadTranslation()
QCOMPARE(rootObject->property("translation").toString(), translation);
}
+void tst_qqmlapplicationengine::loadFromModuleTranslation_data()
+{
+ QTest::addColumn<QString>("executable");
+ QTest::addColumn<QLocale::Language>("LANG");
+ QTest::addColumn<QString>("output");
+
+ QString qmlTypeExecutable = "loadFromModuleTranslationsQmlType/i18nLoadFromModuleQmlType";
+ QString cppTypeExecutable = "loadFromModuleTranslationsCppType/i18nLoadFromModuleCppType";
+
+ QTest::newRow("Qml: en -> en") << qmlTypeExecutable << QLocale::English << "Hello";
+ QTest::newRow("Qml: en -> fr") << qmlTypeExecutable << QLocale::French << "Salut";
+ QTest::newRow("Cpp: en -> en") << cppTypeExecutable << QLocale::English << "Hello";
+ QTest::newRow("Cpp: en -> es") << cppTypeExecutable << QLocale::Spanish << "Hola";
+}
+
+void tst_qqmlapplicationengine::loadFromModuleTranslation()
+{
+#if defined(Q_OS_ANDROID)
+ QSKIP("Test doesn't currently run on Android");
+ return;
+#endif
+
+#if QT_CONFIG(process)
+ QFETCH(QString, executable);
+ QFETCH(QLocale::Language, LANG);
+ QFETCH(QString, output);
+
+ QDir::setCurrent(buildDir);
+ QProcess app;
+ auto env = QProcessEnvironment::systemEnvironment();
+ env.insert("qtlang", QString::number(int(LANG)));
+ env.insert("LOADFROMMODULE_TEST_EXPECTED_OUTPUT", output);
+ app.setProcessEnvironment(env);
+ app.start(executable);
+ QVERIFY(app.waitForStarted());
+ QVERIFY(app.waitForFinished());
+
+ auto status = app.exitStatus();
+ auto code = app.exitCode();
+ QVERIFY2(code == 0,
+ QStringLiteral("status: %1, exitCode: %2").arg(status).arg(code).toStdString().c_str());
+ app.kill();
+#else
+ QSKIP("No process support");
+#endif
+}
+
void tst_qqmlapplicationengine::translationChange()
{
if (QLocale().language() == QLocale::SwissGerman) {