// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include #ifdef Q_OS_ANDROID #include #endif class tst_QTranslator : public QObject { Q_OBJECT public: tst_QTranslator(); protected: bool eventFilter(QObject *obj, QEvent *event) override; private slots: void initTestCase(); void init(); void load_data(); void load(); void loadLocale(); void threadLoad(); void testLanguageChange(); void plural(); void translate_qm_file_generated_with_msgfmt(); void loadDirectory(); void dependencies(); void translationInThreadWhileInstallingTranslator(); private: int languageChangeEventCounter; QSharedPointer dataDir; }; tst_QTranslator::tst_QTranslator() : languageChangeEventCounter(0) { qApp->installEventFilter(this); } void tst_QTranslator::initTestCase() { dataDir = QEXTRACTTESTDATA(QStringLiteral("/tst_qtranslator")); QVERIFY2(!dataDir.isNull(), qPrintable("Could not extract test data")); } void tst_QTranslator::init() { QVERIFY2(QDir::setCurrent(dataDir->path()), qPrintable("Could not chdir to " + dataDir->path())); } bool tst_QTranslator::eventFilter(QObject *, QEvent *event) { if (event->type() == QEvent::LanguageChange) ++languageChangeEventCounter; return false; } void tst_QTranslator::load_data() { QTest::addColumn("filepath"); QTest::addColumn("isEmpty"); QTest::addColumn("translation"); QTest::addColumn("language"); QTest::newRow("hellotr_la") << "hellotr_la.qm" << false << "Hallo Welt!" << "de"; QTest::newRow("hellotr_empty") << "hellotr_empty.qm" << true << "" << ""; } void tst_QTranslator::load() { QFETCH(QString, filepath); QFETCH(bool, isEmpty); QFETCH(QString, translation); QFETCH(QString, language); { QTranslator tor; QVERIFY(tor.load(QFileInfo(filepath).baseName())); QCOMPARE(tor.isEmpty(), isEmpty); QCOMPARE(tor.translate("QPushButton", "Hello world!"), translation); QCOMPARE(tor.filePath(), filepath); QCOMPARE(tor.language(), language); } { QFile file(filepath); QVERIFY(file.open(QFile::ReadOnly)); QByteArray data = file.readAll(); QTranslator tor; QVERIFY(tor.load((const uchar *)data.constData(), data.length())); QCOMPARE(tor.isEmpty(), isEmpty); QCOMPARE(tor.translate("QPushButton", "Hello world!"), translation); QCOMPARE(tor.filePath(), ""); QCOMPARE(tor.language(), language); } { QTranslator tor; QString path = QString(":/tst_qtranslator/%1").arg(filepath); QVERIFY(tor.load(path)); QCOMPARE(tor.isEmpty(), isEmpty); QCOMPARE(tor.translate("QPushButton", "Hello world!"), translation); QCOMPARE(tor.filePath(), path); QCOMPARE(tor.language(), language); } } void tst_QTranslator::loadLocale() { QLocale locale; auto localeName = locale.uiLanguages(QLocale::TagSeparator::Underscore).value(0); if (localeName.isEmpty()) QSKIP("This test requires at least one available UI language."); QByteArray ba; { QFile file(":/tst_qtranslator/hellotr_la.qm"); QVERIFY2(file.open(QFile::ReadOnly), qPrintable(file.errorString())); ba = file.readAll(); QVERIFY(!ba.isEmpty()); } QTemporaryDir dir; QVERIFY(dir.isValid()); auto path = dir.path(); QFile file(path + "/dummy"); QVERIFY2(file.open(QFile::WriteOnly), qPrintable(file.errorString())); QCOMPARE(file.write(ba), ba.size()); file.close(); /* Test the following order: /tmp/tmpDir/foo-en_US.qm /tmp/tmpDir/foo-en_US /tmp/tmpDir/foo-en.qm /tmp/tmpDir/foo-en /tmp/tmpDir/foo.qm /tmp/tmpDir/foo- /tmp/tmpDir/foo */ QStringList files; while (true) { files.append(path + "/foo-" + localeName + ".qm"); QVERIFY2(file.copy(files.last()), qPrintable(file.errorString())); files.append(path + "/foo-" + localeName); QVERIFY2(file.copy(files.last()), qPrintable(file.errorString())); int rightmost = localeName.lastIndexOf(QLatin1Char('_')); if (rightmost <= 0) break; localeName.truncate(rightmost); } files.append(path + "/foo.qm"); QVERIFY2(file.copy(files.last()), qPrintable(file.errorString())); files.append(path + "/foo-"); QVERIFY2(file.copy(files.last()), qPrintable(file.errorString())); files.append(path + "/foo"); QVERIFY2(file.rename(files.last()), qPrintable(file.errorString())); QTranslator tor; for (const auto &filePath : files) { QVERIFY(tor.load(locale, "foo", "-", path, ".qm")); QCOMPARE(tor.filePath(), filePath); QVERIFY2(file.remove(filePath), qPrintable(file.errorString())); } } class TranslatorThread : public QThread { void run() override { QTranslator tor( 0 ); (void)tor.load("hellotr_la"); if (tor.isEmpty()) qFatal("Could not load translation"); if (tor.translate("QPushButton", "Hello world!") != QLatin1String("Hallo Welt!")) qFatal("Test string was not translated correctlys"); } }; void tst_QTranslator::threadLoad() { TranslatorThread thread; thread.start(); QVERIFY(thread.wait(10 * 1000)); } void tst_QTranslator::testLanguageChange() { languageChangeEventCounter = 0; QTranslator *tor = new QTranslator; QVERIFY(tor->load("hellotr_la.qm")); qApp->sendPostedEvents(); qApp->sendPostedEvents(); QCOMPARE(languageChangeEventCounter, 0); QVERIFY(!tor->load("doesn't exist, same as clearing")); qApp->sendPostedEvents(); qApp->sendPostedEvents(); QCOMPARE(languageChangeEventCounter, 0); QVERIFY(tor->load("hellotr_la.qm")); qApp->sendPostedEvents(); qApp->sendPostedEvents(); QCOMPARE(languageChangeEventCounter, 0); qApp->installTranslator(tor); qApp->sendPostedEvents(); qApp->sendPostedEvents(); QCOMPARE(languageChangeEventCounter, 1); QVERIFY(!tor->load("doesn't exist, same as clearing")); qApp->sendPostedEvents(); qApp->sendPostedEvents(); QCOMPARE(languageChangeEventCounter, 2); QVERIFY(tor->load("hellotr_la.qm")); qApp->sendPostedEvents(); qApp->sendPostedEvents(); QCOMPARE(languageChangeEventCounter, 3); qApp->removeTranslator(tor); qApp->sendPostedEvents(); qApp->sendPostedEvents(); QCOMPARE(languageChangeEventCounter, 4); QVERIFY(!tor->load("doesn't exist, same as clearing")); qApp->sendPostedEvents(); qApp->sendPostedEvents(); QCOMPARE(languageChangeEventCounter, 4); qApp->installTranslator(tor); qApp->sendPostedEvents(); qApp->sendPostedEvents(); QCOMPARE(languageChangeEventCounter, 4); QVERIFY(tor->load("hellotr_la.qm")); qApp->sendPostedEvents(); qApp->sendPostedEvents(); QCOMPARE(languageChangeEventCounter, 5); delete tor; tor = 0; qApp->sendPostedEvents(); qApp->sendPostedEvents(); QCOMPARE(languageChangeEventCounter, 6); } void tst_QTranslator::plural() { QTranslator tor( 0 ); QVERIFY(tor.load("hellotr_la")); QVERIFY(!tor.isEmpty()); QCoreApplication::installTranslator(&tor); QCOMPARE(QCoreApplication::translate("QPushButton", "Hello %n world(s)!", 0, 0), QLatin1String("Hallo 0 Welten!")); QCOMPARE(QCoreApplication::translate("QPushButton", "Hello %n world(s)!", 0, 1), QLatin1String("Hallo 1 Welt!")); QCOMPARE(QCoreApplication::translate("QPushButton", "Hello %n world(s)!", 0, 2), QLatin1String("Hallo 2 Welten!")); } void tst_QTranslator::translate_qm_file_generated_with_msgfmt() { QTranslator translator; QVERIFY(translator.load("msgfmt_from_po")); qApp->installTranslator(&translator); QCOMPARE(QCoreApplication::translate("", "Intro"), QLatin1String("Einleitung")); // The file is converted from a po file, thus it does not have any context info. // The following should then not be translated QCOMPARE(QCoreApplication::translate("contekst", "Intro"), QLatin1String("Intro")); QCOMPARE(QCoreApplication::translate("contekst", "Intro\0\0"), QLatin1String("Intro")); QCOMPARE(QCoreApplication::translate("contekst", "Intro\0x"), QLatin1String("Intro")); QCOMPARE(QCoreApplication::translate("", "Intro\0\0"), QLatin1String("Einleitung")); QCOMPARE(QCoreApplication::translate("", "Intro\0x"), QLatin1String("Einleitung")); qApp->removeTranslator(&translator); } void tst_QTranslator::loadDirectory() { QString current_base = QDir::current().dirName(); QVERIFY(QFileInfo("../" + current_base).isDir()); QTranslator tor; QVERIFY(!tor.load(current_base, "..")); QVERIFY(tor.isEmpty()); } void tst_QTranslator::dependencies() { { // load QTranslator tor; QVERIFY(tor.load("dependencies_la")); QVERIFY(!tor.isEmpty()); QCOMPARE(tor.translate("QPushButton", "Hello world!"), QLatin1String("Hallo Welt!")); // plural QCoreApplication::installTranslator(&tor); QCOMPARE(QCoreApplication::translate("QPushButton", "Hello %n world(s)!", 0, 0), QLatin1String("Hallo 0 Welten!")); QCOMPARE(QCoreApplication::translate("QPushButton", "Hello %n world(s)!", 0, 1), QLatin1String("Hallo 1 Welt!")); QCOMPARE(QCoreApplication::translate("QPushButton", "Hello %n world(s)!", 0, 2), QLatin1String("Hallo 2 Welten!")); // pick up translation from the file with dependencies QCOMPARE(tor.translate("QPushButton", "It's a small world"), QLatin1String("Es ist eine kleine Welt")); } { QTranslator tor( 0 ); QFile file("dependencies_la.qm"); QVERIFY(file.open(QFile::ReadOnly)); QByteArray data = file.readAll(); QVERIFY(tor.load((const uchar *)data.constData(), data.length())); QVERIFY(!tor.isEmpty()); QCOMPARE(tor.translate("QPushButton", "Hello world!"), QLatin1String("Hallo Welt!")); } { // Test resolution of paths relative to main file const QString absoluteFile = QFileInfo("dependencies_la").absoluteFilePath(); QDir::setCurrent(QDir::tempPath()); QTranslator tor; QVERIFY(tor.load(absoluteFile)); QVERIFY(!tor.isEmpty()); } } struct TranslateThread : public QThread { bool ok = false; QAtomicInt terminate; QMutex startupLock; QWaitCondition runningCondition; void run() override { bool startSignalled = false; while (terminate.loadRelaxed() == 0) { const QString result = QCoreApplication::translate("QPushButton", "Hello %n world(s)!", 0, 0); if (!startSignalled) { QMutexLocker startupLocker(&startupLock); runningCondition.wakeAll(); startSignalled = true; } ok = (result == QLatin1String("Hallo 0 Welten!")) || (result == QLatin1String("Hello 0 world(s)!")); if (!ok) break; } } }; void tst_QTranslator::translationInThreadWhileInstallingTranslator() { TranslateThread thread; QMutexLocker startupLocker(&thread.startupLock); thread.start(); thread.runningCondition.wait(&thread.startupLock); QTranslator tor; QVERIFY(tor.load("hellotr_la")); QVERIFY(QCoreApplication::installTranslator(&tor)); ++thread.terminate; QVERIFY(thread.wait()); QVERIFY(thread.ok); } QTEST_MAIN(tst_QTranslator) #include "tst_qtranslator.moc"