diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index 2f037548a8..aee0020a67 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -763,18 +764,26 @@ void AOTCompiledContext::setReturnValueUndefined() const } } -static void captureFallbackProperty( - QObject *object, int coreIndex, int notifyIndex, bool isConstant, - QQmlContextData *qmlContext) +static QQmlPropertyCapture *propertyCapture(const QQmlContextData *qmlContext) { - if (!qmlContext || isConstant) - return; + if (!qmlContext) + return nullptr; QQmlEngine *engine = qmlContext->engine(); Q_ASSERT(engine); QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); Q_ASSERT(ep); - if (QQmlPropertyCapture *capture = ep->propertyCapture) + return ep->propertyCapture; +} + +static void captureFallbackProperty( + QObject *object, int coreIndex, int notifyIndex, bool isConstant, + const QQmlContextData *qmlContext) +{ + if (isConstant) + return; + + if (QQmlPropertyCapture *capture = propertyCapture(qmlContext)) capture->captureProperty(object, coreIndex, notifyIndex); } @@ -782,14 +791,10 @@ static void captureObjectProperty( QObject *object, const QQmlPropertyCache *propertyCache, const QQmlPropertyData *property, QQmlContextData *qmlContext) { - if (!qmlContext || property->isConstant()) + if (property->isConstant()) return; - QQmlEngine *engine = qmlContext->engine(); - Q_ASSERT(engine); - QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); - Q_ASSERT(ep); - if (QQmlPropertyCapture *capture = ep->propertyCapture) + if (QQmlPropertyCapture *capture = propertyCapture(qmlContext)) capture->captureProperty(object, propertyCache, property); } @@ -1059,6 +1064,21 @@ bool AOTCompiledContext::captureQmlContextPropertyLookup(uint index) const return false; } +void AOTCompiledContext::captureTranslation() const +{ + if (QQmlPropertyCapture *capture = propertyCapture(qmlContext)) + capture->captureTranslation(); +} + +QString AOTCompiledContext::translationContext() const +{ +#if QT_CONFIG(translation) + return QV4::GlobalExtensions::currentTranslationContext(engine->handle()); +#else + return QString(); +#endif +} + QMetaType AOTCompiledContext::lookupResultMetaType(uint index) const { QV4::Lookup *l = compilationUnit->runtimeLookups + index; diff --git a/src/qml/qml/qqmlbuiltinfunctions.cpp b/src/qml/qml/qqmlbuiltinfunctions.cpp index 4702797e74..80bd427970 100644 --- a/src/qml/qml/qqmlbuiltinfunctions.cpp +++ b/src/qml/qml/qqmlbuiltinfunctions.cpp @@ -1942,6 +1942,42 @@ ReturnedValue GlobalExtensions::method_qsTranslateNoOp(const FunctionObject *b, return argv[1].asReturnedValue(); } +QString GlobalExtensions::currentTranslationContext(ExecutionEngine *engine) +{ + QString context; + CppStackFrame *frame = engine->currentStackFrame; + + // The first non-empty source URL in the call stack determines the translation context. + while (frame && context.isEmpty()) { + if (CompiledData::CompilationUnitBase *baseUnit = frame->v4Function->compilationUnit) { + const auto *unit = static_cast(baseUnit); + QString fileName = unit->fileName(); + QUrl url(unit->fileName()); + if (url.isValid() && url.isRelative()) { + context = url.fileName(); + } else { + context = QQmlFile::urlToLocalFileOrQrc(fileName); + if (context.isEmpty() && fileName.startsWith(QLatin1String(":/"))) + context = fileName; + } + context = QFileInfo(context).baseName(); + } + frame = frame->parentFrame(); + } + + if (context.isEmpty()) { + if (QQmlRefPointer ctxt = engine->callingQmlContext()) { + QString path = ctxt->urlString(); + int lastSlash = path.lastIndexOf(QLatin1Char('/')); + int lastDot = path.lastIndexOf(QLatin1Char('.')); + int length = lastDot - (lastSlash + 1); + context = (lastSlash > -1) ? path.mid(lastSlash + 1, (length > -1) ? length : -1) : QString(); + } + } + + return context; +} + /*! \qmlmethod string Qt::qsTr(string sourceText, string disambiguation, int n) @@ -1971,43 +2007,10 @@ ReturnedValue GlobalExtensions::method_qsTr(const FunctionObject *b, const Value if ((argc > 2) && !argv[2].isNumber()) THROW_GENERIC_ERROR("qsTr(): third argument (n) must be a number"); - QString context; - CppStackFrame *frame = scope.engine->currentStackFrame; - // The first non-empty source URL in the call stack determines the translation context. - while (frame && context.isEmpty()) { - if (CompiledData::CompilationUnitBase *baseUnit = frame->v4Function->compilationUnit) { - const auto *unit = static_cast(baseUnit); - QString fileName = unit->fileName(); - QUrl url(unit->fileName()); - if (url.isValid() && url.isRelative()) { - context = url.fileName(); - } else { - context = QQmlFile::urlToLocalFileOrQrc(fileName); - if (context.isEmpty() && fileName.startsWith(QLatin1String(":/"))) - context = fileName; - } - context = QFileInfo(context).baseName(); - } - frame = frame->parentFrame(); - } - - if (context.isEmpty()) { - if (QQmlRefPointer ctxt = scope.engine->callingQmlContext()) { - QString path = ctxt->urlString(); - int lastSlash = path.lastIndexOf(QLatin1Char('/')); - int lastDot = path.lastIndexOf(QLatin1Char('.')); - int length = lastDot - (lastSlash + 1); - context = (lastSlash > -1) ? path.mid(lastSlash + 1, (length > -1) ? length : -1) : QString(); - } - } - - QString text = argv[0].toQStringNoThrow(); - QString comment; - if (argc > 1) - comment = argv[1].toQStringNoThrow(); - int n = -1; - if (argc > 2) - n = argv[2].toInt32(); + const QString context = currentTranslationContext(scope.engine); + const QString text = argv[0].toQStringNoThrow(); + const QString comment = argc > 1 ? argv[1].toQStringNoThrow() : QString(); + const int n = argc > 2 ? argv[2].toInt32() : -1; if (QQmlEnginePrivate *ep = (scope.engine->qmlEngine() ? QQmlEnginePrivate::get(scope.engine->qmlEngine()) : nullptr)) if (ep->propertyCapture) diff --git a/src/qml/qml/qqmlbuiltinfunctions_p.h b/src/qml/qml/qqmlbuiltinfunctions_p.h index fad30e437f..026a79d1c7 100644 --- a/src/qml/qml/qqmlbuiltinfunctions_p.h +++ b/src/qml/qml/qqmlbuiltinfunctions_p.h @@ -209,6 +209,7 @@ struct Q_QML_PRIVATE_EXPORT GlobalExtensions { static void init(Object *globalObject, QJSEngine::Extensions extensions); #if QT_CONFIG(translation) + static QString currentTranslationContext(ExecutionEngine *engine); static ReturnedValue method_qsTranslate(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc); static ReturnedValue method_qsTranslateNoOp(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc); static ReturnedValue method_qsTr(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc); diff --git a/src/qml/qml/qqmlprivate.h b/src/qml/qml/qqmlprivate.h index b7ec09c857..c3833e7848 100644 --- a/src/qml/qml/qqmlprivate.h +++ b/src/qml/qml/qqmlprivate.h @@ -600,6 +600,8 @@ namespace QQmlPrivate // Run QQmlPropertyCapture::captureProperty() without retrieving the value. bool captureLookup(uint index, QObject *object) const; bool captureQmlContextPropertyLookup(uint index) const; + void captureTranslation() const; + QString translationContext() const; QMetaType lookupResultMetaType(uint index) const; void storeNameSloppy(uint nameIndex, void *value, QMetaType type) const; QJSValue javaScriptGlobalProperty(uint nameIndex) const; diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp index 24fbdfe140..0681916fa9 100644 --- a/src/qmlcompiler/qqmljscodegenerator.cpp +++ b/src/qmlcompiler/qqmljscodegenerator.cpp @@ -1166,6 +1166,81 @@ void QQmlJSCodeGenerator::generate_CallProperty(int nameIndex, int baseReg, int reject(u"CallProperty"_s); } +bool QQmlJSCodeGenerator::inlineTranslateMethod(const QString &name, int argc, int argv) +{ + addInclude(u"qcoreapplication.h"_s); + + const auto arg = [&](int i, const QQmlJSScope::ConstPtr &type) { + Q_ASSERT(i < argc); + return conversion(registerType(argv + i).storedType(), type, registerVariable(argv + i)); + }; + + const auto stringArg = [&](int i) { + return i < argc + ? (arg(i, m_typeResolver->stringType()) + u".toUtf8().constData()"_s) + : u"\"\""_s; + }; + + const auto intArg = [&](int i) { + return i < argc ? arg(i, m_typeResolver->intType()) : u"-1"_s; + }; + + const auto stringRet = [&](const QString &expression) { + return conversion( + m_typeResolver->stringType(), m_state.accumulatorOut().storedType(), expression); + }; + + const auto capture = [&]() { + m_body += u"aotContext->captureTranslation();\n"_s; + }; + + if (name == u"QT_TRID_NOOP"_s || name == u"QT_TR_NOOP"_s) { + Q_ASSERT(argc > 0); + m_body += m_state.accumulatorVariableOut + u" = "_s + + stringRet(arg(0, m_typeResolver->stringType())) + u";\n"_s; + return true; + } + + if (name == u"QT_TRANSLATE_NOOP"_s) { + Q_ASSERT(argc > 1); + m_body += m_state.accumulatorVariableOut + u" = "_s + + stringRet(arg(1, m_typeResolver->stringType())) + u";\n"_s; + return true; + } + + if (name == u"qsTrId"_s) { + capture(); + // We inline qtTrId() here because in the !QT_CONFIG(translation) case it's unavailable. + // QCoreApplication::translate() is always available in some primitive form. + // Also, this saves a function call. + m_body += m_state.accumulatorVariableOut + u" = "_s + + stringRet(u"QCoreApplication::translate(nullptr, "_s + stringArg(0) + + u", nullptr, "_s + intArg(1) + u")"_s) + u";\n"_s; + return true; + } + + if (name == u"qsTr"_s) { + capture(); + m_body += m_state.accumulatorVariableOut + u" = "_s + + stringRet(u"QCoreApplication::translate("_s + + u"aotContext->translationContext().toUtf8().constData(), "_s + + stringArg(0) + u", "_s + stringArg(1) + u", "_s + + intArg(2) + u")"_s) + u";\n"_s; + return true; + } + + if (name == u"qsTranslate"_s) { + capture(); + m_body += m_state.accumulatorVariableOut + u" = "_s + + stringRet(u"QCoreApplication::translate("_s + + stringArg(0) + u", "_s + stringArg(1) + u", "_s + + stringArg(2) + u", "_s + intArg(3) + u")"_s) + u";\n"_s; + return true; + } + + return false; +} + bool QQmlJSCodeGenerator::inlineMathMethod(const QString &name, int argc, int argv) { addInclude(u"cmath"_s); @@ -1372,6 +1447,14 @@ void QQmlJSCodeGenerator::generate_CallQmlContextPropertyLookup(int index, int a if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::JavaScriptReturnValue) reject(u"call to untyped JavaScript function"_s); + if (m_typeResolver->equals(m_state.accumulatorOut().scopeType(), + m_typeResolver->jsGlobalObject())) { + const QString name = m_jsUnitGenerator->stringForIndex( + m_jsUnitGenerator->lookupNameIndex(index)); + if (inlineTranslateMethod(name, argc, argv)) + return; + } + AccumulatorConverter registers(this); const QString indexString = QString::number(index); diff --git a/src/qmlcompiler/qqmljscodegenerator_p.h b/src/qmlcompiler/qqmljscodegenerator_p.h index f9e59b072a..56fd6ad624 100644 --- a/src/qmlcompiler/qqmljscodegenerator_p.h +++ b/src/qmlcompiler/qqmljscodegenerator_p.h @@ -261,7 +261,9 @@ private: QString argumentsList(int argc, int argv, QString *outVar); QString castTargetName(const QQmlJSScope::ConstPtr &type) const; + bool inlineTranslateMethod(const QString &name, int argc, int argv); bool inlineMathMethod(const QString &name, int argc, int argv); + QQmlJSScope::ConstPtr mathObject() const { using namespace Qt::StringLiterals; diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp index 3c37c5ebd6..f0a14c882e 100644 --- a/src/qmlcompiler/qqmljstypepropagator.cpp +++ b/src/qmlcompiler/qqmljstypepropagator.cpp @@ -1027,7 +1027,7 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar } addReadRegister(base, callBase); - propagateCall(member.method(), argc, argv); + propagateCall(member.method(), argc, argv, member.scopeType()); } QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList &methods, @@ -1149,7 +1149,9 @@ void QQmlJSTypePropagator::addReadRegister(int index, const QQmlJSRegisterConten m_state.addReadRegister(index, m_typeResolver->convert(m_state.registers[index], convertTo)); } -void QQmlJSTypePropagator::propagateCall(const QList &methods, int argc, int argv) +void QQmlJSTypePropagator::propagateCall( + const QList &methods, int argc, int argv, + const QQmlJSScope::ConstPtr &scope) { QStringList errors; const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors); @@ -1167,9 +1169,11 @@ void QQmlJSTypePropagator::propagateCall(const QList &methods, ? m_typeResolver->jsValueType() : QQmlJSScope::ConstPtr(match.returnType()); setAccumulator(m_typeResolver->returnType( - returnType ? QQmlJSScope::ConstPtr(returnType) : m_typeResolver->voidType(), - match.isJavaScriptFunction() ? QQmlJSRegisterContent::JavaScriptReturnValue - : QQmlJSRegisterContent::MethodReturnValue)); + returnType ? QQmlJSScope::ConstPtr(returnType) : m_typeResolver->voidType(), + match.isJavaScriptFunction() + ? QQmlJSRegisterContent::JavaScriptReturnValue + : QQmlJSRegisterContent::MethodReturnValue, + scope)); if (!m_state.accumulatorOut().isValid()) setError(u"Cannot store return type of method %1()."_s.arg(match.methodName())); @@ -1189,6 +1193,114 @@ void QQmlJSTypePropagator::propagateCall(const QList &methods, } } +bool QQmlJSTypePropagator::propagateTranslationMethod( + const QList &methods, int argc, int argv) +{ + if (methods.length() != 1) + return false; + + const QQmlJSMetaMethod method = methods.front(); + const QQmlJSRegisterContent intType + = m_typeResolver->globalType(m_typeResolver->intType()); + const QQmlJSRegisterContent stringType + = m_typeResolver->globalType(m_typeResolver->stringType()); + const QQmlJSRegisterContent returnType + = m_typeResolver->returnType( + m_typeResolver->stringType(), QQmlJSRegisterContent::MethodReturnValue, + m_typeResolver->jsGlobalObject()); + + if (method.methodName() == u"qsTranslate"_s) { + switch (argc) { + case 4: + addReadRegister(argv + 3, intType); // n + Q_FALLTHROUGH(); + case 3: + addReadRegister(argv + 2, stringType); // disambiguation + Q_FALLTHROUGH(); + case 2: + addReadRegister(argv + 1, stringType); // sourceText + addReadRegister(argv, stringType); // context + setAccumulator(returnType); + return true; + default: + return false; + } + } + + if (method.methodName() == u"QT_TRANSLATE_NOOP"_s) { + switch (argc) { + case 3: + addReadRegister(argv + 2, stringType); // disambiguation + Q_FALLTHROUGH(); + case 2: + addReadRegister(argv + 1, stringType); // sourceText + addReadRegister(argv, stringType); // context + setAccumulator(returnType); + return true; + default: + return false; + } + } + + if (method.methodName() == u"qsTr"_s) { + switch (argc) { + case 3: + addReadRegister(argv + 2, intType); // n + Q_FALLTHROUGH(); + case 2: + addReadRegister(argv + 1, stringType); // disambiguation + Q_FALLTHROUGH(); + case 1: + addReadRegister(argv, stringType); // sourceText + setAccumulator(returnType); + return true; + default: + return false; + } + } + + if (method.methodName() == u"QT_TR_NOOP"_s) { + switch (argc) { + case 2: + addReadRegister(argv + 1, stringType); // disambiguation + Q_FALLTHROUGH(); + case 1: + addReadRegister(argv, stringType); // sourceText + setAccumulator(returnType); + return true; + default: + return false; + } + } + + if (method.methodName() == u"qsTrId"_s) { + switch (argc) { + case 2: + addReadRegister(argv + 1, intType); // n + Q_FALLTHROUGH(); + case 1: + addReadRegister(argv, stringType); // id + setAccumulator(returnType); + return true; + default: + return false; + } + } + + if (method.methodName() == u"QT_TRID_NOOP"_s) { + switch (argc) { + case 1: + addReadRegister(argv, stringType); // id + setAccumulator(returnType); + return true; + default: + return false; + } + } + + return false; +} + void QQmlJSTypePropagator::generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv) { @@ -1224,8 +1336,13 @@ void QQmlJSTypePropagator::propagateScopeLookupCall(const QString &functionName, = m_typeResolver->scopedType(m_function->qmlScope, functionName); if (resolvedContent.isMethod()) { const auto methods = resolvedContent.method(); + if (resolvedContent.variant() == QQmlJSRegisterContent::JavaScriptGlobal) { + if (propagateTranslationMethod(methods, argc, argv)) + return; + } + if (!methods.isEmpty()) { - propagateCall(methods, argc, argv); + propagateCall(methods, argc, argv, resolvedContent.scopeType()); return; } } diff --git a/src/qmlcompiler/qqmljstypepropagator_p.h b/src/qmlcompiler/qqmljstypepropagator_p.h index 30967413b9..c01ad71cc2 100644 --- a/src/qmlcompiler/qqmljstypepropagator_p.h +++ b/src/qmlcompiler/qqmljstypepropagator_p.h @@ -196,7 +196,10 @@ private: QQmlJS::SourceLocation getCurrentBindingSourceLocation() const; QQmlJSRegisterContent propagateBinaryOperation(QSOperator::Op op, int lhs); - void propagateCall(const QList &methods, int argc, int argv); + void propagateCall( + const QList &methods, int argc, int argv, + const QQmlJSScope::ConstPtr &scope); + bool propagateTranslationMethod(const QList &methods, int argc, int argv); void propagatePropertyLookup(const QString &name); void propagateScopeLookupCall(const QString &functionName, int argc, int argv); void saveRegisterStateForJump(int offset); diff --git a/src/qmlcompiler/qqmljstyperesolver.cpp b/src/qmlcompiler/qqmljstyperesolver.cpp index 58b18f8fb7..533b71fcc4 100644 --- a/src/qmlcompiler/qqmljstyperesolver.cpp +++ b/src/qmlcompiler/qqmljstyperesolver.cpp @@ -1200,11 +1200,12 @@ QQmlJSRegisterContent QQmlJSTypeResolver::valueType(const QQmlJSRegisterContent } QQmlJSRegisterContent QQmlJSTypeResolver::returnType( - const QQmlJSScope::ConstPtr &type, QQmlJSRegisterContent::ContentVariant variant) const + const QQmlJSScope::ConstPtr &type, QQmlJSRegisterContent::ContentVariant variant, + const QQmlJSScope::ConstPtr &scope) const { Q_ASSERT(variant == QQmlJSRegisterContent::MethodReturnValue || variant == QQmlJSRegisterContent::JavaScriptReturnValue); - return QQmlJSRegisterContent::create(storedType(type), type, variant); + return QQmlJSRegisterContent::create(storedType(type), type, variant, scope); } bool QQmlJSTypeResolver::registerIsStoredIn( diff --git a/src/qmlcompiler/qqmljstyperesolver_p.h b/src/qmlcompiler/qqmljstyperesolver_p.h index 2f56003d65..3aad8b7761 100644 --- a/src/qmlcompiler/qqmljstyperesolver_p.h +++ b/src/qmlcompiler/qqmljstyperesolver_p.h @@ -99,8 +99,9 @@ public: QQmlJSRegisterContent scopedType(const QQmlJSScope::ConstPtr &scope, const QString &name) const; QQmlJSRegisterContent memberType(const QQmlJSRegisterContent &type, const QString &name) const; QQmlJSRegisterContent valueType(const QQmlJSRegisterContent &list) const; - QQmlJSRegisterContent returnType(const QQmlJSScope::ConstPtr &type, - QQmlJSRegisterContent::ContentVariant variant) const; + QQmlJSRegisterContent returnType( + const QQmlJSScope::ConstPtr &type, QQmlJSRegisterContent::ContentVariant variant, + const QQmlJSScope::ConstPtr &scope) const; bool registerIsStoredIn(const QQmlJSRegisterContent ®, const QQmlJSScope::ConstPtr &type) const; diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 834978919e..4301e10552 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -136,6 +136,7 @@ set(qml_files themergood.qml throwObjectName.qml toString.qml + translation.qml typePropertyClash.qml typedArray.qml undefinedResets.qml diff --git a/tests/auto/qml/qmlcppcodegen/data/translation.qml b/tests/auto/qml/qmlcppcodegen/data/translation.qml new file mode 100644 index 0000000000..cc666176a5 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/translation.qml @@ -0,0 +1,25 @@ +pragma Strict +import QtQml + +QtObject { + // Force the engine to generate script bindings by appending an empty string to each one. + + property string translate2: qsTranslate("c", "s") + "" + property string translate3: qsTranslate("c", "s", "d") + "" + property string translate4: qsTranslate("c", "s", "d", 4) + "" + + property string translateNoop2: QT_TRANSLATE_NOOP("c", "s") + "" + property string translateNoop3: QT_TRANSLATE_NOOP("c", "s", "d") + "" + + property string tr1: qsTr("s") + "" + property string tr2: qsTr("s", "d") + "" + property string tr3: qsTr("s", "d", 4) + "" + + property string trNoop1: QT_TR_NOOP("s") + "" + property string trNoop2: QT_TR_NOOP("s", "d") + "" + + property string trId1: qsTrId("s") + "" + property string trId2: qsTrId("s", 4) + "" + + property string trIdNoop1: QT_TRID_NOOP("s") + "" +} diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index 8096ed872d..93a93183b4 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -123,6 +123,7 @@ private slots: void objectToString(); void throwObjectName(); void javaScriptArgument(); + void translation(); }; void tst_QmlCppCodegen::simpleBinding() @@ -2242,6 +2243,33 @@ void tst_QmlCppCodegen::javaScriptArgument() QCOMPARE(o->property("d").toString(), u"9"_qs); } +void tst_QmlCppCodegen::translation() +{ + QQmlEngine engine; + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/translation.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer o(c.create()); + + QCOMPARE(o->property("translate2"), u"s"_s); + QCOMPARE(o->property("translate3"), u"s"_s); + QCOMPARE(o->property("translate4"), u"s"_s); + + QCOMPARE(o->property("translateNoop2"), u"s"_s); + QCOMPARE(o->property("translateNoop3"), u"s"_s); + + QCOMPARE(o->property("tr1"), u"s"_s); + QCOMPARE(o->property("tr2"), u"s"_s); + QCOMPARE(o->property("tr3"), u"s"_s); + + QCOMPARE(o->property("trNoop1"), u"s"_s); + QCOMPARE(o->property("trNoop2"), u"s"_s); + + QCOMPARE(o->property("trId1"), u"s"_s); + QCOMPARE(o->property("trId2"), u"s"_s); + + QCOMPARE(o->property("trIdNoop1"), u"s"_s); +} + void tst_QmlCppCodegen::runInterpreted() { #ifdef Q_OS_ANDROID