diff --git a/autotests/klocalizedstringtest.cpp b/autotests/klocalizedstringtest.cpp index 1bfe551..3571cea 100644 --- a/autotests/klocalizedstringtest.cpp +++ b/autotests/klocalizedstringtest.cpp @@ -1,611 +1,633 @@ // krazy:excludeall=i18ncheckarg /* This file is part of the KDE libraries Copyright (C) 2006 Chusslove Illich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Tests explicitly use their own test catalogs. #undef TRANSLATION_DOMAIN #include "klocalizedstringtest.h" #include "klocalizedtranslator.h" #include #include #include #include #include #include #include #include #include #include void KLocalizedStringTest::initTestCase() { KLocalizedString::setApplicationDomain("ki18n-test"); m_hasFrench = true; m_hasCatalan = true; setlocale(LC_ALL, "ca_ES.utf8"); if (setlocale(LC_ALL, nullptr) != QByteArray("ca_ES.utf8")) { qDebug() << "Failed to set locale to ca_ES.utf8."; m_hasCatalan = false; } if (m_hasFrench) { setlocale(LC_ALL, "fr_FR.utf8"); if (setlocale(LC_ALL, nullptr) != QByteArray("fr_FR.utf8")) { qDebug() << "Failed to set locale to fr_FR.utf8."; m_hasFrench = false; } else { QLocale::setDefault(QLocale("fr_FR")); // the setlocale is "too late" for Qt that already has created the default QLocale, so set it manually } } if (m_hasFrench) { if (!m_tempDir.isValid()) { qDebug() << "Failed to create temporary directory for test data."; m_hasFrench = false; } } QDir dataDir(m_tempDir.path()); if (m_hasFrench) { m_hasFrench = compileCatalogs({QFINDTESTDATA("po/fr/ki18n-test.po"), QFINDTESTDATA("po/fr/ki18n-test-qt.po")}, dataDir, "fr"); } if (m_hasCatalan) { m_hasCatalan = compileCatalogs({QFINDTESTDATA("po/ca/ki18n-test.po")}, dataDir, "ca"); } if (m_hasFrench) { qputenv("XDG_DATA_DIRS", qgetenv("XDG_DATA_DIRS") + ":" + QFile::encodeName(dataDir.path())); // bind... dataDir.path() QStringList languages; languages.append("fr"); KLocalizedString::setLanguages(languages); } #if 0 // until locale system is ready if (m_hasFrench) { KLocale::global()->setLanguage(QStringList() << "fr" << "en_US"); } KLocale::global()->setThousandsSeparator(QLatin1String(",")); KLocale::global()->setDecimalSymbol(QLatin1String(".")); #endif } bool KLocalizedStringTest::compileCatalogs(const QStringList &testPoPaths, const QDir &dataDir, const QString &lang) { const QString lcMessages = QString("locale/%1/LC_MESSAGES").arg(lang); if (!dataDir.mkpath(lcMessages)) { qDebug() << "Failed to create locale subdirectory " "inside temporary directory."; return false; } QString msgfmt = QStandardPaths::findExecutable(QLatin1String("msgfmt")); if (msgfmt.isEmpty()) { qDebug() << "msgfmt(1) not found in path."; return false; } for (const QString &testPoPath : testPoPaths) { int pos_1 = testPoPath.lastIndexOf(QLatin1Char('/')); int pos_2 = testPoPath.lastIndexOf(QLatin1Char('.')); QString domain = testPoPath.mid(pos_1 + 1, pos_2 - pos_1 - 1); QString testMoPath; testMoPath = QString::fromLatin1("%1/%3/%2.mo") .arg(dataDir.path(), domain, lcMessages); QProcess process; QStringList arguments; arguments << testPoPath << QLatin1String("-o") << testMoPath; process.start(msgfmt, arguments); process.waitForFinished(10000); if (process.exitCode() != 0) { qDebug() << QString::fromLatin1("msgfmt(1) could not compile %1.") .arg(testPoPath); return false; } } return true; } void KLocalizedStringTest::correctSubs() { if (!m_hasFrench) { QSKIP("French test files not usable."); } // Warm up. QCOMPARE(i18n("Daisies, daisies"), QString("Daisies, daisies")); // Placeholder in the middle. QCOMPARE(i18n("Fault in %1 unit", QString("AE35")), QString("Fault in AE35 unit")); // Placeholder at the start. QCOMPARE(i18n("%1, Tycho Magnetic Anomaly 1", QString("TMA-1")), QString("TMA-1, Tycho Magnetic Anomaly 1")); // Placeholder at the end. QCOMPARE(i18n("...odd things happening at %1", QString("Clavius")), QString("...odd things happening at Clavius")); QCOMPARE(i18n("Group %1", 1), QString("Group 1")); // Two placeholders. QCOMPARE(i18n("%1 and %2", QString("Bowman"), QString("Poole")), QString("Bowman and Poole")); // Two placeholders in inverted order. QCOMPARE(i18n("%2 and %1", QString("Poole"), QString("Bowman")), QString("Bowman and Poole")); // % which is not of placeholder. QCOMPARE(i18n("It's going to go %1% failure in 72 hours.", 100), QString("It's going to go 100% failure in 72 hours.")); // Usual plural. QCOMPARE(i18np("%1 pod", "%1 pods", 1), QString("1 pod")); QCOMPARE(i18np("%1 pod", "%1 pods", 10), QString("10 pods")); // No plural-number in singular. QCOMPARE(i18np("A pod", "%1 pods", 1), QString("A pod")); QCOMPARE(i18np("A pod", "%1 pods", 10), QString("10 pods")); // No plural-number in singular or plural. QCOMPARE(i18np("A pod", "Few pods", 1), QString("A pod")); QCOMPARE(i18np("A pod", "Few pods", 10), QString("Few pods")); // First of two arguments as plural-number. QCOMPARE(i18np("A pod left on %2", "%1 pods left on %2", 1, QString("Discovery")), QString("A pod left on Discovery")); QCOMPARE(i18np("A pod left on %2", "%1 pods left on %2", 2, QString("Discovery")), QString("2 pods left on Discovery")); // Second of two arguments as plural-number. QCOMPARE(i18np("%1 has a pod left", "%1 has %2 pods left", QString("Discovery"), 1), QString("Discovery has a pod left")); QCOMPARE(i18np("%1 has a pod left", "%1 has %2 pods left", QString("Discovery"), 2), QString("Discovery has 2 pods left")); // No plural-number in singular or plural, but another argument present. QCOMPARE(i18np("A pod left on %2", "Some pods left on %2", 1, QString("Discovery")), QString("A pod left on Discovery")); QCOMPARE(i18np("A pod left on %2", "Some pods left on %2", 2, QString("Discovery")), QString("Some pods left on Discovery")); // Visual formatting. // FIXME: Needs much more tests. QCOMPARE(xi18n("E = mc^2"), QString("E = mc^2")); QCOMPARE(xi18n("E < mc^2"), QString("E < mc^2")); QCOMPARE(xi18n("E ? mc^2"), QString("E ? *mc^2*")); QCOMPARE(xi18n("E < mc^2"), QString("E < *mc^2*")); QCOMPARE(xi18nc("@label", "E < mc^2"), QString("E < *mc^2*")); QCOMPARE(xi18nc("@info", "E < mc^2"), QString("E < mc^2")); + QCOMPARE(xi18nc("@info:status", "E < mc^2"), + QString("E < *mc^2*")); + QCOMPARE(xi18nc("@info:progress", "E < mc^2"), + QString("E < *mc^2*")); + QCOMPARE(xi18nc("@info:tooltip", "E < mc^2"), + QString("E < mc^2")); + QCOMPARE(xi18nc("@info:shell", "E < mc^2"), + QString("E < *mc^2*")); QCOMPARE(xi18n("E = mc^2"), QString("E = mc^2")); QCOMPARE(xi18n("E = mc^2"), QString("E = mc^2")); + // with additional whitespace + QCOMPARE(xi18nc(" @info:progress ", "E < mc^2"), + QString("E < *mc^2*")); + QCOMPARE(xi18nc(" @info:tooltip ", "E < mc^2"), + QString("E < mc^2")); + QCOMPARE(xi18nc(" @info: progress ", "E < mc^2"), // not parsed as a cue + QString("E < mc^2")); + QCOMPARE(xi18nc(" @info: tooltip ", "E < mc^2"), // not parsed as a cue + QString("E < mc^2")); + + QTest::ignoreMessage(QtWarningMsg, "\"Unknown subcue ':doesnotexist' in UI marker in context {@info:doesnotexist}.\""); + QCOMPARE(xi18nc("@info:doesnotexist", "E < mc^2"), + QString("E < mc^2")); + // Number formatting. QCOMPARE(ki18n("%1").subs(42).toString(), QString("42")); QCOMPARE(ki18n("%1").subs(42, 5).toString(), QString(" 42")); QCOMPARE(ki18n("%1").subs(42, -5, 10, QChar('_')).toString(), QString("42___")); QCOMPARE(ki18n("%1").subs(4.2, 5, 'f', 2).toString(), QString(" 4,20")); } void KLocalizedStringTest::wrongSubs() { #ifndef NDEBUG // Too many arguments. QVERIFY(i18n("Europa", 1) != QString("Europa")); // Too few arguments. QVERIFY(i18n("%1, %2 and %3", QString("Hunter"), QString("Kimball")) != QString("Hunter, Kimball and %3")); // Gaps in placheholder numbering. QVERIFY(ki18n("Beyond the %2").subs("infinity").toString() != QString("Beyond the infinity")); // Plural argument not supplied. QVERIFY(ki18np("1 pod", "%1 pods").toString() != QString("1 pod")); QVERIFY(ki18np("1 pod", "%1 pods").toString() != QString("%1 pods")); #endif } void KLocalizedStringTest::semanticTags() { KLocalizedString::setLanguages({"en"}); // QCOMPARE(xi18nc("@action:inmenu", "Open with %1", "Okteta"), QString("Open with Okteta")); QCOMPARE(xi18nc("@info", "Open with %1", "Okteta"), QString("Open with Okteta")); // QCOMPARE(xi18nc("@info:whatsthis", "You can try the following snippet:" "\\begin{equation}\n" " C_{x_i} = \\frac{C_z^2}{e \\pi \\lambda}\n" "\\end{equation}" ""), QString("You can try the following snippet:\n\n
"
                     "\\begin{equation}\n"
                     "  C_{x_i} = \\frac{C_z^2}{e \\pi \\lambda}\n"
                     "\\end{equation}"
                     "
")); // QCOMPARE(xi18nc("@info", "This will call %1 internally.", "true"), QString("This will call true internally.")); QCOMPARE(xi18nc("@info", "Consult man entry for %1", "true", 1), QString("Consult man entry for true(1)")); // QCOMPARE(xi18nc("@info", "Send bug reports to %1.", "konqi@kde.org"), QString("Send bug reports to <konqi@kde.org>.")); QCOMPARE(xi18nc("@info", "Send praises to %2.", "konqi@kde.org", "Konqi"), QString("Send praises to Konqi.")); // QCOMPARE(xi18nc("@info:progress", "Checking feedback circuits..."), QString("Checking *feedback* circuits...")); QCOMPARE(xi18nc("@info:progress", "Checking feedback circuits..."), QString("Checking **feedback** circuits...")); // QCOMPARE(xi18nc("@info", "Assure that your PATH is properly set."), QString("Assure that your $PATH is properly set.")); // QCOMPARE(xi18nc("@info", "Cannot read %1.", "data.dat"), QString("Cannot read data.dat.")); // TODO: is nested really wanted? #ifndef Q_OS_WIN QString homeFooRc("$HOME/.foorc does not exist."); #else //TODO $HOME -> %HOME% ? QString homeFooRc("$HOME\\.foorc does not exist."); #endif QCOMPARE(xi18nc("@info", "HOME/.foorc does not exist."), homeFooRc); // QCOMPARE(xi18nc("@info:tooltip", "Execute svn merge on selected revisions."), QString("Execute svn merge on selected revisions.")); // QCOMPARE(xi18nc("@info:whatsthis", "If you make a mistake, click Reset to start again."), QString("If you make a mistake, click Reset to start again.")); QCOMPARE(xi18nc("@info:whatsthis", "The line colors can be changed under Settings->Visuals."), QString("The line colors can be changed under Settings->Visuals.")); // QCOMPARE(xi18nc("@info:tooltip", "Go to %1 website.", "http://kde.org/"), QString("Go to http://kde.org/ website.")); QCOMPARE(xi18nc("@info:tooltip", "Go to %2.", "http://kde.org/", "the KDE website"), QString("Go to the KDE website.")); // QCOMPARE(xi18nc("@info", "The fortune cookie says: %1", "Nothing"), QString("The fortune cookie says: Nothing")); // #ifndef Q_OS_WIN QString deleteEtcPasswd("Do you really want to delete:
/etc/passwd"); #else QString deleteEtcPasswd("Do you really want to delete:
\\etc\\passwd"); #endif QCOMPARE(xi18nc("@info", "Do you really want to delete:%1", "/etc/passwd"), deleteEtcPasswd); //check within filename doesn't break (Windows path separators) #ifndef Q_OS_WIN QString filenameWithNewline("/filename/with
/newline
"); #else QString filenameWithNewline("\\filename\\with
\\newline
"); #endif QCOMPARE(xi18nc("@info", "/filename/with/newline"), filenameWithNewline); // QEXPECT_FAIL("", "what happened to ? TODO.", Continue); QCOMPARE(xi18nc("@info:progress", "Connecting to %1...", 22), QString("Connecting to 22")); QCOMPARE(xi18nc("@info", "Replace name with your name."), QString("Replace <name> with your name.")); QCOMPARE(xi18nc("@item:inlistbox", "All images"), QString("")); // QCOMPARE(xi18nc("@info", "Apply color scheme %1?", "XXX"), QString("Apply color scheme “XXX”?")); QCOMPARE(xi18nc("@info:whatsthis", "Cycle through layouts using Alt+Space."), QString("Cycle through layouts using Alt+Space.")); // QCOMPARE(xi18nc("@info", "Probably the best known of all duck species is the Mallard. " "It breeds throughout the temperate areas around the world. " "Most domestic ducks are derived from Mallard."), QString("Probably the best known of all duck species is the Mallard. " "It breeds throughout the temperate areas around the world. " "Note: Most domestic ducks are derived from Mallard.")); QCOMPARE(xi18nc("@info", "Most domestic ducks are derived from Mallard."), QString("Trivia: Most domestic ducks are derived from Mallard.")); // QCOMPARE(xi18nc("@info", "Really delete this key?" "This cannot be undone."), QString("Really delete this key?" "Warning: This cannot be undone.")); QCOMPARE(xi18nc("@info", "This cannot be undone."), QString("Danger: This cannot be undone.")); } void KLocalizedStringTest::removeAcceleratorMarker() { // No accelerator marker. QCOMPARE(KLocalizedString::removeAcceleratorMarker(QString()), QString()); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar"), QString("Foo bar")); // Run of the mill. QCOMPARE(KLocalizedString::removeAcceleratorMarker("&Foo bar"), QString("Foo bar")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo &bar"), QString("Foo bar")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo b&ar"), QString("Foo bar")); // - presence of escaped ampersands QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo && Bar"), QString("Foo & Bar")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo && &Bar"), QString("Foo & Bar")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("&Foo && Bar"), QString("Foo & Bar")); // CJK-style markers. QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar (&F)"), QString("Foo bar")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("(&F) Foo bar"), QString("Foo bar")); // - interpunction after/before parenthesis still qualifies CJK marker QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar (&F):"), QString("Foo bar:")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar (&F)..."), QString("Foo bar...")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("...(&F) foo bar"), QString("...foo bar")); // - alphanumerics around parenthesis disqualify CJK marker QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo (&F) bar"), QString("Foo (F) bar")); // - something removed raw ampersands, leaving dangling reduced CJK markers. // Remove reduced markers only if CJK characters are found in the string. QCOMPARE(KLocalizedString::removeAcceleratorMarker(QString::fromUtf8("Foo bar (F)")), QString::fromUtf8("Foo bar (F)")); QCOMPARE(KLocalizedString::removeAcceleratorMarker(QString::fromUtf8("印刷(P)...")), QString::fromUtf8("印刷...")); // Shady cases, where ampersand is obviously not a marker // and should have been escaped, but it was not. QCOMPARE(KLocalizedString::removeAcceleratorMarker("&"), QString("&")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar &"), QString("Foo bar &")); QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo & Bar"), QString("Foo & Bar")); } void KLocalizedStringTest::miscMethods() { KLocalizedString k; QVERIFY(k.isEmpty()); if (m_hasFrench) { QSet availableLanguages; availableLanguages.insert("fr"); availableLanguages.insert("en_US"); if (m_hasCatalan) { availableLanguages.insert("ca"); } QCOMPARE(KLocalizedString::availableApplicationTranslations(), availableLanguages); } } // Same as translateToFrench, but using libintl directly (bindtextdomain+dgettext). // Useful for debugging. This changes global state, though, so it's skipped by default. void KLocalizedStringTest::translateToFrenchLowlevel() { if (!m_hasFrench) { QSKIP("French test files not usable."); } QSKIP("Skipped by default to avoid changing global state."); // fr_FR locale was set by initTestCase already. if (QFile::exists("/usr/share/locale/fr/LC_MESSAGES/ki18n-test.mo")) { bindtextdomain("ki18n-test", "/usr/share/locale"); QCOMPARE(QString::fromUtf8(dgettext("ki18n-test", "Loadable modules")), QString::fromUtf8("Modules chargeables")); } } void KLocalizedStringTest::translateToFrench() { if (!m_hasFrench) { QSKIP("French test files not usable."); } QCOMPARE(i18n("Loadable modules"), QString::fromUtf8("Modules chargeables")); QCOMPARE(i18n("Job"), QString::fromUtf8("Tâche")); } #if KI18N_ENABLE_DEPRECATED_SINCE(5, 0) void KLocalizedStringTest::translateQt() { KLocalizedString::insertQtDomain("ki18n-test-qt"); QString result = KLocalizedString::translateQt("QPrintPreviewDialog", "Landscape", nullptr, 0); // When we use the default language, translateQt returns an empty string. QString expected = m_hasFrench ? QString("Paysage") : QString(); QCOMPARE(result, expected); #if 0 // KLocalizedString no longer does anything with QTranslator, this needed? result = QCoreApplication::translate("QPrintPreviewDialog", "Landscape"); QString expected2 = m_hasFrench ? QString("Paysage") : QString("Landscape"); QCOMPARE(result, expected2); #endif #if 0 // translateRaw no longer public, this needed? // So let's use translateRaw instead for the threaded test QString lang; KLocale::global()->translateRaw("Landscape", &lang, &result); QCOMPARE(lang, m_hasFrench ? QString("fr") : QString("en_US")); QCOMPARE(result, m_hasFrench ? QString("Paysage") : QString("Landscape")); #endif KLocalizedString::removeQtDomain("ki18n-test-qt"); } #endif void KLocalizedStringTest::testLocalizedTranslator() { if (!m_hasFrench) { QSKIP("French test files not usable."); } QScopedPointer translator(new KLocalizedTranslator()); QCoreApplication *app = QCoreApplication::instance(); app->installTranslator(translator.data()); // no translation domain and no context QCOMPARE(app->translate("foo", "Job"), QStringLiteral("Job")); // adding the translation domain still lacks the context translator->setTranslationDomain(QStringLiteral("ki18n-test")); QCOMPARE(app->translate("foo", "Job"), QStringLiteral("Job")); translator->addContextToMonitor(QStringLiteral("foo")); // now it should translate QCOMPARE(app->translate("foo", "Job"), QStringLiteral("Tâche")); // other context shouldn't translate QCOMPARE(app->translate("bar", "Job"), QStringLiteral("Job")); // with a mismatching disambiguation it shouldn't translate QCOMPARE(app->translate("foo", "Job", "bar"), QStringLiteral("Job")); } void KLocalizedStringTest::addCustomDomainPath() { if (!m_hasFrench) { QSKIP("French test files not usable."); } QTemporaryDir dir; compileCatalogs({QFINDTESTDATA("po/fr/ki18n-test2.po")}, dir.path(), "fr"); KLocalizedString::addDomainLocaleDir("ki18n-test2", dir.path() + "/locale"); QSet expectedAvailableTranslations({"en_US", "fr"}); QCOMPARE(KLocalizedString::availableDomainTranslations("ki18n-test2"), expectedAvailableTranslations); QCOMPARE(i18nd("ki18n-test2", "Cheese"), QString::fromUtf8("Fromage")); } void KLocalizedStringTest::multipleLanguages() { if (!m_hasFrench || !m_hasCatalan) { QSKIP("French or Catalan test files not usable."); } KLocalizedString::setLanguages({"ca"}); QCOMPARE(i18n("Job"), QString::fromUtf8("Job")); // This is not the actual catalan translation but who cares KLocalizedString::setLanguages({"fr"}); QCOMPARE(i18n("Job"), QString::fromUtf8("Tâche")); KLocalizedString::setLanguages({"ca", "fr"}); QCOMPARE(i18n("Job"), QString::fromUtf8("Job")); // This is not the actual catalan translation but who cares KLocalizedString::setLanguages({"ca"}); QCOMPARE(i18n("Loadable modules"), QString::fromUtf8("Loadable modules")); // The po doesn't have a translation so we get the English text KLocalizedString::setLanguages({"fr"}); QCOMPARE(i18n("Loadable modules"), QString::fromUtf8("Modules chargeables")); KLocalizedString::setLanguages({"ca", "fr"}); QCOMPARE(i18n("Loadable modules"), QString::fromUtf8("Modules chargeables")); // The Catalan po doesn't have a translation so we get the English text } void KLocalizedStringTest::untranslatedText() { if (!m_hasFrench) { QSKIP("French test files not usable."); } KLocalizedString s = ki18n("Job"); KLocalizedString::setLanguages({"fr"}); QCOMPARE(s.untranslatedText(), "Job"); QCOMPARE(s.toString(), QString::fromUtf8("Tâche")); QCOMPARE(s.untranslatedText(), "Job"); } #include #include #include void KLocalizedStringTest::testThreads() { QThreadPool::globalInstance()->setMaxThreadCount(10); QFutureSynchronizer sync; sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::correctSubs)); sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::correctSubs)); sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::correctSubs)); #if KI18N_ENABLE_DEPRECATED_SINCE(5, 0) sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::translateQt)); sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::translateQt)); #endif sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::translateToFrench)); sync.waitForFinished(); QThreadPool::globalInstance()->setMaxThreadCount(1); // delete those threads } QTEST_MAIN(KLocalizedStringTest)