I am trying to write a simple qml application with language translations using CMake and although the application runs, it never shows translations. I'm on a Linux platform with CMake version 3.22.2 and Qt version 3.15.2 Here is the directory structure:
├── CMakeLists.txt
└── src
├── CMakeLists.txt
├── main.cpp
├── silly.qrc
└── qml
├── silly.qml
└── translations
├── qml_de.ts
├── qml_en.ts
└── qml_fr.ts
CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(silly VERSION 1.0.0)
add_subdirectory(src)
src/CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
find_package(Qt5 COMPONENTS Qml Quick LinguistTools REQUIRED)
set(TRANSLATIONS
qml/translations/qml_en.ts
qml/translations/qml_fr.ts
qml/translations/qml_de.ts
)
qt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TRANSLATIONS})
qt5_add_resources(QRC_RESOURCES "silly.qrc")
add_executable(silly main.cpp
"${QRC_RESOURCES}"
"${QM_FILES}"
)
target_compile_features(silly PUBLIC cxx_std_17)
set_target_properties(silly PROPERTIES AUTOMOC ON AUTORCC ON)
target_link_libraries(silly PRIVATE Qt5::Quick Qt5::Qml)
target_include_directories(silly
PUBLIC
$<INSTALL_INTERFACE:.>
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>
)
install(TARGETS silly)
src/main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.addImportPath("qrc:/");
engine.load(QUrl("qrc:/qml/silly.qml"));
return app.exec();
}
src/silly.qrc
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/qml">
<file alias="silly.qml">qml/silly.qml</file>
</qresource>
</RCC>
src/qml/silly.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
Button {
anchors.fill: parent
spacing: 20
text: qsTr("Hello")
}
}
src/qml/translations/qml_de.ts
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de_DE">
<context>
<name>silly</name>
<message>
<location filename="../silly.qml" line="11"/>
<source>Hello</source>
<translation>Hallo</translation>
</message>
</context>
</TS>
src/qml/translations/qml_en.ts
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US">
<context>
<name>silly</name>
<message>
<location filename="../silly.qml" line="11"/>
<source>Hello</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>
src/qml/translations/qml_fr.ts
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="fr_FR">
<context>
<name>silly</name>
<message>
<location filename="../silly.qml" line="11"/>
<source>Hello</source>
<translation>Bonjour</translation>
</message>
</context>
</TS>
It builds and runs with no errors, but when I attempt to test with this:
LANGUAGE=fr src/silly I get a button that says "Hello" instead of a button that says "Bonjour". I've been trying to figure this out for hours which also leads me to the secondary question: how does one generally troubleshoot Qt language translations? This is my first time and I could find no documentation on that.
That's pretty badly documented stuff which works magically with ready-made example using dedicated qmake configuration parameter embed_translations. I advice you to take a look into the original example's build dir where .qm files and a special qrc file qmake_qmake_qm_files.qrc get generated.
You don't need to use QTranslator unless you want to support dynamic language switch. At startup, QML runtime automatically loads a translation file qml_<language_COUNTRY>.qm (qml_xx_XX.qm where xx is ISO639 and XX is optional ISO 3166 code) from the i18n subdirectory of the root QML file, based on the system language, if it finds one.
You need to get your .qm files to qrc:/qml/i18n/ folder because your main qml file is in qrc:/qml/.
With CMake you can do it as follows:
Add a new qrc file, e.g. cmake_qm_files.qrc to your project
<RCC>
<qresource prefix="/qml/i18n">
<file>qml_de.qm</file>
<file>qml_en.qm</file>
<file>qml_fr.qm</file>
</qresource>
</RCC>
Get CMake to copy the qrc file to binary dir where .qm files get created
configure_file(cmake_qm_files.qrc ${CMAKE_BINARY_DIR} COPYONLY)
Get qrc file resource compiled and embedded to your executable
add_executable(silly main.cpp
${QRC_RESOURCES}
${CMAKE_BINARY_DIR}/cmake_qm_files.qrc
)
I typically use QLocale for translation testing as follows:
QGuiApplication app(argc, argv);
QLocale systemLocale = QLocale::system();
QLocale::Language language = systemLocale.language();
QLocale::Country country = systemLocale.country();
qDebug() << "System locale language:" << language << ", country:" << country;
// TEST: change default locale by removing comments below
// language = QLocale::French;
// country = QLocale::France;
language = QLocale::English;
country = QLocale::Australia;
QLocale locale(language, country);
qDebug() << "Changing default locale to language:" << locale.language() << ", country:" << locale.country();
QLocale::setDefault(locale); // TEST: set default locale to something else than system locale
QQmlApplicationEngine engine;
Related
I have yocto dev environment setup, in which I can bitbake and run a simple c++ application in the target. Now I want to try with simple Qt application. When I execute bitbake-layers show-layers it shows meta-qt5 in the list...
meta-qt5
/home/prc1cob/repo/out/salt/kawa/../../..//os/external/meta-qt5 7
meta-oe
/home/prc1cob/repo/out/salt/kawa/../../../build/yocto/meta-openembedded/meta-oe
6
With this, I assume qt5 is already present in my yocto build.
How to write .bb file to build a simple HelloWorld qt application as below...
#include <QCoreApplication>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "Hello World";
return a.exec();
}
Thankyou!!
Yocto provides a great class qmake5 to compile QT projects based on QMake.
In order to use it create a .pro file for the project:
qtexample.pro
QT += core
SOURCES += qtexample.cpp
qtexample.cpp
#include <QCoreApplication>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "Hello World";
return a.exec();
}
Now, in your layer, you can add a simple recipe that compiles that project.
For example: meta-custom/recipes-project/qtexample
In qtexample folder create files folder and copy qtexample.pro and qtexample.cpp in it.
In qtexample folder directly create qtexample_0.1.bb recipe:
SUMMARY = "QT Example Recipe"
LICENSE = "CLOSED"
SRC_URI = "file://qtexample.pro \
file://qtexample.cpp"
DEPENDS += "qtbase"
RDEPENDS_${PN} += "qtwayland"
S = "${WORKDIR}"
inherit qmake5
You can change the version ofcourse (0.1).
The layout should look like this:
meta-custom/
├── recipes-project/
├── qtexample_0.1.bb
└── files/
├── qtexample.pro
└── qtexample.cpp
Then, bitbake qtexample should work and create a qtexample binary that you can find in ${WORKDIR}
Update: Simplified problem
With CMake 3.16.8 or less, the following workflow was working, but 3.17+ deletes the Qt's TS files.
The idea is taken from Professional CMake, 7th Edition by Craig Scott
Requirements
cmake 3.17+, Qt 5.9+, ninja
Concept
To update the translation files for Qt, UPDATE_TRANSLATIONS can be enabled. When finished, disable it.
CMake 3.17+ deletes the updated TS files.
Adjust pathes in the script update_translation.sh to automate the workflow.
Steps to reproduce
git clone https://github.com/Macintron/QtTranslationDemo.git # or create files from below in QtTranslationDemo.
mkdir build # NOT in QtTranslationDemo
cd build
cmake -G Ninja -DCMAKE_PREFIX_PATH=[pathToQtDir/5.15.1/clang_64] ../QtTranslationDemo
ninja
./demo
Output should be:
Demo
de.qm: 85 bytes
Language: 'Deutsch'
greeting: ''
en.qm: 85 bytes
Language: 'English'
greeting: ''
now the problem starts:
cmake -DUPDATE_TRANSLATIONS=ON ../QtTranslationDemo
ninja demoTranslations
cmake -DUPDATE_TRANSLATIONS=OFF ../QtTranslationDemo
# new strings ("greeting") should haven been added to the ts files, but TS files are deleted instead!
Why are the TS files get deleted? Is this a bug in CMake 3.17+ or a bug in my workflow?
build info
The build directory must NOT be inside the sources.
Source files
CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(QtTranslationDemo LANGUAGES CXX)
find_package(Qt5
COMPONENTS
LinguistTools Core
REQUIRED
)
option(UPDATE_TRANSLATIONS "Enable rescanning sources to update .ts files" OFF)
set(_tsFiles en.ts de.ts)
if(UPDATE_TRANSLATIONS)
message("** parse sources for new translations")
qt5_create_translation(_qmFiles
main.cpp
${_tsFiles}
)
else()
message("** update qm files")
qt5_add_translation(_qmFiles
${_tsFiles}
)
endif()
add_custom_target(demoTranslations DEPENDS ${_qmFiles})
add_executable(demo
main.cpp
${_qmFiles}
)
target_link_libraries(demo
PUBLIC
Qt5::Core
)
main.cpp
#include <QTranslator>
#include <QDir>
#include <QStringList>
static const char* language = QT_TRANSLATE_NOOP("#default", "Language");
static const char* greeting = QT_TRANSLATE_NOOP("#default", "greeting"); // not in ts files now!
int main(int argc, char *argv[])
{
qDebug("Demo");
QStringList qms = QDir("./").entryList({"*.qm"});
for (const auto& qm: qms) {
qDebug("%s: %lld bytes", qPrintable(qm), QFileInfo("./" + qm).size());
QTranslator appTranslator;
if (appTranslator.load(qm, "./")) {
qDebug(" Language: '%s'", qPrintable(appTranslator.translate("#default", language)));
qDebug(" greeting: '%s'", qPrintable(appTranslator.translate("#default", greeting)));
} else {
qWarning("Failed to load %s", qPrintable(qm));
}
}
return 0;
}
en.ts
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en" sourcelanguage="en">
<context>
<name>#default</name>
<message>
<source>Language</source>
<translation>English</translation>
</message>
</context>
</TS>
de.ts
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de" sourcelanguage="en">
<context>
<name>#default</name>
<message>
<source>Language</source>
<translation>Deutsch</translation>
</message>
</context>
</TS>
update_translation.sh
#!/bin/sh
set -e
thisdir=$(dirname $0)
qt_root=/usr/local/Trolltech/Qt5.15/5.15.1/clang_64
#cmaketool=/usr/local/Cellar/cmake/3.16.8/bin/cmake
cmaketool=cmake
# easier setup
rm -Rf en.qm de.qm CMakeFiles CMakeCache.txt cmake_install.cmake build.ninja .ninja_* > /dev/null 2>&1
git checkout $thisdir/de.ts $thisdir/en.ts # restore files
$cmaketool --version
$cmaketool -G Ninja -DCMAKE_PREFIX_PATH=$qt_root ../QtTranslationDemo
ninja
./demo
rm -f demo
echo "Starting CMake with DUPDATE_TRANSLATIONS..."
$cmaketool -DUPDATE_TRANSLATIONS=ON $thisdir
echo
echo "Updating translation files..."
ninja demoTranslations
echo
echo "Restore CMake configuration..."
$cmaketool -DUPDATE_TRANSLATIONS=OFF $thisdir
ninja
./demo
exit 0
This was a generic CMake problem, caused by the cleandead feature. It should be fixed in CMake 3.19.2.
Thank you all for your contributions to solve the problem.
My project looks like this:
.
├── CMakeLists.txt
└── src
├── CMakeLists.txt
├── main.cpp
└── radon
├── main.qml
├── qml.qrc
├── radon.cpp
└── radon.h
The src/CMakeList.txt defines two targets, a library and an executable that links to the library.
# only relevant code shown
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(Qt5 5.15 REQUIRED COMPONENTS Core Quick REQUIRED)
add_library(RadonCore
radon/radon.h
radon/radon.cpp
radon/qml.qrc
)
target_link_libraries(RadonCore
PUBLIC Qt5::Core Qt5::Quick)
add_executable(RadonApp
main.cpp)
target_link_libraries(RadonApp
PRIVATE RadonCore)
The library simply contains the entry point to my application, main.cpp merely calls the entry.
// header
#ifndef RADON_RADON_H
#define RADON_RADON_H
namespace radon{
int radon_entry(int argc, char* argv[]);
}
#endif //RADON_RADON_H
// source
#include "radon.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int radon::radon_entry(int argc, char* argv[])
{
// only relevant code shown
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
engine.load(url);
return QGuiApplication::exec();
}
// main.cpp
#include "radon/radon.h"
int main(int argc, char* argv[])
{
return radon::radon_entry(argc, argv);
}
The idea of making the entry independent of main() is so that I can link the library to tests that have their own main(). However, under the configuration, qt complains about not able to find main.qml
QQmlApplicationEngine failed to load component
qrc:/main.qml: No such file or directory
If I provide a main() inside radon.cpp and built it as an executable, then it works fine. What gives?
The qml.qrc looks like this:
<RCC>
<qresource prefix="/">
<file>main.qml</file>
</qresource>
</RCC>
The issue is that RadonCore is a static library. In Qt, if a static library uses resources, it must expand the Q_INIT_RESOURCE macro somewhere before first use. For more, see here. Note that the macro cannot be placed inside any namespaces (excluding global namespace). So the solution to this problem would so look like this:
extract radon_entry to the global namespace
place a macro call of Q_INIT_RESOURCE(qml) on the first line of radon_entry
I have a project that uses a server/client feature. My project layout is:
Project w/ subprojects
-server
--- some files (not important)
-client display
---Display.qml --> Main view
---ViewerForm.ui.qml --> UI Display to create the view
---Viewer.qml --> To run button clicks and javascript
I am able to run the server and client with no problems. Now, I would like to have the Viewer and ViewerForm to be available for different applications. For example:
Standalone client application (just the client display)
Imported into another application which may have several kinds of UI pages in which one is the client display.
How can I set this up so that I have only 1 Viewer project and it can be imported into different applications. Should this be a Qt Plugin? Module? I looked at the Qt documentation and I created a ViewerPlugin project as it's own project with the ViewerForm.ui.qml, Viewer.qml, several component *.ui.qml and *.qml files, and javascript files as shown below. When I build the ViewerPlugin it creates a Viewer folder with files: ViewerPlugin.dll, ViewerPlugin.exp, ViewerPlugin.lib, and qmldir. Then, I copy this folder into my client application and import the QML plugin in my Display.qml like import Viewer 1.0. But I get build errors that it can't find: CListView.qml and the other qml files.
viewerplugin.pro
TEMPLATE = lib
TARGET = ViewerPlugin
QT += qml quick
CONFIG += qt plugin c++11
DESTDIR = ../../imports/Viewer
TARGET = $$qtLibraryTarget($$TARGET)
uri = Viewer
# Input
SOURCES += \
viewerplugin_plugin.cpp \
viewer.cpp
HEADERS += \
viewerplugin_plugin.h \
viewer.h
DISTFILES += qmldir \
Viewer.qml \
ViewerForm.ui.qml \
components/CListView.qml \
components/CListViewForm.ui.qml \
components/CRangeSliderForm.ui.qml \
components/CSliderForm.ui.qml \
components/IconButtonForm.ui.qml \
components/PressAndHoldButton.qml \
components/TextButton.qml
RESOURCES += qml.qrc
!equals(_PRO_FILE_PWD_, $$OUT_PWD) {
copy_qmldir.target = $$OUT_PWD/qmldir
copy_qmldir.depends = $$_PRO_FILE_PWD_/qmldir
copy_qmldir.commands = $(COPY_FILE) "$$replace(copy_qmldir.depends, /, $$QMAKE_DIR_SEP)" "$$replace(copy_qmldir.target, /, $$QMAKE_DIR_SEP)"
QMAKE_EXTRA_TARGETS += copy_qmldir
PRE_TARGETDEPS += $$copy_qmldir.target
}
qmldir.files = qmldir
# Copy the qmldir file to the same folder as the plugin binary
cpqmldir.files = qmldir
cpqmldir.path = $$DESTDIR
COPIES += cpqmldir
qml.qrc
<RCC>
<qresource prefix="/">
<file alias="ViewerForm.ui.qml">ViewerForm.ui.qml</file>
</qresource>
<qresource prefix="/components">
<file alias="IconButtonForm.ui.qml">components/IconButtonForm.ui.qml</file>
<file alias="CRangeSliderForm.ui.qml">components/CRangeSliderForm.ui.qml</file>
<file alias="CSliderForm.ui.qml">components/CSliderForm.ui.qml</file>
<file alias="CListView.qml">components/CListView.qml</file>
<file alias="TextButton.qml">components/TextButton.qml</file>
</qresource>
<qresource prefix="/HTML5">
<file alias="index.html">HTML5/index.html</file>
<file alias="loader.css">HTML5/loader.css</file>
</qresource>
<qresource prefix="/resources">
<file alias="fontawesome-webfont.ttf">resources/fontawesome-webfont.ttf</file>
</qresource>
</RCC>
qmldir
module Viewer
CListView 1.0 CListView.qml
CListViewForm 1.0 CListViewForm.ui.qml
CRangeSliderForm 1.0 CRangeSliderForm.ui.qml
CSliderForm 1.0 CSliderForm.ui.qml
IconButtonForm 1.0 IconButtonForm.ui.qml
PressAndHoldButton 1.0 PressAndHoldButton.qml
TextButton 1.0 TextButton.qml
plugin ViewerPlugin
The components and HTML5 folders exist as described in the .pro file. The viewerplugin_plugin.h/cpp and viewer.h/cpp are the basic files created from the Qt5 wizard for QT Extensions to extend QQmlExtensionPlugin.
Below are the files that try to import the ViewerPlugin:
Client.pro
QT += quick qml serialport core webengine webchannel
CONFIG += c++11 qt
CONFIG += qtquickcompiler
RESOURCES += qml.qrc
!include($${top_srcdir}/common/common.pri) {
error("Couldn't find common.pri file")
}
!include($${top_srcdir}/qmake-target-platform.pri) {
error("Couldn't find qmake-target-platform.pri file")
}
!include($${top_srcdir}/qmake-destination-path.pri) {
error("Couldn't find qmake-destination-path.pri file")
}
SOURCES += \
main.cpp
DESTDIR = $${top_srcdir}/binaries/$$DESTINATION_PATH
OBJECTS_DIR = $${top_srcdir}/build/$$DESTINATION_PATH/.obj
MOC_DIR = $${top_srcdir}/build/$$DESTINATION_PATH/.moc
RCC_DIR = $${top_srcdir}/build/$$DESTINATION_PATH/.qrc
UI_DIR = $${top_srcdir}/build/$$DESTINATION_PATH/.ui
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Refer to the documentation for the
# deprecated API to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
DISTFILES += \
Display.qml
# ViewerPlugin needs to be copied to binaries executable directory
CONFIG += file_copies
COPIES += ViewerPlugin
ViewerPlugin.files = $$files($${Viewer}/*)
ViewerPlugin.path = $${DESTDIR}/Viewer
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH = ${top_srcdir}
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
qml.qrc
<RCC>
<qresource prefix="/">
<file alias="Display.qml">Display.qml</file>
</qresource>
</RCC>
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtWebEngine>
#include <QObject>
#include <QMetaObject>
int main(int argc, char *argv[])
{
qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QtWebEngine::initialize();
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/Display.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
Display.qml
import Viewer 1.0
ApplicationWindow {
id: window
visibility: "Maximized"
visible: true
ViewerForm {
id: viewer
}
Looks like all I needed to do was update the qmldir file to add: Viewer 1.0 qrc:/Viewer.qml and it works.
I try to create an application in which I use rcc modules but I get the error "modules not installed".
I have the following folders and files:
MainFolder
|_Folder1
|_Folder11
|_qmldir
|_MyButton.qml
|_Folder2
|_qmldir
|_MyComponent.qml
Application
|_application.pro
|_main.cpp
|_main.qml
|_rccFolders
|_folder11.rcc
|_folder2.rcc
My qmldirs contain:
In Folder11:
module MainFolder.Folder1.Folder11
MyButton 1.0 MyButton.qml
In Folder2:
module MainFolder.Folder2
MyComponent 1.0 MyComponent.qml
The main.cpp:
#include ...
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QString sourcePath="C:/path_to_rcc_files/";
QQmlApplicationEngine engine;
QResource::registerResource(sourcePath+"folder11.rcc");
QResource::registerResource(sourcePath+"folder2.rcc");
engine.addImportPath(":/"); //to use .rcc files
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
The main.qml:
//QML imports
import MainFolder.Folder1.Folder11
import MainFolder.Folder2
ApplicationWindow
{
...
}
But when I execute the application, I get:
qrc:/main.qml:... module "MainFolder.Folder1.Folder11" is not installed
qrc:/main.qml:... module "MainFolder.Folder2" is not installed
What is wrong in my code? Thanks.
You may debug resource problems (go to Debugging section at the end) using QML_IMPORT_TRACE
It says import <ModuleIdentifier> <Version.Number> [as <Qualifier>] so be sure to try:
import MainFolder.Folder1.Folder11.MyButton 1.0
import MainFolder.Folder2.MyComponent 1.0
Maybe you could try to use an import path with qrc: prefix:
engine.addImportPath("qrc:/");
I am not completely sure (and actually the documentation says both the :/ and qrc:/ notations should work), but I remember I had some similar issues in the past. At least in my apps (where I use a similar structure) I always use the qrc:/ prefix.