Skip to content

Commit c69c61d

Browse files
committed
0.14.0: rules-qt places Qt's own translations (translations::qt_languages): lconvert combines the linked modules' catalogs into qt_<language>.qm, as windeployqt does
1 parent 6b1f379 commit c69c61d

5 files changed

Lines changed: 92 additions & 6 deletions

File tree

‎.github/scripts/check-deps-and-qt.sh‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ qt_consumer() {
201201
fail "moc, rcc or lrelease did not reach the program"
202202
find target -name 'qt_consumer_de.qm' | grep -q . || fail "no .qm was produced"
203203
echo "ok: moc (header and inline), rcc and lrelease reached the program"
204+
grep -q "Qt's own 'Abbrechen'$" target/ci/run.log ||
205+
fail "Qt's own strings were not translated from the combined qt_de.qm"
206+
echo "ok: lconvert combined Qt's catalogs into qt_de.qm, and the program loads it"
204207
"$MCPP" build --profile dev -v > target/ci/second-build.log 2>&1 ||
205208
{ cat target/ci/second-build.log; fail "the second build failed"; }
206209
! grep -qE '/(lupdate|lrelease)[^ ]* ' target/ci/second-build.log ||

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,7 @@ int main() {
10151015
| `private_modules` | modules whose private headers are included |
10161016
| `moc`, `moc_headers` | `moc_scan::project_headers` (default) scans the package's headers by content; `moc_scan::listed` takes `moc_headers` only |
10171017
| `forms`, `resources` | `.ui` and `.qrc` files beside those in `[build] sources` |
1018-
| `i18n` | `.ts` files beside those in `[build] sources`; `update_sources` runs `lupdate` before `lrelease`, as an action whose output is the `.ts` file it rewrites; `tr_function_alias`; `deploy_to` (default `translations`); `out_dir`, where `lrelease` writes (default `<out dir>/qt/translations`) |
1018+
| `i18n` | `.ts` files beside those in `[build] sources`; `update_sources` runs `lupdate` before `lrelease`, as an action whose output is the `.ts` file it rewrites; `tr_function_alias`; `deploy_to` (default `translations`); `out_dir`, where `lrelease` writes (default `<out dir>/qt/translations`); `qt_languages`, Qt's own strings for each language: the catalogs of the linked modules combined by `lconvert` into `qt_<language>.qm` under `deploy_to`, the file windeployqt writes |
10191019
| `deploy_plugins` | plugin directories placed beside the program; default `platforms` |
10201020
| `deploy_software_gl` | Windows: `opengl32sw.dll` and `d3dcompiler_47.dll` beside the program |
10211021
| `root`, `extra_roots` | an SDK, and further prefixes |

‎rules/qt.cppm‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ struct translations {
6969
// Where `lrelease` writes them. Empty is `<out dir>/qt/translations`; a
7070
// project whose own release step copies them names a directory it knows.
7171
std::string out_dir;
72+
// Qt's own UI strings for these languages (`de`, `zh_CN`): the catalogs of
73+
// the modules the program links, from the SDK's `translations/`, combined
74+
// by `lconvert` into one `qt_<language>.qm` with no dependency left to
75+
// resolve, and placed under `deploy_to` -- the file windeployqt writes.
76+
std::vector<std::string> qt_languages;
7277
};
7378

7479
struct options {
@@ -131,6 +136,26 @@ inline std::string module_name(std::string m) {
131136
return m;
132137
}
133138

139+
// The translation catalog Qt ships a module's strings in: `qtbase` for the
140+
// modules of qtbase, the repository's name for the others. A module with no
141+
// catalog of its own has its strings in qtbase's.
142+
inline std::string catalog_of(const std::string& module) {
143+
static const std::vector<std::pair<std::string, std::vector<std::string>>> table = {
144+
{"qtdeclarative", {"Qml", "Quick", "QuickControls2", "QuickWidgets", "QuickDialogs2"}},
145+
{"qtmultimedia", {"Multimedia", "MultimediaWidgets"}},
146+
{"qtserialport", {"SerialPort"}},
147+
{"qtwebsockets", {"WebSockets"}},
148+
{"qtconnectivity", {"Bluetooth", "Nfc"}},
149+
{"qtlocation", {"Location"}},
150+
{"qtwebengine", {"WebEngineCore", "WebEngineWidgets", "WebEngineQuick"}},
151+
{"qt_help", {"Help"}},
152+
};
153+
for (auto const& [catalog, modules] : table)
154+
for (auto const& m : modules)
155+
if (m == module) return catalog;
156+
return "qtbase";
157+
}
158+
134159
// By index, as `mcpp.deps.vcpkg` lowers a triplet line: GCC 16 cannot inline a
135160
// non-const std::string iterator in a module unit.
136161
inline std::string upper(std::string s) {
@@ -579,6 +604,50 @@ inline bool compile(options opt = {}) {
579604
}
580605
}
581606

607+
// ── Qt's own translations ──
608+
if (!opt.i18n.qt_languages.empty()) {
609+
const std::string lconvert = detail::tool(sdks, "lconvert");
610+
const fs::path qmDir = opt.i18n.out_dir.empty() ? gen / "translations"
611+
: detail::absolute_from_root(opt.i18n.out_dir);
612+
std::vector<std::string> catalogs = {"qtbase"};
613+
for (auto const& m : opt.modules) {
614+
const std::string c = detail::catalog_of(detail::module_name(m));
615+
if (std::ranges::find(catalogs, c) == catalogs.end()) catalogs.push_back(c);
616+
}
617+
if (lconvert.empty()) {
618+
detail::warn(std::format("{}: `lconvert` not found under {}; it is part of qttools, and "
619+
"Qt's own translations are not placed.", who, generic(main)));
620+
} else {
621+
for (auto const& lang : opt.i18n.qt_languages) {
622+
std::vector<std::string> inputs;
623+
for (auto const& c : catalogs)
624+
for (auto const& r : sdks)
625+
if (const auto qm = r / "translations" / (c + "_" + lang + ".qm"); fs::is_regular_file(qm, ec)) {
626+
inputs.push_back(generic(qm));
627+
break;
628+
}
629+
if (inputs.empty()) {
630+
detail::warn(std::format("{}: the SDK has no Qt translation for '{}' (no "
631+
"translations/qtbase_{}.qm under {}).", who, lang, lang, generic(main)));
632+
continue;
633+
}
634+
const std::string name = "qt_" + lang + ".qm";
635+
if (!claim(name, fs::path(inputs.front()))) return false;
636+
const std::string out = generic(qmDir / name);
637+
const std::string id = "qt:lconvert:" + lang;
638+
const std::string desc = "LCONVERT " + name;
639+
mcpp::action a;
640+
a.id = id.c_str();
641+
a.role = mcpp::roles::source;
642+
a.description = desc.c_str();
643+
a.arg(lconvert.c_str()).arg("-o").arg(out.c_str());
644+
for (auto const& in : inputs) a.arg("-i").arg(in.c_str()).input(in.c_str());
645+
a.output(out.c_str()).submit();
646+
mcpp::deploy(out.c_str(), opt.i18n.deploy_to.c_str());
647+
}
648+
}
649+
}
650+
582651
// ── what Qt loads at run time ──
583652
for (auto const& dir : opt.deploy_plugins) {
584653
bool any = false;

‎tests/qt-consumer/build.mcpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@ int main() {
1010
// lupdate brings the `.ts` up to date with src/ before lrelease reads it.
1111
// The committed file is lupdate's own output, so a build leaves it as is.
1212
o.i18n.update_sources = true;
13+
// Qt's own strings in German, combined from the catalogs of the modules
14+
// linked into `translations/qt_de.qm`, as windeployqt writes it.
15+
o.i18n.qt_languages = {"de"};
1316
return mcpp::rules::qt::compile(o) ? 0 : 1;
1417
}

‎tests/qt-consumer/src/main.cpp‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,22 @@ int main(int argc, char** argv) {
4040
return 1;
4141
}
4242
QCoreApplication::installTranslator(&translator);
43-
const QString hello = QCoreApplication::translate("main", "hello");
44-
45-
std::printf("qt-consumer: signal %d, resource '%s', translation '%s', Qt %s\n",
46-
relay.seen, text.constData(), hello.toUtf8().constData(), qVersion());
47-
return relay.seen == 42 && text == "greetings from rcc" && hello == "hallo" ? 0 : 1;
43+
// Qt's own strings, from the qt_de.qm rules-qt combined: it loads only
44+
// when no catalog it depends on is missing.
45+
QTranslator qtTranslator;
46+
if (!qtTranslator.load("qt_de", dir)) {
47+
std::printf("qt-consumer: translations/qt_de.qm does not load\n");
48+
return 1;
49+
}
50+
QCoreApplication::installTranslator(&qtTranslator);
51+
const QString hello = QCoreApplication::translate("main", "hello");
52+
const QString cancel = QCoreApplication::translate("QProgressDialog", "Cancel");
53+
54+
std::printf("qt-consumer: signal %d, resource '%s', translation '%s', Qt %s, Qt's own '%s'\n",
55+
relay.seen, text.constData(), hello.toUtf8().constData(), qVersion(),
56+
cancel.toUtf8().constData());
57+
return relay.seen == 42 && text == "greetings from rcc" && hello == "hallo" &&
58+
cancel == "Abbrechen" ? 0 : 1;
4859
}
4960

5061
#include "main.moc"

0 commit comments

Comments
 (0)