diff --git a/autotests/klocalizedstringtest.cpp b/autotests/klocalizedstringtest.cpp index b29f0a5..cdd0ad5 100644 --- a/autotests/klocalizedstringtest.cpp +++ b/autotests/klocalizedstringtest.cpp @@ -1,537 +1,537 @@ // 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 void KLocalizedStringTest::initTestCase() { KLocalizedString::setApplicationDomain("ki18n-test"); m_hasFrench = true; if (m_hasFrench) { setlocale(LC_ALL, "fr_FR.utf8"); - if (setlocale(LC_ALL, NULL) != QByteArray("fr_FR.utf8")) { + if (setlocale(LC_ALL, nullptr) != QByteArray("fr_FR.utf8")) { qDebug() << "Failed to set locale to fr_FR.utf8."; m_hasFrench = false; } } 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(dataDir); } 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 QDir &dataDir) { if (!dataDir.mkpath("locale/fr/LC_MESSAGES")) { 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; } QStringList testPoPaths; testPoPaths << QFINDTESTDATA("po/fr/ki18n-test.po"); testPoPaths << QFINDTESTDATA("po/fr/ki18n-test-qt.po"); foreach (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/locale/fr/LC_MESSAGES/%2.mo") .arg(dataDir.path(), domain); 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() { // 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(xi18n("E = mc^2"), QString("E = mc^2")); QCOMPARE(xi18n("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() { // 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"); 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")); } void KLocalizedStringTest::translateQt() { KLocalizedString::insertQtDomain("ki18n-test-qt"); - QString result = KLocalizedString::translateQt("QPrintPreviewDialog", "Landscape", 0, 0); + 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"); } 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")); } #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)); sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::translateQt)); sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::translateQt)); sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::translateToFrench)); sync.waitForFinished(); QThreadPool::globalInstance()->setMaxThreadCount(1); // delete those threads } QTEST_MAIN(KLocalizedStringTest) diff --git a/autotests/ktranscriptcleantest.cpp b/autotests/ktranscriptcleantest.cpp index 851903f..a748afe 100644 --- a/autotests/ktranscriptcleantest.cpp +++ b/autotests/ktranscriptcleantest.cpp @@ -1,99 +1,99 @@ /* Copyright 2014 Kevin Krammer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include #include #include QTEST_MAIN(KTranscriptCleanTest) KTranscriptCleanTest::KTranscriptCleanTest() - : m_transcript(0) + : m_transcript(nullptr) { } void KTranscriptCleanTest::init() { m_transcript = autotestCreateKTranscriptImp(); } void KTranscriptCleanTest::cleanup() { autotestDestroyKTranscriptImp(); - m_transcript = 0; + m_transcript = nullptr; } void KTranscriptCleanTest::test_data() { QTest::addColumn("argv"); QTest::addColumn("fallsBack"); QTest::addColumn("expected"); // Example test case, replace with first clean-slate test QTest::newRow("test_basic") << (QVariantList() << "test_basic" << "foo") << false << "foo bar"; } void KTranscriptCleanTest::test() { QFETCH(QVariantList, argv); QFETCH(bool, fallsBack); QFETCH(QString, expected); QString language = "fr"; QString country = "fr"; QString msgctxt = "a-context"; QHash dynamicContext; dynamicContext.insert("origin", "neverwhere"); QString msgid = "source-text"; QStringList subs; subs << "10" << "qwyx"; QList values; values << 10 << "qwyx"; QString ordinaryTranslation = "translated-text"; QString testJs = QFINDTESTDATA("test.js"); QList modules; modules << (QStringList() << testJs << language); QString error; bool fallback; QString result = m_transcript->eval( argv, language, country, msgctxt, dynamicContext, msgid, subs, values, ordinaryTranslation, modules, error, fallback); if (!error.isEmpty()) { QFAIL(qPrintable(error)); } if (!fallsBack) { QVERIFY(!fallback); QCOMPARE(result, expected); } else { QVERIFY(fallback); } } diff --git a/autotests/ktranscripttest.cpp b/autotests/ktranscripttest.cpp index 981f51f..ccfc3af 100644 --- a/autotests/ktranscripttest.cpp +++ b/autotests/ktranscripttest.cpp @@ -1,234 +1,234 @@ /* Copyright 2013 Aurélien Gâteau Copyright (C) 2014 Chusslove Illich Copyright (C) 2014 Kevin Krammer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "ktranscripttest.h" #include "testhelpers.h" #include #include #include QTEST_MAIN(KTranscriptTest) extern "C" { typedef KTranscript *(*InitFunc)(); } KTranscriptTest::KTranscriptTest() - : m_transcript(0) + : m_transcript(nullptr) { } void KTranscriptTest::initTestCase() { QVERIFY2(deployTestConfig(), "Could not deploy test ktranscript.ini"); QString pluginPath = QStringLiteral(KTRANSCRIPT_PATH); QVERIFY2(QFile::exists(pluginPath), "Could not find ktranscript plugin"); m_library.setFileName(pluginPath); QVERIFY(m_library.load()); InitFunc initf = (InitFunc) m_library.resolve("load_transcript"); QVERIFY(initf); m_transcript = initf(); QVERIFY(m_transcript); } void KTranscriptTest::cleanupTestCase() { QVERIFY2(removeTestConfig(), "Could not remove test ktranscript.ini"); } void KTranscriptTest::test_data() { QTest::addColumn("argv"); QTest::addColumn("fallsBack"); QTest::addColumn("expected"); QTest::newRow("test_basic") << (QVariantList() << "test_basic" << "foo") << false << "foo bar"; QTest::newRow("test_unicode") << (QVariantList() << "test_unicode" << "čгσィ九") << false << "čгσィ九 фу"; QTest::newRow("test_hascall") << (QVariantList() << "test_hascall" << "test_basic") << false << "yes"; QTest::newRow("test_acall") << (QVariantList() << "test_acall" << "test_basic" << "qwyx") << false << "qwyx bar"; QTest::newRow("test_load") << (QVariantList() << "test_load") << false << "foo blurb"; QTest::newRow("test_fallback") << (QVariantList() << "test_fallback") << true << ""; QTest::newRow("test_msgid") << (QVariantList() << "test_msgid") << false << "source-text"; QTest::newRow("test_msgtrf") << (QVariantList() << "test_msgtrf") << false << "translated-text"; QTest::newRow("test_msgctxt") << (QVariantList() << "test_msgctxt") << false << "a-context"; QTest::newRow("test_msgkey") << (QVariantList() << "test_msgkey") << false << "a-context|source-text"; QTest::newRow("test_nsubs") << (QVariantList() << "test_nsubs") << false << "2"; QTest::newRow("test_subs") << (QVariantList() << "test_subs" << 1) << false << "qwyx"; QTest::newRow("test_vals") << (QVariantList() << "test_vals" << 0 << 5) << false << "50"; QTest::newRow("test_dynctxt") << (QVariantList() << "test_dynctxt" << "origin") << false << "neverwhere"; QTest::newRow("test_dbgputs") << (QVariantList() << "test_dbgputs") << false << "debugged"; QTest::newRow("test_warnputs") << (QVariantList() << "test_warnputs") << false << "warned"; QTest::newRow("test_setcallForall") << (QVariantList() << "test_setcallForall") << false << "done"; QTest::newRow("test_toUpperFirst") << (QVariantList() << "test_toUpperFirst" << "...123 foo") << false << "...123 Foo"; QTest::newRow("test_toUpperFirst_unicode") << (QVariantList() << "test_toUpperFirst" << "...123 фу") << false << "...123 Фу"; QTest::newRow("test_toLowerFirst") << (QVariantList() << "test_toLowerFirst" << "...123 FOO") << false << "...123 fOO"; QTest::newRow("test_loadProps") << (QVariantList() << "test_loadProps" << "cities") << false << "loaded"; QTest::newRow("test_getProp") << (QVariantList() << "test_getProp" << "cities" << "Athens" << "gen") << false << "Atine"; QTest::newRow("test_setProp") << (QVariantList() << "test_setProp" << "Oslo" << "dat" << "Oslou") << false << "Oslou"; QTest::newRow("test_normKey") << (QVariantList() << "test_normKey" << "Some &Thing") << false << "something"; QTest::newRow("test_getConfString") << (QVariantList() << "test_getConfString" << "StringKey") << false << "StringValue"; QTest::newRow("test_getConfStringWithDefault") << (QVariantList() << "test_getConfStringWithDefault" << "NoSuchKey" << "DefaultValue") << false << "DefaultValue"; QTest::newRow("test_getConfBool") << (QVariantList() << "test_getConfBool" << "BoolKey") << false << "true"; QTest::newRow("test_getConfBoolWithDefault") << (QVariantList() << "test_getConfBoolWithDefault" << "NoSuchKey" << true) << false << "true"; QTest::newRow("test_getConfNumber") << (QVariantList() << "test_getConfNumber" << "NumberKey") << false << "12345"; QTest::newRow("test_getConfNumberWithDefault") << (QVariantList() << "test_getConfNumberWithDefault" << "NoSuchKey" << 54321) << false << "54321"; } void KTranscriptTest::test() { QFETCH(QVariantList, argv); QFETCH(bool, fallsBack); QFETCH(QString, expected); QString language = "fr"; QString country = "fr"; QString msgctxt = "a-context"; QHash dynamicContext; dynamicContext.insert("origin", "neverwhere"); QString msgid = "source-text"; QStringList subs; subs << "10" << "qwyx"; QList values; values << 10 << "qwyx"; QString ordinaryTranslation = "translated-text"; QString testJs = QFINDTESTDATA("test.js"); QList modules; modules << (QStringList() << testJs << language); QString error; bool fallback; QString result = m_transcript->eval( argv, language, country, msgctxt, dynamicContext, msgid, subs, values, ordinaryTranslation, modules, error, fallback); if (!error.isEmpty()) { QFAIL(qPrintable(error)); } if (!fallsBack) { QVERIFY(!fallback); QCOMPARE(result, expected); } else { QVERIFY(fallback); } } diff --git a/src/gettext.h b/src/gettext.h index fd15bda..41e2c73 100644 --- a/src/gettext.h +++ b/src/gettext.h @@ -1,198 +1,198 @@ /* Convenience header for conditional use of GNU . Copyright (C) 1995-1998, 2000-2002, 2004-2006 Free Software Foundation, Inc. This program 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, or (at your option) any later version. This program 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 program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _LIBGETTEXT_H #define _LIBGETTEXT_H 1 /* Get declarations of GNU message catalog functions. */ # include // libintl.h redefines inline which causes MSVC to abort compilation with the message // fatal error C1189: #error : The C++ Standard Library forbids macroizing keywords #undef inline /* You can set the DEFAULT_TEXT_DOMAIN macro to specify the domain used by the gettext() and ngettext() macros. This is an alternative to calling textdomain(), and is useful for libraries. */ # ifdef DEFAULT_TEXT_DOMAIN # undef gettext # define gettext(Msgid) \ dgettext (DEFAULT_TEXT_DOMAIN, Msgid) # undef ngettext # define ngettext(Msgid1, Msgid2, N) \ dngettext (DEFAULT_TEXT_DOMAIN, Msgid1, Msgid2, N) # endif /* The separator between msgctxt and msgid in a .mo file. */ #define GETTEXT_CONTEXT_GLUE "\004" /* Pseudo function calls, taking a MSGCTXT and a MSGID instead of just a MSGID. MSGCTXT and MSGID must be string literals. MSGCTXT should be short and rarely need to change. The letter 'p' stands for 'particular' or 'special'. */ #ifdef DEFAULT_TEXT_DOMAIN # define pgettext(Msgctxt, Msgid) \ pgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid) #else # define pgettext(Msgctxt, Msgid) \ pgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid) #endif #define dpgettext(Domainname, Msgctxt, Msgid) \ pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid) #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static const char * pgettext_aux(const char *domain, const char *msg_ctxt_id, const char *msgid) { const char *translation = dgettext(domain, msg_ctxt_id); if (translation == msg_ctxt_id) { return msgid; } else { return translation; } } /* The same thing extended for non-constant arguments. Here MSGCTXT and MSGID can be arbitrary expressions. But for string literals these macros are less efficient than those above. */ #include #ifndef __STRICT_ANSI__ #define __STRICT_ANSI__ #define __STRICT_ANSI_FORCED__ #endif #define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS \ (__STRICT_ANSI__ - 0 == 0) && (__GNUC__ >= 3 || __GNUG__ >= 2 /* || __STDC_VERSION__ >= 199901L */ ) #if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS #include #endif #define pgettext_expr(Msgctxt, Msgid) \ dpgettext_expr (NULL, Msgctxt, Msgid) #define dpgettext_expr(Domainname, Msgctxt, Msgid) \ dpgettext_expr (Domainname, Msgctxt, Msgid) #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static const char * dpgettext_expr(const char *domain, const char *msgctxt, const char *msgid) { size_t msgctxt_len = strlen(msgctxt) + 1; size_t msgid_len = strlen(msgid) + 1; const char *translation; int translation_found; #if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS char msg_ctxt_id[msgctxt_len + msgid_len]; #else char buf[1024]; char *msg_ctxt_id = (msgctxt_len + msgid_len <= sizeof(buf) ? buf : (char *) malloc(msgctxt_len + msgid_len)); - if (msg_ctxt_id != NULL) + if (msg_ctxt_id != nullptr) #endif { memcpy(msg_ctxt_id, msgctxt, msgctxt_len - 1); msg_ctxt_id[msgctxt_len - 1] = '\004'; memcpy(msg_ctxt_id + msgctxt_len, msgid, msgid_len); translation = dgettext(domain, msg_ctxt_id); /* Test must occur before msg_ctxt_id freed */ translation_found = translation != msg_ctxt_id; #if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS if (msg_ctxt_id != buf) { free(msg_ctxt_id); } #endif if (translation_found) { return translation; } } return msgid; } #define npgettext_expr(Msgctxt, Msgid, MsgidPlural, N) \ dnpgettext_expr (NULL, Msgctxt, Msgid, MsgidPlural, N) #define dnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N) \ dnpgettext_expr (Domainname, Msgctxt, Msgid, MsgidPlural, N) #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static const char * dnpgettext_expr(const char *domain, const char *msgctxt, const char *msgid, const char *msgid_plural, unsigned long int n) { size_t msgctxt_len = strlen(msgctxt) + 1; size_t msgid_len = strlen(msgid) + 1; const char *translation; int translation_found; #if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS char msg_ctxt_id[msgctxt_len + msgid_len]; #else char buf[1024]; char *msg_ctxt_id = (msgctxt_len + msgid_len <= sizeof(buf) ? buf : (char *) malloc(msgctxt_len + msgid_len)); - if (msg_ctxt_id != NULL) + if (msg_ctxt_id != nullptr) #endif { memcpy(msg_ctxt_id, msgctxt, msgctxt_len - 1); msg_ctxt_id[msgctxt_len - 1] = '\004'; memcpy(msg_ctxt_id + msgctxt_len, msgid, msgid_len); translation = dngettext(domain, msg_ctxt_id, msgid_plural, n); /* Test must occur before msg_ctxt_id freed */ translation_found = !(translation == msg_ctxt_id || translation == msgid_plural); #if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS if (msg_ctxt_id != buf) { free(msg_ctxt_id); } #endif if (translation_found) { return translation; } } return (n == 1 ? msgid : msgid_plural); } #ifdef __STRICT_ANSI_FORCED__ #undef __STRICT_ANSI__ #undef __STRICT_ANSI_FORCED__ #endif #endif /* _LIBGETTEXT_H */ diff --git a/src/kcatalog.cpp b/src/kcatalog.cpp index 85d64ab..81f25d4 100644 --- a/src/kcatalog.cpp +++ b/src/kcatalog.cpp @@ -1,259 +1,259 @@ /* This file is part of the KDE libraries Copyright (c) 2001 Hans Petter Bieker Copyright (c) 2012, 2013 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. */ #include #include #include "gettext.h" #include #include #include #include #include #include #include #include #include #include // not defined on win32 :( #ifdef _WIN32 #ifndef LC_MESSAGES #define LC_MESSAGES 42 #endif #endif #if defined(__USE_GNU_GETTEXT) extern "C" int Q_DECL_IMPORT _nl_msg_cat_cntr; #endif -static char *langenv = 0; +static char *langenv = nullptr; static const int langenvMaxlen = 42; // = "LANGUAGE=" + 32 chars for language code + terminating zero class KCatalogStaticData { public: KCatalogStaticData() {} QMutex mutex; }; Q_GLOBAL_STATIC(KCatalogStaticData, catalogStaticData) class KCatalogPrivate { public: KCatalogPrivate(); QByteArray domain; QByteArray language; QByteArray localeDir; QByteArray systemLanguage; bool bindDone; static QByteArray currentLanguage; void setupGettextEnv(); void resetSystemLanguage(); }; KCatalogPrivate::KCatalogPrivate() : bindDone(false) {} QByteArray KCatalogPrivate::currentLanguage; KCatalog::KCatalog(const QByteArray &domain, const QString &language_) : d(new KCatalogPrivate) { d->domain = domain; d->language = QFile::encodeName(language_); d->localeDir = QFile::encodeName(catalogLocaleDir(domain, language_)); if (!d->localeDir.isEmpty()) { // Always get translations in UTF-8, regardless of user's environment. bind_textdomain_codeset(d->domain, "UTF-8"); // Invalidate current language, to trigger binding at next translate call. KCatalogPrivate::currentLanguage.clear(); if (!langenv) { // Call putenv only here, to initialize LANGUAGE variable. // Later only change langenv to what is currently needed. langenv = new char[langenvMaxlen]; QByteArray baselang = qgetenv("LANGUAGE"); qsnprintf(langenv, langenvMaxlen, "LANGUAGE=%s", baselang.constData()); putenv(langenv); } } } KCatalog::~KCatalog() { delete d; } QString KCatalog::catalogLocaleDir(const QByteArray &domain, const QString &language) { QString relpath = QStringLiteral("%1/LC_MESSAGES/%2.mo") .arg(language, QFile::decodeName(domain)); QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("locale/") + relpath); QString localeDir; if (file.isEmpty()) { localeDir = QString(); } else { // Path of the locale/ directory must be returned. localeDir = QFileInfo(file.left(file.size() - relpath.size())).absolutePath(); } return localeDir; } QSet KCatalog::availableCatalogLanguages(const QByteArray &domain_) { QString domain = QFile::decodeName(domain_); QStringList localeDirPaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("locale"), QStandardPaths::LocateDirectory); QSet availableLanguages; foreach (const QString &localDirPath, localeDirPaths) { QDir localeDir(localDirPath); QStringList languages = localeDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); foreach (const QString &language, languages) { QString relPath = QStringLiteral("%1/LC_MESSAGES/%2.mo") .arg(language, domain); if (localeDir.exists(relPath)) { availableLanguages.insert(language); } } } return availableLanguages; } void KCatalogPrivate::setupGettextEnv() { // Point Gettext to current language, recording system value for recovery. systemLanguage = qgetenv("LANGUAGE"); if (systemLanguage != language) { // putenv has been called in the constructor, // it is enough to change the string set there. qsnprintf(langenv, langenvMaxlen, "LANGUAGE=%s", language.constData()); } // Rebind text domain if language actually changed from the last time, // as locale directories may differ for different languages of same catalog. if (language != currentLanguage || !bindDone) { currentLanguage = language; bindDone = true; //qDebug() << "bindtextdomain" << domain << localeDir; bindtextdomain(domain, localeDir); // Magic to make sure GNU Gettext doesn't use stale cached translation // from previous language. #if defined(__USE_GNU_GETTEXT) ++_nl_msg_cat_cntr; #endif } } void KCatalogPrivate::resetSystemLanguage() { if (language != systemLanguage) { qsnprintf(langenv, langenvMaxlen, "LANGUAGE=%s", systemLanguage.constData()); } } QString KCatalog::translate(const QByteArray &msgid) const { if (!d->localeDir.isEmpty()) { QMutexLocker locker(&catalogStaticData()->mutex); d->setupGettextEnv(); const char *msgstr = dgettext(d->domain.constData(), msgid.constData()); d->resetSystemLanguage(); return msgstr != msgid ? QString::fromUtf8(msgstr) : QString(); } else { return QString(); } } QString KCatalog::translate(const QByteArray &msgctxt, const QByteArray &msgid) const { if (!d->localeDir.isEmpty()) { QMutexLocker locker(&catalogStaticData()->mutex); d->setupGettextEnv(); const char *msgstr = dpgettext_expr(d->domain.constData(), msgctxt.constData(), msgid.constData()); d->resetSystemLanguage(); return msgstr != msgid ? QString::fromUtf8(msgstr) : QString(); } else { return QString(); } } QString KCatalog::translate(const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n) const { if (!d->localeDir.isEmpty()) { QMutexLocker locker(&catalogStaticData()->mutex); d->setupGettextEnv(); const char *msgstr = dngettext(d->domain.constData(), msgid.constData(), msgid_plural.constData(), n); d->resetSystemLanguage(); // If original and translation are same, dngettext will return // the original pointer, which is generally fine, except in // the corner cases where e.g. msgstr[1] is same as msgid. // Therefore check for pointer difference only with msgid or // only with msgid_plural, and not with both. return (n == 1 && msgstr != msgid) || (n != 1 && msgstr != msgid_plural) ? QString::fromUtf8(msgstr) : QString(); } else { return QString(); } } QString KCatalog::translate(const QByteArray &msgctxt, const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n) const { if (!d->localeDir.isEmpty()) { QMutexLocker locker(&catalogStaticData()->mutex); d->setupGettextEnv(); const char *msgstr = dnpgettext_expr(d->domain.constData(), msgctxt.constData(), msgid.constData(), msgid_plural.constData(), n); d->resetSystemLanguage(); return (n == 1 && msgstr != msgid) || (n != 1 && msgstr != msgid_plural) ? QString::fromUtf8(msgstr) : QString(); } else { return QString(); } } diff --git a/src/klocalizedstring.cpp b/src/klocalizedstring.cpp index 5cc8d33..e689a90 100644 --- a/src/klocalizedstring.cpp +++ b/src/klocalizedstring.cpp @@ -1,1644 +1,1644 @@ /* This file is part of the KDE libraries Copyright (C) 2006, 2013 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. */ // We don't want i18n to be expanded to i18nd here #undef TRANSLATION_DOMAIN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Truncate string, for output of long messages. static QString shortenMessage(const QString &str) { const int maxlen = 20; if (str.length() <= maxlen) { return str; } else { return str.leftRef(maxlen) + QLatin1String("..."); } } static void splitLocale(const QString &aLocale, QString &language, QString &country, QString &modifier, QString &charset) { QString locale = aLocale; language.clear(); country.clear(); modifier.clear(); charset.clear(); // In case there are several concatenated locale specifications, // truncate all but first. int f = locale.indexOf(QLatin1Char(':')); if (f >= 0) { locale.truncate(f); } // now decompose into [language[_territory][.codeset][@modifier]] f = locale.indexOf(QLatin1Char('@')); if (f >= 0) { modifier = locale.mid(f + 1); locale.truncate(f); } f = locale.indexOf(QLatin1Char('.')); if (f >= 0) { charset = locale.mid(f + 1); locale.truncate(f); } f = locale.indexOf(QLatin1Char('_')); if (f >= 0) { country = locale.mid(f + 1); locale.truncate(f); } language = locale; } static void appendLocaleString(QStringList &languages, const QString &value) { // Process the value to create possible combinations. QString language, country, modifier, charset; splitLocale(value, language, country, modifier, charset); if (language.isEmpty()) { return; } if (!country.isEmpty() && !modifier.isEmpty()) { languages += language + QLatin1Char('_') + country + QLatin1Char('@') + modifier; } // NOTE: Priority is unclear in case both the country and // the modifier are present. Should really language@modifier be of // higher priority than language_country? // In at least one case (Serbian language), it is better this way. if (!modifier.isEmpty()) { languages += language + QLatin1Char('@') + modifier; } if (!country.isEmpty()) { languages += language + QLatin1Char('_') + country; } languages += language; } static void appendLanguagesFromVariable(QStringList &languages, const char *envar, bool isList = false) { QByteArray qenvar(qgetenv(envar)); if (!qenvar.isEmpty()) { QString value = QFile::decodeName(qenvar); if (isList) { foreach(const QString &v, value.split(QLatin1Char(':'), QString::SkipEmptyParts)) { appendLocaleString(languages, v); } } else { appendLocaleString(languages, value); } } } #ifndef Q_OS_UNIX static void appendLanguagesFromQLocale(QStringList &languages, const QLocale &locale) { const QStringList uiLangs = locale.uiLanguages(); Q_FOREACH (QString value, uiLangs) { appendLocaleString(languages, value.replace(QLatin1Char('-'), QLatin1Char('_'))); } } #endif // Extract the first country code from a list of language_COUNTRY strings. // Country code is converted to all lower case letters. static QString extractCountry(const QStringList &languages) { QString country; foreach (const QString &language, languages) { int pos1 = language.indexOf(QLatin1Char('_')); if (pos1 >= 0) { ++pos1; int pos2 = pos1; while (pos2 < language.length() && language[pos2].isLetter()) { ++pos2; } country = language.mid(pos1, pos2 - pos1); break; } } country = country.toLower(); return country; } typedef qulonglong pluraln; typedef qlonglong intn; typedef qulonglong uintn; typedef double realn; class KLocalizedStringPrivate { friend class KLocalizedString; QByteArray domain; QStringList languages; Kuit::VisualFormat format; QByteArray context; QByteArray text; QByteArray plural; QStringList arguments; QList values; QHash klsArguments; QHash klsArgumentFieldWidths; QHash klsArgumentFillChars; bool numberSet; pluraln number; int numberOrdinal; QHash dynamicContext; bool markupAware; bool relaxedSubs; static void translateRaw(const QByteArray &domain, const QStringList &languages, const QByteArray &msgctxt, const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n, QString &language, QString &translation); QString toString(const QByteArray &domain, const QStringList &languages, Kuit::VisualFormat format, bool isArgument = false) const; QString substituteSimple(const QString &translation, const QStringList &arguments, QChar plchar = QLatin1Char('%'), bool isPartial = false) const; QString formatMarkup(const QByteArray &domain, const QString &language, const QString &context, const QString &text, Kuit::VisualFormat format) const; QString substituteTranscript(const QString &scriptedTranslation, const QString &language, const QString &country, const QString &ordinaryTranslation, const QStringList &arguments, const QList &values, bool &fallback) const; int resolveInterpolation(const QString &scriptedTranslation, int pos, const QString &language, const QString &country, const QString &ordinaryTranslation, const QStringList &arguments, const QList &values, QString &result, bool &fallback) const; QVariant segmentToValue(const QString &segment) const; QString postTranscript(const QString &pcall, const QString &language, const QString &country, const QString &finalTranslation, const QStringList &arguments, const QList &values) const; static const KCatalog &getCatalog(const QByteArray &domain, const QString &language); static void locateScriptingModule(const QByteArray &domain, const QString &language); static void loadTranscript(); void checkNumber(pluraln a) { if (!plural.isEmpty() && !numberSet) { number = a; numberSet = true; numberOrdinal = arguments.size(); } } }; typedef QHash KCatalogPtrHash; class KLocalizedStringPrivateStatics { public: QHash catalogs; QStringList languages; QByteArray ourDomain; QByteArray applicationDomain; QString codeLanguage; QStringList localeLanguages; const QString theFence; const QString startInterp; const QString endInterp; const QChar scriptPlchar; const QChar scriptVachar; const QString scriptDir; QHash > scriptModules; QList scriptModulesToLoad; bool loadTranscriptCalled; KTranscript *ktrs; QHash formatters; QList qtDomains; QList qtDomainInsertCount; QMutex klspMutex; KLocalizedStringPrivateStatics(); ~KLocalizedStringPrivateStatics(); void initializeLocaleLanguages(); }; KLocalizedStringPrivateStatics::KLocalizedStringPrivateStatics() : catalogs() , languages() , ourDomain(QByteArrayLiteral("ki18n5")) , applicationDomain() , codeLanguage(QStringLiteral("en_US")) , localeLanguages() , theFence(QStringLiteral("|/|")) , startInterp(QStringLiteral("$[")) , endInterp(QStringLiteral("]")) , scriptPlchar(QLatin1Char('%')) , scriptVachar(QLatin1Char('^')) , scriptDir(QStringLiteral("LC_SCRIPTS")) , scriptModules() , scriptModulesToLoad() , loadTranscriptCalled(false) - , ktrs(NULL) + , ktrs(nullptr) , formatters() , qtDomains() , qtDomainInsertCount() , klspMutex(QMutex::Recursive) { initializeLocaleLanguages(); languages = localeLanguages; } KLocalizedStringPrivateStatics::~KLocalizedStringPrivateStatics() { foreach (const KCatalogPtrHash &languageCatalogs, catalogs) { qDeleteAll(languageCatalogs); } // ktrs is handled by QLibrary. //delete ktrs; qDeleteAll(formatters); } Q_GLOBAL_STATIC(KLocalizedStringPrivateStatics, staticsKLSP) void KLocalizedStringPrivateStatics::initializeLocaleLanguages() { QMutexLocker lock(&klspMutex); // Collect languages by same order of priority as for gettext(3). // LANGUAGE contains list of language codes, not locale string. appendLanguagesFromVariable(localeLanguages, "LANGUAGE", true); appendLanguagesFromVariable(localeLanguages, "LC_ALL"); appendLanguagesFromVariable(localeLanguages, "LC_MESSAGES"); appendLanguagesFromVariable(localeLanguages, "LANG"); #ifndef Q_OS_UNIX // For non UNIX platforms the environment variables might not // suffice so we add system locale UI languages, too. appendLanguagesFromQLocale(localeLanguages, QLocale::system()); #endif } KLocalizedString::KLocalizedString() : d(new KLocalizedStringPrivate) { } KLocalizedString::KLocalizedString(const char *domain, const char *context, const char *text, const char *plural, bool markupAware) : d(new KLocalizedStringPrivate) { d->domain = domain; d->languages.clear(); d->format = Kuit::UndefinedFormat; d->context = context; d->text = text; d->plural = plural; d->numberSet = false; d->number = 0; d->numberOrdinal = 0; d->markupAware = markupAware; d->relaxedSubs = false; } KLocalizedString::KLocalizedString(const KLocalizedString &rhs) : d(new KLocalizedStringPrivate(*rhs.d)) { } KLocalizedString &KLocalizedString::operator=(const KLocalizedString &rhs) { if (&rhs != this) { *d = *rhs.d; } return *this; } KLocalizedString::~KLocalizedString() { delete d; } bool KLocalizedString::isEmpty() const { return d->text.isEmpty(); } void KLocalizedStringPrivate::translateRaw(const QByteArray &domain, const QStringList &languages, const QByteArray &msgctxt, const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n, QString &language, QString &msgstr) { KLocalizedStringPrivateStatics *s = staticsKLSP(); // Empty msgid would result in returning the catalog header, // which is never intended, so warn and return empty translation. if (msgid.isNull() || msgid.isEmpty()) { qWarning() << "KLocalizedString: " "Trying to look up translation of \"\", fix the code."; language.clear(); msgstr.clear(); return; } // Gettext semantics allows empty context, but it is pointless, so warn. if (!msgctxt.isNull() && msgctxt.isEmpty()) { qWarning() << "KLocalizedString: " "Using \"\" as context, fix the code."; } // Gettext semantics allows empty plural, but it is pointless, so warn. if (!msgid_plural.isNull() && msgid_plural.isEmpty()) { qWarning() << "KLocalizedString: " "Using \"\" as plural text, fix the code."; } // Set translation to text in code language, in case no translation found. msgstr = msgid_plural.isNull() || n == 1 ? QString::fromUtf8(msgid) : QString::fromUtf8(msgid_plural); language = s->codeLanguage; if (domain.isEmpty()) { return; } // Languages are ordered from highest to lowest priority. foreach (const QString &testLanguage, languages) { // If code language reached, no catalog lookup is needed. if (testLanguage == s->codeLanguage) { return; } const KCatalog &catalog = getCatalog(domain, testLanguage); QString testMsgstr; if (!msgctxt.isNull() && !msgid_plural.isNull()) { testMsgstr = catalog.translate(msgctxt, msgid, msgid_plural, n); } else if (!msgid_plural.isNull()) { testMsgstr = catalog.translate(msgid, msgid_plural, n); } else if (!msgctxt.isNull()) { testMsgstr = catalog.translate(msgctxt, msgid); } else { testMsgstr = catalog.translate(msgid); } if (!testMsgstr.isEmpty()) { // Translation found. language = testLanguage; msgstr = testMsgstr; return; } } } QString KLocalizedString::toString() const { return d->toString(d->domain, d->languages, d->format); } QString KLocalizedString::toString(const char *domain) const { return d->toString(domain, d->languages, d->format); } QString KLocalizedString::toString(const QStringList &languages) const { return d->toString(d->domain, languages, d->format); } QString KLocalizedString::toString(Kuit::VisualFormat format) const { return d->toString(d->domain, d->languages, format); } QString KLocalizedStringPrivate::toString(const QByteArray &domain, const QStringList &languages, Kuit::VisualFormat format, bool isArgument) const { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); // Assure the message has been supplied. if (text.isEmpty()) { qWarning() << "Trying to convert empty KLocalizedString to QString."; #ifndef NDEBUG return QStringLiteral("(I18N_EMPTY_MESSAGE)"); #else return QString(); #endif } // Check whether plural argument has been supplied, if message has plural. if (!plural.isEmpty() && !numberSet) { qWarning() << QStringLiteral( "Plural argument to message {%1} not supplied before conversion.") .arg(shortenMessage(QString::fromUtf8(text))); } // Resolve inputs. QByteArray resolvedDomain = domain; if (resolvedDomain.isEmpty()) { resolvedDomain = s->applicationDomain; } QStringList resolvedLanguages = languages; if (resolvedLanguages.isEmpty()) { resolvedLanguages = s->languages; } Kuit::VisualFormat resolvedFormat = format; // Get raw translation. QString language, rawTranslation; translateRaw(resolvedDomain, resolvedLanguages, context, text, plural, number, language, rawTranslation); QString country = extractCountry(resolvedLanguages); // Set ordinary translation and possibly scripted translation. QString translation, scriptedTranslation; int fencePos = rawTranslation.indexOf(s->theFence); if (fencePos > 0) { // Script fence has been found, strip the scripted from the // ordinary translation. translation = rawTranslation.left(fencePos); // Scripted translation. scriptedTranslation = rawTranslation.mid(fencePos + s->theFence.length()); // Try to initialize Transcript if not initialized and script not empty. // FIXME: And also if Transcript not disabled: where to configure this? if (!s->loadTranscriptCalled && !scriptedTranslation.isEmpty()) { loadTranscript(); // Definitions from this library's scripting module // must be available to all other modules. // So force creation of this library's catalog here, // to make sure the scripting module is loaded. getCatalog(s->ourDomain, language); } } else if (fencePos < 0) { // No script fence, use translation as is. translation = rawTranslation; } else { // fencePos == 0 // The msgstr starts with the script fence, no ordinary translation. // This is not allowed, consider message not translated. qWarning() << QStringLiteral( "Scripted message {%1} without ordinary translation, discarded.") .arg(shortenMessage(translation)); translation = plural.isEmpty() || number == 1 ? QString::fromUtf8(text) : QString::fromUtf8(plural); } // Resolve substituted KLocalizedString arguments. QStringList resolvedArguments; QList resolvedValues; for (int i = 0; i < arguments.size(); i++) { if (klsArguments.contains(i)) { const KLocalizedString &kls = klsArguments.value(i); int fieldWidth = klsArgumentFieldWidths.value(i); QChar fillChar = klsArgumentFillChars.value(i); // Override argument's languages and format, but not domain. bool isArgumentSub = true; QString resdArg = kls.d->toString(kls.d->domain, resolvedLanguages, resolvedFormat, isArgumentSub); resolvedValues.append(resdArg); if (markupAware && !kls.d->markupAware) { resdArg = Kuit::escape(resdArg); } resdArg = QStringLiteral("%1").arg(resdArg, fieldWidth, fillChar); resolvedArguments.append(resdArg); } else { QString resdArg = arguments[i]; if (markupAware) { resdArg = Kuit::escape(resdArg); } resolvedArguments.append(resdArg); resolvedValues.append(values[i]); } } // Substitute placeholders in ordinary translation. QString finalTranslation = substituteSimple(translation, resolvedArguments); if (markupAware && !isArgument) { // Resolve markup in ordinary translation. finalTranslation = formatMarkup(resolvedDomain, language, QString::fromUtf8(context), finalTranslation, resolvedFormat); } // If there is also a scripted translation. if (!scriptedTranslation.isEmpty()) { // Evaluate scripted translation. bool fallback; scriptedTranslation = substituteTranscript(scriptedTranslation, language, country, finalTranslation, resolvedArguments, resolvedValues, fallback); // If any translation produced and no fallback requested. if (!scriptedTranslation.isEmpty() && !fallback) { if (markupAware && !isArgument) { // Resolve markup in scripted translation. scriptedTranslation = formatMarkup(resolvedDomain, language, QString::fromUtf8(context), scriptedTranslation, resolvedFormat); } finalTranslation = scriptedTranslation; } } // Execute any scripted post calls; they cannot modify the final result, // but are used to set states. - if (s->ktrs != NULL) { + if (s->ktrs != nullptr) { QStringList pcalls = s->ktrs->postCalls(language); foreach (const QString &pcall, pcalls) { postTranscript(pcall, language, country, finalTranslation, resolvedArguments, resolvedValues); } } return finalTranslation; } QString KLocalizedStringPrivate::substituteSimple(const QString &translation, const QStringList &arguments, QChar plchar, bool isPartial) const { #ifdef NDEBUG Q_UNUSED(isPartial); #endif QStringList tsegs; // text segments per placeholder occurrence QList plords; // ordinal numbers per placeholder occurrence #ifndef NDEBUG QVector ords; // indicates which placeholders are present #endif int slen = translation.length(); int spos = 0; int tpos = translation.indexOf(plchar); while (tpos >= 0) { int ctpos = tpos; ++tpos; if (tpos == slen) { break; } if (translation[tpos].digitValue() > 0) { // NOTE: %0 is not considered a placeholder. // Get the placeholder ordinal. int plord = 0; while (tpos < slen && translation[tpos].digitValue() >= 0) { plord = 10 * plord + translation[tpos].digitValue(); ++tpos; } --plord; // ordinals are zero based #ifndef NDEBUG // Perhaps enlarge storage for indicators. // Note that QVector will initialize new elements to 0, // as they are supposed to be. if (plord >= ords.size()) { ords.resize(plord + 1); } // Indicate that placeholder with computed ordinal is present. ords[plord] = 1; #endif // Store text segment prior to placeholder and placeholder number. tsegs.append(translation.mid(spos, ctpos - spos)); plords.append(plord); // Position of next text segment. spos = tpos; } tpos = translation.indexOf(plchar, tpos); } // Store last text segment. tsegs.append(translation.mid(spos)); #ifndef NDEBUG // Perhaps enlarge storage for plural-number ordinal. if (!plural.isEmpty() && numberOrdinal >= ords.size()) { ords.resize(numberOrdinal + 1); } // Message might have plural but without plural placeholder, which is an // allowed state. To ease further logic, indicate that plural placeholder // is present anyway if message has plural. if (!plural.isEmpty()) { ords[numberOrdinal] = 1; } #endif // Assemble the final string from text segments and arguments. QString finalTranslation; for (int i = 0; i < plords.size(); i++) { finalTranslation.append(tsegs.at(i)); if (plords.at(i) >= arguments.size()) { // too little arguments // put back the placeholder finalTranslation.append(QLatin1Char('%') + QString::number(plords.at(i) + 1)); #ifndef NDEBUG if (!isPartial) { // spoof the message finalTranslation.append(QStringLiteral("(I18N_ARGUMENT_MISSING)")); } #endif } else { // just fine finalTranslation.append(arguments.at(plords.at(i))); } } finalTranslation.append(tsegs.last()); #ifndef NDEBUG if (!isPartial && !relaxedSubs) { // Check that there are no gaps in numbering sequence of placeholders. bool gaps = false; for (int i = 0; i < ords.size(); i++) { if (!ords.at(i)) { gaps = true; qWarning() << QStringLiteral( "Placeholder %%1 skipped in message {%2}.") .arg(QString::number(i + 1), shortenMessage(translation)); } } // If no gaps, check for mismatch between the number of // unique placeholders and actually supplied arguments. if (!gaps && ords.size() != arguments.size()) { qWarning() << QString::fromLatin1( "%1 instead of %2 arguments to message {%3} " "supplied before conversion.") .arg(arguments.size()).arg(ords.size()) .arg(shortenMessage(translation)); } // Some spoofs. if (gaps) { finalTranslation.append(QStringLiteral("(I18N_GAPS_IN_PLACEHOLDER_SEQUENCE)")); } if (ords.size() < arguments.size()) { finalTranslation.append(QStringLiteral("(I18N_EXCESS_ARGUMENTS_SUPPLIED)")); } } if (!isPartial) { if (!plural.isEmpty() && !numberSet) { finalTranslation.append(QStringLiteral("(I18N_PLURAL_ARGUMENT_MISSING)")); } } #endif return finalTranslation; } QString KLocalizedStringPrivate::formatMarkup(const QByteArray &domain, const QString &language, const QString &context, const QString &text, Kuit::VisualFormat format) const { KLocalizedStringPrivateStatics *s = staticsKLSP(); QHash::iterator formatter = s->formatters.find(language); if (formatter == s->formatters.end()) { formatter = s->formatters.insert(language, new KuitFormatter(language)); } return (*formatter)->format(domain, context, text, format); } QString KLocalizedStringPrivate::substituteTranscript(const QString &scriptedTranslation, const QString &language, const QString &country, const QString &ordinaryTranslation, const QStringList &arguments, const QList &values, bool &fallback) const { KLocalizedStringPrivateStatics *s = staticsKLSP(); - if (s->ktrs == NULL) { + if (s->ktrs == nullptr) { // Scripting engine not available. return QString(); } // Iterate by interpolations. QString finalTranslation; fallback = false; int ppos = 0; int tpos = scriptedTranslation.indexOf(s->startInterp); while (tpos >= 0) { // Resolve substitutions in preceding text. QString ptext = substituteSimple(scriptedTranslation.mid(ppos, tpos - ppos), arguments, s->scriptPlchar, true); finalTranslation.append(ptext); // Resolve interpolation. QString result; bool fallbackLocal; tpos = resolveInterpolation(scriptedTranslation, tpos, language, country, ordinaryTranslation, arguments, values, result, fallbackLocal); // If there was a problem in parsing the interpolation, cannot proceed // (debug info already reported while parsing). if (tpos < 0) { return QString(); } // If fallback has been explicitly requested, indicate global fallback // but proceed with evaluations (other interpolations may set states). if (fallbackLocal) { fallback = true; } // Add evaluated interpolation to the text. finalTranslation.append(result); // On to next interpolation. ppos = tpos; tpos = scriptedTranslation.indexOf(s->startInterp, tpos); } // Last text segment. finalTranslation.append(substituteSimple(scriptedTranslation.mid(ppos), arguments, s->scriptPlchar, true)); // Return empty string if fallback was requested. return fallback ? QString() : finalTranslation; } int KLocalizedStringPrivate::resolveInterpolation(const QString &scriptedTranslation, int pos, const QString &language, const QString &country, const QString &ordinaryTranslation, const QStringList &arguments, const QList &values, QString &result, bool &fallback) const { // pos is the position of opening character sequence. // Returns the position of first character after closing sequence, // or -1 in case of parsing error. // result is set to result of Transcript evaluation. // fallback is set to true if Transcript evaluation requested so. KLocalizedStringPrivateStatics *s = staticsKLSP(); result.clear(); fallback = false; // Split interpolation into arguments. QList iargs; int slen = scriptedTranslation.length(); int islen = s->startInterp.length(); int ielen = s->endInterp.length(); int tpos = pos + s->startInterp.length(); while (1) { // Skip whitespace. while (tpos < slen && scriptedTranslation[tpos].isSpace()) { ++tpos; } if (tpos == slen) { qWarning() << QStringLiteral( "Unclosed interpolation {%1} in message {%2}.") .arg(scriptedTranslation.mid(pos, tpos - pos), shortenMessage(scriptedTranslation)); return -1; } if (scriptedTranslation.midRef(tpos, ielen) == s->endInterp) { break; // no more arguments } // Parse argument: may be concatenated from free and quoted text, // and sub-interpolations. // Free and quoted segments may contain placeholders, substitute them; // recurse into sub-interpolations. // Free segments may be value references, parse and record for // consideration at the end. // Mind backslash escapes throughout. QStringList segs; QVariant vref; while (!scriptedTranslation[tpos].isSpace() && scriptedTranslation.mid(tpos, ielen) != s->endInterp) { if (scriptedTranslation[tpos] == QLatin1Char('\'')) { // quoted segment QString seg; ++tpos; // skip opening quote // Find closing quote. while (tpos < slen && scriptedTranslation[tpos] != QLatin1Char('\'')) { if (scriptedTranslation[tpos] == QLatin1Char('\\')) { ++tpos; // escape next character } seg.append(scriptedTranslation[tpos]); ++tpos; } if (tpos == slen) { qWarning() << QStringLiteral( "Unclosed quote in interpolation {%1} in message {%2}.") .arg(scriptedTranslation.mid(pos, tpos - pos), shortenMessage(scriptedTranslation)); return -1; } // Append to list of segments, resolving placeholders. segs.append(substituteSimple(seg, arguments, s->scriptPlchar, true)); ++tpos; // skip closing quote } else if (scriptedTranslation.mid(tpos, islen) == s->startInterp) { // sub-interpolation QString resultLocal; bool fallbackLocal; tpos = resolveInterpolation(scriptedTranslation, tpos, language, country, ordinaryTranslation, arguments, values, resultLocal, fallbackLocal); if (tpos < 0) { // unrecoverable problem in sub-interpolation // Error reported in the subcall. return tpos; } if (fallbackLocal) { // sub-interpolation requested fallback fallback = true; } segs.append(resultLocal); } else { // free segment QString seg; // Find whitespace, quote, opening or closing sequence. while (tpos < slen && !scriptedTranslation[tpos].isSpace() && scriptedTranslation[tpos] != QLatin1Char('\'') && scriptedTranslation.mid(tpos, islen) != s->startInterp && scriptedTranslation.mid(tpos, ielen) != s->endInterp) { if (scriptedTranslation[tpos] == QLatin1Char('\\')) { ++tpos; // escape next character } seg.append(scriptedTranslation[tpos]); ++tpos; } if (tpos == slen) { qWarning() << QStringLiteral( "Non-terminated interpolation {%1} in message {%2}.") .arg(scriptedTranslation.mid(pos, tpos - pos), shortenMessage(scriptedTranslation)); return -1; } // The free segment may look like a value reference; // in that case, record which value it would reference, // and add verbatim to the segment list. // Otherwise, do a normal substitution on the segment. vref = segmentToValue(seg); if (vref.isValid()) { segs.append(seg); } else { segs.append(substituteSimple(seg, arguments, s->scriptPlchar, true)); } } } // Append this argument to rest of the arguments. // If the there was a single text segment and it was a proper value // reference, add it instead of the joined segments. // Otherwise, add the joined segments. if (segs.size() == 1 && vref.isValid()) { iargs.append(vref); } else { iargs.append(segs.join(QString())); } } tpos += ielen; // skip to first character after closing sequence // NOTE: Why not substitute placeholders (via substituteSimple) in one // global pass, then handle interpolations in second pass? Because then // there is the danger of substituted text or sub-interpolations producing // quotes and escapes themselves, which would mess up the parsing. // Evaluate interpolation. QString msgctxt = QString::fromUtf8(context); QString msgid = QString::fromUtf8(text); QString scriptError; bool fallbackLocal; result = s->ktrs->eval(iargs, language, country, msgctxt, dynamicContext, msgid, arguments, values, ordinaryTranslation, s->scriptModulesToLoad, scriptError, fallbackLocal); // s->scriptModulesToLoad will be cleared during the call. if (fallbackLocal) { // evaluation requested fallback fallback = true; } if (!scriptError.isEmpty()) { // problem with evaluation fallback = true; // also signal fallback if (!scriptError.isEmpty()) { qWarning() << QStringLiteral( "Interpolation {%1} in {%2} failed: %3") .arg(scriptedTranslation.mid(pos, tpos - pos), shortenMessage(scriptedTranslation), scriptError); } } return tpos; } QVariant KLocalizedStringPrivate::segmentToValue(const QString &segment) const { KLocalizedStringPrivateStatics *s = staticsKLSP(); // Return invalid variant if segment is either not a proper // value reference, or the reference is out of bounds. // Value reference must start with a special character. if (!segment.startsWith(s->scriptVachar)) { return QVariant(); } // Reference number must start with 1-9. // (If numstr is empty, toInt() will return 0.) QString numstr = segment.mid(1); if (numstr.leftRef(1).toInt() < 1) { return QVariant(); } // Number must be valid and in bounds. bool ok; int index = numstr.toInt(&ok) - 1; if (!ok || index >= values.size()) { return QVariant(); } // Passed all hoops. return values.at(index); } QString KLocalizedStringPrivate::postTranscript(const QString &pcall, const QString &language, const QString &country, const QString &finalTranslation, const QStringList &arguments, const QList &values) const { KLocalizedStringPrivateStatics *s = staticsKLSP(); - if (s->ktrs == NULL) { + if (s->ktrs == nullptr) { // Scripting engine not available. // (Though this cannot happen, we wouldn't be here then.) return QString(); } // Resolve the post call. QList iargs; iargs.append(pcall); QString msgctxt = QString::fromUtf8(context); QString msgid = QString::fromUtf8(text); QString scriptError; bool fallback; QString dummy = s->ktrs->eval(iargs, language, country, msgctxt, dynamicContext, msgid, arguments, values, finalTranslation, s->scriptModulesToLoad, scriptError, fallback); // s->scriptModulesToLoad will be cleared during the call. // If the evaluation went wrong. if (!scriptError.isEmpty()) { qWarning() << QStringLiteral( "Post call {%1} for message {%2} failed: %3") .arg(pcall, shortenMessage(msgid), scriptError); return QString(); } return finalTranslation; } KLocalizedString KLocalizedString::withLanguages(const QStringList &languages) const { KLocalizedString kls(*this); kls.d->languages = languages; return kls; } KLocalizedString KLocalizedString::withDomain(const char *domain) const { KLocalizedString kls(*this); kls.d->domain = domain; return kls; } KLocalizedString KLocalizedString::withFormat(Kuit::VisualFormat format) const { KLocalizedString kls(*this); kls.d->format = format; return kls; } KLocalizedString KLocalizedString::subs(int a, int fieldWidth, int base, QChar fillChar) const { KLocalizedString kls(*this); kls.d->checkNumber(std::abs(a)); kls.d->arguments.append(QStringLiteral("%1").arg(a, fieldWidth, base, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(uint a, int fieldWidth, int base, QChar fillChar) const { KLocalizedString kls(*this); kls.d->checkNumber(a); kls.d->arguments.append(QStringLiteral("%1").arg(a, fieldWidth, base, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(long a, int fieldWidth, int base, QChar fillChar) const { KLocalizedString kls(*this); kls.d->checkNumber(std::abs(a)); kls.d->arguments.append(QStringLiteral("%1").arg(a, fieldWidth, base, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(ulong a, int fieldWidth, int base, QChar fillChar) const { KLocalizedString kls(*this); kls.d->checkNumber(a); kls.d->arguments.append(QStringLiteral("%1").arg(a, fieldWidth, base, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(qlonglong a, int fieldWidth, int base, QChar fillChar) const { KLocalizedString kls(*this); kls.d->checkNumber(qAbs(a)); kls.d->arguments.append(QStringLiteral("%1").arg(a, fieldWidth, base, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(qulonglong a, int fieldWidth, int base, QChar fillChar) const { KLocalizedString kls(*this); kls.d->checkNumber(a); kls.d->arguments.append(QStringLiteral("%1").arg(a, fieldWidth, base, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(double a, int fieldWidth, char format, int precision, QChar fillChar) const { KLocalizedString kls(*this); kls.d->arguments.append(QStringLiteral("%1").arg(a, fieldWidth, format, precision, fillChar)); kls.d->values.append(static_cast(a)); return kls; } KLocalizedString KLocalizedString::subs(QChar a, int fieldWidth, QChar fillChar) const { KLocalizedString kls(*this); QString baseArg = QString(a); QString fmtdArg = QStringLiteral("%1").arg(a, fieldWidth, fillChar); kls.d->arguments.append(fmtdArg); kls.d->values.append(baseArg); return kls; } KLocalizedString KLocalizedString::subs(const QString &a, int fieldWidth, QChar fillChar) const { KLocalizedString kls(*this); QString baseArg = a; QString fmtdArg = QStringLiteral("%1").arg(a, fieldWidth, fillChar); kls.d->arguments.append(fmtdArg); kls.d->values.append(baseArg); return kls; } KLocalizedString KLocalizedString::subs(const KLocalizedString &a, int fieldWidth, QChar fillChar) const { KLocalizedString kls(*this); // KLocalizedString arguments must be resolved inside toString // when the domain, language, visual format, etc. become known. int i = kls.d->arguments.size(); kls.d->klsArguments[i] = a; kls.d->klsArgumentFieldWidths[i] = fieldWidth; kls.d->klsArgumentFillChars[i] = fillChar; kls.d->arguments.append(QString()); kls.d->values.append(0); return kls; } KLocalizedString KLocalizedString::inContext(const QString &key, const QString &value) const { KLocalizedString kls(*this); kls.d->dynamicContext[key] = value; return kls; } KLocalizedString KLocalizedString::relaxSubs() const { KLocalizedString kls(*this); kls.d->relaxedSubs = true; return kls; } KLocalizedString KLocalizedString::ignoreMarkup() const { KLocalizedString kls(*this); kls.d->markupAware = false; return kls; } void KLocalizedString::setApplicationDomain(const char *domain) { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); s->applicationDomain = domain; } QByteArray KLocalizedString::applicationDomain() { KLocalizedStringPrivateStatics *s = staticsKLSP(); return s->applicationDomain; } QStringList KLocalizedString::languages() { KLocalizedStringPrivateStatics *s = staticsKLSP(); return s->languages; } void KLocalizedString::setLanguages(const QStringList &languages) { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); s->languages = languages; } void KLocalizedString::clearLanguages() { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); s->languages = s->localeLanguages; } bool KLocalizedString::isApplicationTranslatedInto(const QString &language) { KLocalizedStringPrivateStatics *s = staticsKLSP(); return language == s->codeLanguage || !KCatalog::catalogLocaleDir(s->applicationDomain, language).isEmpty(); } QSet KLocalizedString::availableApplicationTranslations() { KLocalizedStringPrivateStatics *s = staticsKLSP(); QSet availableLanguages; QByteArray domain = s->applicationDomain; if (!domain.isEmpty()) { availableLanguages = KCatalog::availableCatalogLanguages(domain); availableLanguages.insert(s->codeLanguage); } return availableLanguages; } QSet KLocalizedString::availableDomainTranslations(const QByteArray &domain) { QSet availableLanguages; if (!domain.isEmpty()) { availableLanguages = KCatalog::availableCatalogLanguages(domain); availableLanguages.insert(staticsKLSP()->codeLanguage); } return availableLanguages; } const KCatalog &KLocalizedStringPrivate::getCatalog(const QByteArray &domain, const QString &language) { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); QHash::iterator languageCatalogs = s->catalogs.find(domain); if (languageCatalogs == s->catalogs.end()) { languageCatalogs = s->catalogs.insert(domain, KCatalogPtrHash()); } KCatalogPtrHash::iterator catalog = languageCatalogs->find(language); if (catalog == languageCatalogs->end()) { catalog = languageCatalogs->insert(language, new KCatalog(domain, language)); locateScriptingModule(domain, language); } return **catalog; } void KLocalizedStringPrivate::locateScriptingModule(const QByteArray &domain, const QString &language) { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); // Assemble module's relative path. QString modrpath = language + QLatin1Char('/') + s->scriptDir + QLatin1Char('/') + QString::fromLatin1(domain) + QLatin1Char('/') + QString::fromLatin1(domain) + QLatin1String(".js"); // Try to find this module. QString modapath = QStandardPaths::locate( QStandardPaths::GenericDataLocation, QLatin1String("locale") + QLatin1Char('/') + modrpath); // If the module exists and hasn't been already included. if (!modapath.isEmpty() && !s->scriptModules[language].contains(domain)) { // Indicate that the module has been considered. s->scriptModules[language].append(domain); // Store the absolute path and language of the module, // to load on next script evaluation. QStringList module; module.append(modapath); module.append(language); s->scriptModulesToLoad.append(module); } } extern "C" { typedef KTranscript *(*InitFunc)(); } void KLocalizedStringPrivate::loadTranscript() { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); s->loadTranscriptCalled = true; - s->ktrs = NULL; // null indicates that Transcript is not available + s->ktrs = nullptr; // null indicates that Transcript is not available // QPluginLoader is just used to find the plugin QPluginLoader loader(QStringLiteral("kf5/ktranscript")); if (loader.fileName().isEmpty()) { qWarning() << "Cannot find Transcript plugin."; return; } QLibrary lib(loader.fileName()); if (!lib.load()) { qWarning() << "Cannot load Transcript plugin:" << lib.errorString(); return; } InitFunc initf = (InitFunc) lib.resolve("load_transcript"); if (!initf) { lib.unload(); qWarning() << "Cannot find function load_transcript in Transcript plugin."; return; } s->ktrs = initf(); } QString KLocalizedString::localizedFilePath(const QString &filePath) { KLocalizedStringPrivateStatics *s = staticsKLSP(); // Check if l10n subdirectory is present, stop if not. QFileInfo fileInfo(filePath); QString locDirPath = fileInfo.path() + QLatin1Char('/') + QLatin1String("l10n"); QFileInfo locDirInfo(locDirPath); if (!locDirInfo.isDir()) { return filePath; } // Go through possible localized paths by priority of languages, // return first that exists. QString fileName = fileInfo.fileName(); foreach (const QString &lang, s->languages) { QString locFilePath = locDirPath + QLatin1Char('/') + lang + QLatin1Char('/') + fileName; QFileInfo locFileInfo(locFilePath); if (locFileInfo.isFile() && locFileInfo.isReadable()) { return locFilePath; } } return filePath; } QString KLocalizedString::removeAcceleratorMarker(const QString &label) { return ::removeAcceleratorMarker(label); } QString KLocalizedString::translateQt(const char *context, const char *sourceText, const char *comment, int n) { // NOTE: Qt message semantics. // // Qt's context is normally the name of the class of the method which makes // the tr(sourceText) call. However, it can also be manually supplied via // translate(context, sourceText) call. // // Qt's sourceText is the actual message displayed to the user. // // Qt's comment is an optional argument of tr() and translate(), like // tr(sourceText, comment) and translate(context, sourceText, comment). // // We handle this in the following way: // // If the comment is given, then it is considered gettext's msgctxt, so a // context call is made. // // If the comment is not given, but context is given, then we treat it as // msgctxt only if it was manually supplied (the one in translate()) -- but // we don't know this, so we first try a context call, and if translation // is not found, we fallback to ordinary call. // // If neither comment nor context are given, it's just an ordinary call // on sourceText. Q_UNUSED(n); KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); if (!sourceText || !sourceText[0]) { qWarning() << "KLocalizedString::translateQt: " "Trying to look up translation of \"\", fix the code."; return QString(); } // NOTE: Condition (language != s->codeLanguage) means that translation // was found, otherwise the original text was returned as translation. QString translation; QString language; foreach (const QByteArray &domain, s->qtDomains) { if (comment && comment[0]) { // Comment given, go for context call. KLocalizedStringPrivate::translateRaw(domain, s->languages, - comment, sourceText, 0, 0, + comment, sourceText, nullptr, 0, language, translation); } else { // Comment not given, go for try-fallback with context. if (context && context[0]) { KLocalizedStringPrivate::translateRaw(domain, s->languages, - context, sourceText, 0, 0, + context, sourceText, nullptr, 0, language, translation); } if (language.isEmpty() || language == s->codeLanguage) { KLocalizedStringPrivate::translateRaw(domain, s->languages, - 0, sourceText, 0, 0, + nullptr, sourceText, nullptr, 0, language, translation); } } if (language != s->codeLanguage) { return translation; } } // No proper translation found, return empty according to Qt semantics. return QString(); } void KLocalizedString::insertQtDomain(const char *domain) { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); int pos = s->qtDomains.indexOf(domain); if (pos < 0) { // Domain priority is undefined, but to minimize damage // due to message conflicts, put later inserted catalogs at front. s->qtDomains.prepend(domain); s->qtDomainInsertCount.prepend(1); } else { ++s->qtDomainInsertCount[pos]; } } void KLocalizedString::removeQtDomain(const char *domain) { KLocalizedStringPrivateStatics *s = staticsKLSP(); QMutexLocker lock(&s->klspMutex); int pos = s->qtDomains.indexOf(domain); if (pos >= 0 && --s->qtDomainInsertCount[pos] == 0) { s->qtDomains.removeAt(pos); s->qtDomainInsertCount.removeAt(pos); } } KLocalizedString ki18n(const char *text) { - return KLocalizedString(NULL, NULL, text, NULL, false); + return KLocalizedString(nullptr, nullptr, text, nullptr, false); } KLocalizedString ki18nc(const char *context, const char *text) { - return KLocalizedString(NULL, context, text, NULL, false); + return KLocalizedString(nullptr, context, text, nullptr, false); } KLocalizedString ki18np(const char *singular, const char *plural) { - return KLocalizedString(NULL, NULL, singular, plural, false); + return KLocalizedString(nullptr, nullptr, singular, plural, false); } KLocalizedString ki18ncp(const char *context, const char *singular, const char *plural) { - return KLocalizedString(NULL, context, singular, plural, false); + return KLocalizedString(nullptr, context, singular, plural, false); } KLocalizedString ki18nd(const char *domain, const char *text) { - return KLocalizedString(domain, NULL, text, NULL, false); + return KLocalizedString(domain, nullptr, text, nullptr, false); } KLocalizedString ki18ndc(const char *domain, const char *context, const char *text) { - return KLocalizedString(domain, context, text, NULL, false); + return KLocalizedString(domain, context, text, nullptr, false); } KLocalizedString ki18ndp(const char *domain, const char *singular, const char *plural) { - return KLocalizedString(domain, NULL, singular, plural, false); + return KLocalizedString(domain, nullptr, singular, plural, false); } KLocalizedString ki18ndcp(const char *domain, const char *context, const char *singular, const char *plural) { return KLocalizedString(domain, context, singular, plural, false); } KLocalizedString kxi18n(const char *text) { - return KLocalizedString(NULL, NULL, text, NULL, true); + return KLocalizedString(nullptr, nullptr, text, nullptr, true); } KLocalizedString kxi18nc(const char *context, const char *text) { - return KLocalizedString(NULL, context, text, NULL, true); + return KLocalizedString(nullptr, context, text, nullptr, true); } KLocalizedString kxi18np(const char *singular, const char *plural) { - return KLocalizedString(NULL, NULL, singular, plural, true); + return KLocalizedString(nullptr, nullptr, singular, plural, true); } KLocalizedString kxi18ncp(const char *context, const char *singular, const char *plural) { - return KLocalizedString(NULL, context, singular, plural, true); + return KLocalizedString(nullptr, context, singular, plural, true); } KLocalizedString kxi18nd(const char *domain, const char *text) { - return KLocalizedString(domain, NULL, text, NULL, true); + return KLocalizedString(domain, nullptr, text, nullptr, true); } KLocalizedString kxi18ndc(const char *domain, const char *context, const char *text) { - return KLocalizedString(domain, context, text, NULL, true); + return KLocalizedString(domain, context, text, nullptr, true); } KLocalizedString kxi18ndp(const char *domain, const char *singular, const char *plural) { - return KLocalizedString(domain, NULL, singular, plural, true); + return KLocalizedString(domain, nullptr, singular, plural, true); } KLocalizedString kxi18ndcp(const char *domain, const char *context, const char *singular, const char *plural) { return KLocalizedString(domain, context, singular, plural, true); } diff --git a/src/klocalizedstring.h b/src/klocalizedstring.h index c7e7f53..39746df 100644 --- a/src/klocalizedstring.h +++ b/src/klocalizedstring.h @@ -1,2006 +1,2006 @@ /* This file is part of the KDE libraries Copyright (C) 2006, 2013 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. */ #ifndef KLOCALIZEDSTRING_H #define KLOCALIZEDSTRING_H #include #include #include #include #include #include #include class KLocalizedStringPrivate; /** * \file klocalizedstring.h */ #ifndef I18N_NOOP /** * Wrap string for extraction. * * See \ref i18n_noop for use cases. */ #define I18N_NOOP(text) text #endif #ifndef I18NC_NOOP /** * Wrap string with context for extraction. * * See \ref i18n_noop for use cases. */ #define I18NC_NOOP(context, text) context, text #endif #ifndef I18N_NOOP2 /** * Wrap string with context for extraction, discarding context. * * \deprecated Use \c I18NC_NOOP. */ #define I18N_NOOP2(context, text) text #endif #ifndef I18N_NOOP2_NOSTRIP /** * Wrap string with context for extraction. * * \deprecated Old name for \c I18NC_NOOP. */ #define I18N_NOOP2_NOSTRIP(context, text) context, text #endif /** * \short Class for producing and handling localized messages * * \c KLocalizedString handles translation and * argument substitution and formatting of user-visible text. * * \c KLocalizedString instances are usually not constructed directly, * but through one of the wrapper \c \*i18n\* calls. * * For detailed information on how to use KI18n functions please refer * to \ref prg_guide. */ class KI18N_EXPORT KLocalizedString { friend class KLocalizedStringPrivate; friend KLocalizedString KI18N_EXPORT ki18n(const char *text); friend KLocalizedString KI18N_EXPORT ki18nc(const char *context, const char *text); friend KLocalizedString KI18N_EXPORT ki18np(const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT ki18ncp(const char *context, const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT ki18nd(const char *domain, const char *text); friend KLocalizedString KI18N_EXPORT ki18ndc(const char *domain, const char *context, const char *text); friend KLocalizedString KI18N_EXPORT ki18ndp(const char *domain, const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT ki18ndcp(const char *domain, const char *context, const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT kxi18n(const char *text); friend KLocalizedString KI18N_EXPORT kxi18nc(const char *context, const char *text); friend KLocalizedString KI18N_EXPORT kxi18np(const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT kxi18ncp(const char *context, const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT kxi18nd(const char *domain, const char *text); friend KLocalizedString KI18N_EXPORT kxi18ndc(const char *domain, const char *context, const char *text); friend KLocalizedString KI18N_EXPORT kxi18ndp(const char *domain, const char *singular, const char *plural); friend KLocalizedString KI18N_EXPORT kxi18ndcp(const char *domain, const char *context, const char *singular, const char *plural); public: /** * Construct an empty message. * * Direct construction is used when another \c KLocalizedString instance, * obtained by one of \c ki18n\* calls, should later be assigned * to directly constructed instance. * Before the assignment happens, directly constructed instance * is not valid for finalization by \c toString methods. * * \see isEmpty */ explicit KLocalizedString(); /** * Copy constructor. */ KLocalizedString(const KLocalizedString &rhs); /** * Assignment operator. */ KLocalizedString &operator=(const KLocalizedString &rhs); /** * Destructor. */ ~KLocalizedString(); /** * Check whether the message is empty. * * The message is considered empty if the object was constructed * via the default constructor. * * Empty messages are not valid for finalization. * The behavior of calling \c toString on them is undefined. * In debug mode, an error mark may appear in the returned string. * * \return \c true if the message is empty, \c false otherwise */ bool isEmpty() const; /** * Finalize the translation. * * Creates translated \c QString, with placeholders substituted * by arguments given by \c KLocalizedString::subs methods. * Translated text is searched for and arguments are formatted * based on the global locale. * * If there was any mismatch between placeholders and arguments, * in debug mode the returned string may contain error marks. * * \return finalized translation */ QString toString() const Q_REQUIRED_RESULT; /** * Like \c toString, but look for translation only in given languages. * * Given languages override languages defined by the global locale, * and any languages set earlier using \c withLanguages. * If \p languages is empty, original message is returned. * * \param languages list of language codes (by decreasing priority) * \return finalized translation */ QString toString(const QStringList &languages) const Q_REQUIRED_RESULT; #if 0 // until locale system is ready /** * Like \c toString, but look for translation based on given locale. * * Given locale overrides any set earlier using \c withLocale. * If \p locale is \c NULL, original message is returned. * * \param locale the locale for which translations are made * \return finalized translation */ QString toString(const KLocale *locale) const; #endif /** * Like \c toString, but look for translation in the given domain. * * Given domain overrides any set earlier using \c withDomain. * * \param domain the translation domain * \return finalized translation */ QString toString(const char *domain) const Q_REQUIRED_RESULT; /** * Like \c toString, but resolve KUIT markup into given visual format. * * Given visual format overrides that implied by the context UI marker * or set earlier using \c withFormat. * If the message is not markup-aware, * this is same as \c toString without arguments. * * \param format the target visual format * \return finalized translation */ QString toString(Kuit::VisualFormat format) const Q_REQUIRED_RESULT; /** * Indicate to look for translation only in given languages. * * \param languages list of language codes (by decreasing priority) * \return updated \c KLocalizedString */ KLocalizedString withLanguages(const QStringList &languages) const Q_REQUIRED_RESULT; #if 0 // until locale system is ready /** * Indicate to look for translation based on given locale. * * \param locale the locale for which translations are made * \return updated \c KLocalizedString */ KLocalizedString withLocale(const KLocale *locale) const; #endif /** * Indicate to look for translation in the given domain. * * \param domain the translation domain * \return updated \c KLocalizedString */ KLocalizedString withDomain(const char *domain) const Q_REQUIRED_RESULT; /** * Indicate to resolve KUIT markup into given visual format. * * If the message is not markup-aware, this has no effect. * * \param format the target visual format * \return updated \c KLocalizedString */ KLocalizedString withFormat(Kuit::VisualFormat format) const Q_REQUIRED_RESULT; /** * Substitute an int argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param base the radix used to represent the number as a string. * Valid values range from 2 to 36 * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ KLocalizedString subs(int a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const Q_REQUIRED_RESULT; /** * Substitute an unsigned int argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param base the radix used to represent the number as a string. * Valid values range from 2 to 36 * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ KLocalizedString subs(uint a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const Q_REQUIRED_RESULT; /** * Substitute a long argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param base the radix used to represent the number as a string. * Valid values range from 2 to 36 * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ KLocalizedString subs(long a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const Q_REQUIRED_RESULT; /** * Substitute an unsigned long argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param base the radix used to represent the number as a string. * Valid values range from 2 to 36 * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ KLocalizedString subs(ulong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const Q_REQUIRED_RESULT; /** * Substitute a long long argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param base the radix used to represent the number as a string. * Valid values range from 2 to 36 * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ KLocalizedString subs(qlonglong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const Q_REQUIRED_RESULT; /** * Substitute an unsigned long long argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param base the radix used to represent the number as a string. * Valid values range from 2 to 36 * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ KLocalizedString subs(qulonglong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const Q_REQUIRED_RESULT; /** * Substitute a double argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param format type of floating point formating, like in QString::arg * \param precision number of digits after the decimal separator * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ KLocalizedString subs(double a, int fieldWidth = 0, char format = 'g', int precision = -1, QChar fillChar = QLatin1Char(' ')) const Q_REQUIRED_RESULT; /** * Substitute a \c QChar argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ KLocalizedString subs(QChar a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const Q_REQUIRED_RESULT; /** * Substitute a \c QString argument into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ KLocalizedString subs(const QString &a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const Q_REQUIRED_RESULT; /** * Substitute another \c KLocalizedString into the message. * * \param a the argument * \param fieldWidth width of the formatted field, padded by spaces. * Positive value aligns right, negative aligns left * \param fillChar the character used to fill up the empty places when * field width is greater than argument width * \return updated \c KLocalizedString */ KLocalizedString subs(const KLocalizedString &a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const Q_REQUIRED_RESULT; /** * Add dynamic context to the message. * * See \ref dyn_ctxt for use cases. * * \param key context key * \param value context value * \return updated \c KLocalizedString */ KLocalizedString inContext(const QString &key, const QString &value) const Q_REQUIRED_RESULT; /** * Relax matching between placeholders and arguments. * * Normally the placeholders should start from %1 and have no gaps, * and on finalization there must be exactly as many arguments * supplied through \c subs methods as there are unique plaecholders. * If this is not satisfied, in debug mode warnings are printed * and the finalized string may contain error marks. * * This method relaxes the placeholder-argument matching, * such that there must only be an argument available for * every present unique placeholder (taking placeholder numbers * to be 1-based indices into the argument list). * This can come useful in some situations. * * \return updated \c KLocalizedString */ KLocalizedString relaxSubs() const Q_REQUIRED_RESULT; /** * Do not resolve KUIT markup. * * If the message is markup-aware * (constructed by one of \c \*xi18n\* calls), * this function can be used to make it non-markup-aware. * This may be useful for debugging markup. * * \return updated \c KLocalizedString */ KLocalizedString ignoreMarkup() const Q_REQUIRED_RESULT; /** * Set the given domain as application's main domain. * * This function must be called in applications, in order to have * any translations at all. It should never be called in libraries. * This allows to check whether the application is translated * into a given language, so that if it is not, translations from * underlying libraries will not appear even if they are translated. * This prevents mixing of translated and untranslated text * in the user interface. * * \param domain the translation domain of the application */ static void setApplicationDomain(const char *domain); /** * Get the application's main translation domain. * * Returns the domain set by \c setApplicationDomain. */ static QByteArray applicationDomain(); #if 0 // until locale system is ready /** * Set the locale for which translations will be made. * * Locale determines from which languages (and in which order) * to draw translations, formatting of number arguments, etc. * * \param locale the locale * \see setLanguages */ static void setLocale(const KLocale &locale); #endif /** * Get the languages for which translations will be made. * * Returned languages are ordered with decreasing priority. * * \return languages ordered list of language codes * \see setLanguages * \see clearLanguages * * \since 5.20.0 */ static QStringList languages(); /** * Set the languages for which translations will be made. * * This overrides the languages provided by the locale. * Languages should be ordered with decreasing priority. * * \param languages ordered list of language codes * \see setLocale * \see clearLanguages * \see languages */ static void setLanguages(const QStringList &languages); /** * Clear override languages. * * This clears the override languages, going back to those * provided by the locale. * * \see setLanguages * \see languages */ static void clearLanguages(); /** * Check whether the translation catalog file in the given language * for the set application translation domain exists. * * \param language the language code to check * \return \c true if the translation catalog for \p language exits, * \c false otherwise * \see setApplicationDomain */ static bool isApplicationTranslatedInto(const QString &language); /** * @since 5.0 * * Get the languages for which there exists the translation catalog file * for the set application translation domain. * * The application domain is set by \c setApplicationDomain. * If the application domain was not set, empty set is returned. * If the application domain was set, the language set will always * contain at least the source code language (en_US). * * \return set of language codes for existing translation catalogs * \see setApplicationDomain */ static QSet availableApplicationTranslations(); /** * @since 5.0 * * Get the languages for which a translation catalog file * for the passed translation domain exists. * * If the translation domain was not specified in the * domain parameter an empty set is returned. * * If the application domain was set, the language set will always * contain at least the source code language (en_US). * * \param domain query for translations of a specific domain, if an empty * QByteArray is passed, an empty set will be returned * * \return set of language codes for existing translation catalogs * \see setApplicationDomain * \see availableApplicationTranslations */ static QSet availableDomainTranslations(const QByteArray &domain); /** * Find a path to the localized file for the given original path. * * This is intended mainly for non-text resources (images, sounds, etc). * Text resources should be handled in more specific ways. * * Possible localized paths are checked in turn by priority of set * languages, in form of \/l10n/\/\, * where \ and \ are those of * the original path, and \ is the language code. * * \param filePath path to the original file * * \return path to the localized file if found, original path otherwise */ static QString localizedFilePath(const QString &filePath) Q_REQUIRED_RESULT; /** * Remove accelerator marker from a UI text label. * * Accelerator marker is not always a plain ampersand (&), * so it is not enough to just remove it by \c QString::remove. * The label may contain escaped markers ("&&") which must be resolved * and skipped, as well as CJK-style markers ("Foo (&F)") where * the whole parenthesis construct should be removed. * Therefore always use this function to remove accelerator marker * from UI labels. * * \param label UI label which may contain an accelerator marker * \return label without the accelerator marker */ static QString removeAcceleratorMarker(const QString &label) Q_REQUIRED_RESULT; /** * Translate a message with Qt semantics. * * This functions provides a capability to derive a Qt translator from * \c QTranslator and draw translations from PO catalogs of given domain. * All domains added with \c insertQtDomain are checked for translation, * in undefined order. * No Ki18n-specific processing is performed (formatting, scripting, etc). * * \see QTranslator * * \deprecated Use Qt's native i18n system, Qt Linguist, * with roundtrip TS->PO->TS through * Qt's \c lupdate and \c lconvert commands. */ KI18N_DEPRECATED static QString translateQt(const char *context, const char *text, const char *comment, int n) Q_REQUIRED_RESULT; /** * Add another domain to search for Qt translations. * * \param domain the translation domain to add * * \see translateQt * \see removeQtDomain * * \deprecated */ KI18N_DEPRECATED static void insertQtDomain(const char *domain); /** * Remove a domain from Qt translation lookup. * * To really remove the domain, this function must be invoked * at least as many times as \c insertQtDomain was invoked to add * this domain. This makes it safe to always use paired * insertion/removal calls, without pulling out a domain * underneath an unrelated piece of code that uses it as well. * * \param domain the translation domain to remove * * \see translateQt * \see insertQtDomain * * \deprecated */ KI18N_DEPRECATED static void removeQtDomain(const char *domain); private: KLocalizedString(const char *domain, const char *context, const char *text, const char *plural, bool markupAware); KLocalizedStringPrivate *const d; }; // Do not document every multi-argument i18n* call separately, // but provide special quasi-calls that only Doxygen sees. // Placed in front of ki18n* calls, because i18n* are more basic. #ifdef DOXYGEN_PREPROC /** * Translate a string and substitute any arguments. * * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18n(const char *text, const TYPE &arg...); /** * Translate a string with context and substitute any arguments. * * \param context context of the string * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18nc(const char *context, const char *text, const TYPE &arg...); /** * Translate a string with plural and substitute any arguments. * * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18np(const char *singular, const char *plural, const TYPE &arg...); /** * Translate a string with context and plural and substitute any arguments. * * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...); /** * Translate a string from domain and substitute any arguments. * * \param domain domain in which to look for translations * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18nd(const char *domain, const char *text, const TYPE &arg...); /** * Translate a string from domain with context and substitute any arguments. * * \param domain domain in which to look for translations * \param context context of the string * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18ndc(const char *domain, const char *context, const char *text, const TYPE &arg...); /** * Translate a string from domain with plural and substitute any arguments. * * \param domain domain in which to look for translations * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18ndp(const char *domain, const char *singular, const char *plural, const TYPE &arg...); /** * Translate a string from domain with context and plural * and substitute any arguments. * * \param domain domain in which to look for translations * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const TYPE &arg...); /** * Translate a markup-aware string and substitute any arguments. * * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18n(const char *text, const TYPE &arg...); /** * Translate a markup-aware string with context and substitute any arguments. * * \param context context of the string * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18nc(const char *context, const char *text, const TYPE &arg...); /** * Translate a markup-aware string with plural and substitute any arguments. * * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18np(const char *singular, const char *plural, const TYPE &arg...); /** * Translate a markup-aware string with context and plural * and substitute any arguments. * * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...); /** * Translate a markup-aware string from domain and substitute any arguments. * * \param domain domain in which to look for translations * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18nd(const char *domain, const char *text, const TYPE &arg...); /** * Translate a markup-aware string from domain with context * and substitute any arguments. * * \param domain domain in which to look for translations * \param context context of the string * \param text string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18ndc(const char *domain, const char *context, const char *text, const TYPE &arg...); /** * Translate a markup-aware string from domain with plural * and substitute any arguments. * * \param domain domain in which to look for translations * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18ndp(const char *domain, const char *singular, const char *plural, const TYPE &arg...); /** * Translate a markup-aware string from domain with context and plural * and substitute any arguments. * * \param domain domain in which to look for translations * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \param arg arguments to insert (0 to 9), * admissible types according to \c KLocalizedString::subs methods * \return translated string */ QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const TYPE &arg...); #endif // DOXYGEN_PREPROC /** * Create non-finalized translated string. * * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18n(const char *text); /** * Create non-finalized translated string with context. * * \param context context of the string * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18nc(const char *context, const char *text); /** * Create non-finalized translated string with plural. * * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18np(const char *singular, const char *plural); /** * Create non-finalized translated string with context and plural. * * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18ncp(const char *context, const char *singular, const char *plural); /** * Create non-finalized translated string from domain. * * \param domain domain in which to look for translations * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18nd(const char *domain, const char *text); /** * Create non-finalized translated string from domain with context. * * \param domain domain in which to look for translations * \param context context of the string * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18ndc(const char *domain, const char *context, const char *text); /** * Create non-finalized translated string from domain with plural. * * \param domain domain in which to look for translations * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18ndp(const char *domain, const char *singular, const char *plural); /** * Create non-finalized translated string from domain with context and plural. * * \param domain domain in which to look for translations * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT ki18ndcp(const char *domain, const char *context, const char *singular, const char *plural); /** * Create non-finalized markup-aware translated string. * * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18n(const char *text); /** * Create non-finalized markup-aware translated string with context. * * \param context context of the string * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18nc(const char *context, const char *text); /** * Create non-finalized markup-aware translated string with plural. * * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18np(const char *singular, const char *plural); /** * Create non-finalized markup-aware translated string. * with context and plural. * * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18ncp(const char *context, const char *singular, const char *plural); /** * Create non-finalized markup-aware translated string from domain. * * \param domain domain in which to look for translations * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18nd(const char *domain, const char *text); /** * Create non-finalized markup-aware translated string from domain with context. * * \param domain domain in which to look for translations * \param context context of the string * \param text string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18ndc(const char *domain, const char *context, const char *text); /** * Create non-finalized markup-aware translated string from domain with plural. * * \param domain domain in which to look for translations * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18ndp(const char *domain, const char *singular, const char *plural); /** * Create non-finalized markup-aware translated string from domain * with context and plural. * * \param domain domain in which to look for translations * \param context context of the string * \param singular singular form of the string to translate * \param plural plural form of the string to translate * \return non-finalized translated string */ KLocalizedString KI18N_EXPORT kxi18ndcp(const char *domain, const char *context, const char *singular, const char *plural); /** * Redirect Qt's uic-generated translation calls to Ki18n. * * Use -tr tr2i18n option to \c uic to have it redirect calls. * * \param text string to translate * \param comment Qt equivalent of disambiguation context * \return translated string */ -inline QString tr2i18n(const char *text, const char *comment = 0) +inline QString tr2i18n(const char *text, const char *comment = nullptr) { if (comment && comment[0] && text && text[0]) { return ki18nc(comment, text).toString(); } else if (text && text[0]) { return ki18n(text).toString(); } else { return QString(); } } /** * Like \c tr2i18n, but look for translation in a specific domain. * * Use -tr tr2i18nd option to \c uic to have it redirect calls. * * \param domain domain in which to look for translations * \param text string to translate * \param comment Qt equivalent of disambiguation context * \return translated string */ inline QString tr2i18nd(const char *domain, - const char *text, const char *comment = 0) + const char *text, const char *comment = nullptr) { if (comment && comment[0] && text && text[0]) { return ki18ndc(domain, comment, text).toString(); } else if (text && text[0]) { return ki18nd(domain, text).toString(); } else { return QString(); } } /** * Like \c tr2i18n, but when UI strings are KUIT markup-aware. * * Use -tr tr2xi18n option to \c uic to have it redirect calls. * * \param text markup-aware string to translate * \param comment Qt equivalent of disambiguation context * \return translated string */ -inline QString tr2xi18n(const char *text, const char *comment = 0) +inline QString tr2xi18n(const char *text, const char *comment = nullptr) { if (comment && comment[0] && text && text[0]) { return kxi18nc(comment, text).toString(); } else if (text && text[0]) { return kxi18n(text).toString(); } else { return QString(); } } /** * Like \c tr2xi18n, but look for translation in a specific domain. * * Use -tr tr2xi18nd option to \c uic to have it redirect calls. * * \param domain domain in which to look for translations * \param text markup-aware string to translate * \param comment Qt equivalent of disambiguation context * \return translated string */ inline QString tr2xi18nd(const char *domain, - const char *text, const char *comment = 0) + const char *text, const char *comment = nullptr) { if (comment && comment[0] && text && text[0]) { return kxi18ndc(domain, comment, text).toString(); } else if (text && text[0]) { return kxi18nd(domain, text).toString(); } else { return QString(); } } #ifndef DOXYGEN_PREPROC #ifndef NDEBUG #define I18N_ERR_MSG String_literal_as_second_argument_to_i18n___Perhaps_you_need_i18nc_or_i18np template class I18nTypeCheck { public: static void I18N_ERR_MSG() {} }; template class I18nTypeCheck {}; #define STATIC_ASSERT_NOT_LITERAL_STRING(T) I18nTypeCheck::I18N_ERR_MSG(); #else #define STATIC_ASSERT_NOT_LITERAL_STRING(T) #endif // >>>>> Basic calls // Autogenerated; contact maintainer for batch changes. inline QString i18n(const char *text) { return ki18n(text).toString(); } template inline QString i18n(const char *text, const A1 &a1) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<<<< End of basic calls // >>>>> Context calls // Autogenerated; contact maintainer for batch changes. inline QString i18nc(const char *context, const char *text) { return ki18nc(context, text).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1) { return ki18nc(context, text).subs(a1).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2) { return ki18nc(context, text).subs(a1).subs(a2).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return ki18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of context calls // >>>>> Plural calls // Autogenerated; contact maintainer for batch changes. template inline QString i18np(const char *singular, const char *plural, const A1 &a1) { return ki18np(singular, plural).subs(a1).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return ki18np(singular, plural).subs(a1).subs(a2).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return ki18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of plural calls // >>>>> Context-plural calls // Autogenerated; contact maintainer for batch changes. template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1) { return ki18ncp(context, singular, plural).subs(a1).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return ki18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of context-plural calls // >>>>> Basic calls with domain // Autogenerated; contact maintainer for batch changes. inline QString i18nd(const char *domain, const char *text) { return ki18nd(domain, text).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return ki18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<<<< End of basic calls with domain // >>>>> Context calls with domain // Autogenerated; contact maintainer for batch changes. inline QString i18ndc(const char *domain, const char *context, const char *text) { return ki18ndc(domain, context, text).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1) { return ki18ndc(domain, context, text).subs(a1).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2) { return ki18ndc(domain, context, text).subs(a1).subs(a2).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return ki18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of context calls with domain // >>>>> Plural calls with domain // Autogenerated; contact maintainer for batch changes. template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1) { return ki18ndp(domain, singular, plural).subs(a1).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return ki18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of plural calls with domain // >>>>> Context-plural calls with domain // Autogenerated; contact maintainer for batch changes. template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1) { return ki18ndcp(domain, context, singular, plural).subs(a1).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString i18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return ki18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of context-plural calls with domain // >>>>> Markup-aware basic calls // Autogenerated; contact maintainer for batch changes. inline QString xi18n(const char *text) { return kxi18n(text).toString(); } template inline QString xi18n(const char *text, const A1 &a1) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18n(text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<<<< End of markup-aware basic calls // >>>>> Markup-aware context calls // Autogenerated; contact maintainer for batch changes. inline QString xi18nc(const char *context, const char *text) { return kxi18nc(context, text).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1) { return kxi18nc(context, text).subs(a1).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2) { return kxi18nc(context, text).subs(a1).subs(a2).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18nc(const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return kxi18nc(context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of markup-aware context calls // >>>>> Markup-aware plural calls // Autogenerated; contact maintainer for batch changes. template inline QString xi18np(const char *singular, const char *plural, const A1 &a1) { return kxi18np(singular, plural).subs(a1).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return kxi18np(singular, plural).subs(a1).subs(a2).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18np(const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return kxi18np(singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of markup-aware plural calls // >>>>> Markup-aware context-plural calls // Autogenerated; contact maintainer for batch changes. template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1) { return kxi18ncp(context, singular, plural).subs(a1).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18ncp(const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return kxi18ncp(context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of markup-aware context-plural calls // >>>>> Markup-aware basic calls with domain // Autogenerated; contact maintainer for batch changes. inline QString xi18nd(const char *domain, const char *text) { return kxi18nd(domain, text).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18nd(const char *domain, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { STATIC_ASSERT_NOT_LITERAL_STRING(A1) return kxi18nd(domain, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<<<< End of markup-aware basic calls with domain // >>>>> Markup-aware context calls with domain // Autogenerated; contact maintainer for batch changes. inline QString xi18ndc(const char *domain, const char *context, const char *text) { return kxi18ndc(domain, context, text).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1) { return kxi18ndc(domain, context, text).subs(a1).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18ndc(const char *domain, const char *context, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return kxi18ndc(domain, context, text).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of markup-aware context calls with domain // >>>>> Markup-aware plural calls with domain // Autogenerated; contact maintainer for batch changes. template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1) { return kxi18ndp(domain, singular, plural).subs(a1).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18ndp(const char *domain, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return kxi18ndp(domain, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of markup-aware plural calls with domain // >>>>> Markup-aware context-plural calls with domain // Autogenerated; contact maintainer for batch changes. template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1) { return kxi18ndcp(domain, context, singular, plural).subs(a1).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).toString(); } template inline QString xi18ndcp(const char *domain, const char *context, const char *singular, const char *plural, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4, const A5 &a5, const A6 &a6, const A7 &a7, const A8 &a8, const A9 &a9) { return kxi18ndcp(domain, context, singular, plural).subs(a1).subs(a2).subs(a3).subs(a4).subs(a5).subs(a6).subs(a7).subs(a8).subs(a9).toString(); } // <<<<< End of markup-aware context-plural calls with domain #endif // DOXYGEN_PREPROC #endif // KLOCALIZEDSTRING_H #ifndef DOXYGEN_PREPROC // Outside of include guards, to be able to map and unmap domains // by successive inclusions of this header // preceded with different definitions of TRANSLATION_DOMAIN. #ifdef TRANSLATION_DOMAIN #define i18n(...) i18nd(TRANSLATION_DOMAIN, __VA_ARGS__) #define i18nc(...) i18ndc(TRANSLATION_DOMAIN, __VA_ARGS__) #define i18np(...) i18ndp(TRANSLATION_DOMAIN, __VA_ARGS__) #define i18ncp(...) i18ndcp(TRANSLATION_DOMAIN, __VA_ARGS__) #define ki18n(...) ki18nd(TRANSLATION_DOMAIN, __VA_ARGS__) #define ki18nc(...) ki18ndc(TRANSLATION_DOMAIN, __VA_ARGS__) #define ki18np(...) ki18ndp(TRANSLATION_DOMAIN, __VA_ARGS__) #define ki18ncp(...) ki18ndcp(TRANSLATION_DOMAIN, __VA_ARGS__) #define tr2i18n(...) tr2i18nd(TRANSLATION_DOMAIN, __VA_ARGS__) #define xi18n(...) xi18nd(TRANSLATION_DOMAIN, __VA_ARGS__) #define xi18nc(...) xi18ndc(TRANSLATION_DOMAIN, __VA_ARGS__) #define xi18np(...) xi18ndp(TRANSLATION_DOMAIN, __VA_ARGS__) #define xi18ncp(...) xi18ndcp(TRANSLATION_DOMAIN, __VA_ARGS__) #define kxi18n(...) kxi18nd(TRANSLATION_DOMAIN, __VA_ARGS__) #define kxi18nc(...) kxi18ndc(TRANSLATION_DOMAIN, __VA_ARGS__) #define kxi18np(...) kxi18ndp(TRANSLATION_DOMAIN, __VA_ARGS__) #define kxi18ncp(...) kxi18ndcp(TRANSLATION_DOMAIN, __VA_ARGS__) #define tr2xi18n(...) tr2xi18nd(TRANSLATION_DOMAIN, __VA_ARGS__) #else #undef i18n #undef i18nc #undef i18np #undef i18ncp #undef ki18n #undef ki18nc #undef ki18np #undef ki18ncp #undef tr2i18n #undef xi18n #undef xi18nc #undef xi18np #undef xi18ncp #undef kxi18n #undef kxi18nc #undef kxi18np #undef kxi18ncp #undef tr2xi18n #endif #endif // DOXYGEN_PREPROC diff --git a/src/klocalizedtranslator.h b/src/klocalizedtranslator.h index cf64472..bbf89fb 100644 --- a/src/klocalizedtranslator.h +++ b/src/klocalizedtranslator.h @@ -1,108 +1,108 @@ /* * Copyright 2014 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef KLOCALIZEDTRANSLATOR_H #define KLOCALIZEDTRANSLATOR_H #include #include class KLocalizedTranslatorPrivate; /** * @brief A QTranslator using KLocalizedString for translations. * * This class allows to translate strings in Qt's translation system with KLocalizedString. * An example is the translation of a dynamically loaded user interface through QUILoader. * * To use this Translator install it in the QCoreApplication and provide the translation domain * to be used. The Translator can operate for multiple contexts, those needs to be specified. * * Example for translating a UI loaded through QUILoader: * @code * // create translator and install in QCoreApplication * KLocalizedTranslator *translator = new KLocalizedTranslator(this); * QCoreApplication::instance()->installTranslator(translator); * translator->setTranslationDomain(QStringLiteral("MyAppsDomain")); * * // create the QUILoader * QUiLoader *loader = new QUiLoader(this); * loader->setLanguageChangeEnabled(true); * * // load the UI * QFile uiFile(QStringLiteral("/path/to/userInterface.ui")); * uiFile.open(QFile::ReadOnly); * QWidget *loadedWidget = loader->load(&uiFile, this); * uiFile.close(); * * // the object name of the loaded UI is the context in this case * translator->addContextToMonitor(loadedWidget->objectName()); * * // send a LanguageChange event, this will re-translate using our translator * QEvent le(QEvent::LanguageChange); * QCoreApplication::sendEvent(loadedWidget, &le); * @endcode * * @since 5.0 **/ class KI18N_EXPORT KLocalizedTranslator : public QTranslator { Q_OBJECT public: - explicit KLocalizedTranslator(QObject *parent = 0); + explicit KLocalizedTranslator(QObject *parent = nullptr); virtual ~KLocalizedTranslator(); - QString translate(const char *context, const char *sourceText, const char *disambiguation = 0, int n = -1) const Q_DECL_OVERRIDE; + QString translate(const char *context, const char *sourceText, const char *disambiguation = nullptr, int n = -1) const Q_DECL_OVERRIDE; /** * Sets the @p translationDomain to be used. * * The translation domain is required. Without the translation domain any invocation of * translate() will be delegated to the base class. * * @param translationDomain The translation domain to be used. **/ void setTranslationDomain(const QString &translationDomain); /** * Adds a @p context for which this Translator should be active. * * The Translator only translates texts with a context matching one of the monitored contexts. * If the context is not monitored, the translate() method delegates to the base class. * * @param context The context for which the Translator should be active * * @see removeContextToMonitor **/ void addContextToMonitor(const QString &context); /** * Stop translating for the given @p context. * * @param context The context for which the Translator should no longer be active * * @see addContextToMonitor **/ void removeContextToMonitor(const QString &context); private: const QScopedPointer d; }; #endif //KLOCALIZEDTRANSLATOR_H diff --git a/src/ktranscript.cpp b/src/ktranscript.cpp index 7f9c671..67f1434 100644 --- a/src/ktranscript.cpp +++ b/src/ktranscript.cpp @@ -1,1692 +1,1692 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Chusslove Illich Copyright (C) 2014 Kevin Krammer 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. */ #include #include #include //#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KTranscriptImp; class Scriptface; typedef QHash TsConfigGroup; typedef QHash TsConfig; // Transcript implementation (used as singleton). class KTranscriptImp : public KTranscript { public: KTranscriptImp(); ~KTranscriptImp(); QString eval(const QList &argv, const QString &lang, const QString &ctry, const QString &msgctxt, const QHash &dynctxt, const QString &msgid, const QStringList &subs, const QList &vals, const QString &ftrans, QList &mods, QString &error, bool &fallback) Q_DECL_OVERRIDE; QStringList postCalls(const QString &lang) Q_DECL_OVERRIDE; // Lexical path of the module for the executing code. QString currentModulePath; private: void loadModules(const QList &mods, QString &error); void setupInterpreter(const QString &lang); TsConfig config; QHash m_sface; }; // Script-side transcript interface. class Scriptface : public QObject, public QScriptable { Q_OBJECT public: - explicit Scriptface(const TsConfigGroup &config, QObject *parent = 0); + explicit Scriptface(const TsConfigGroup &config, QObject *parent = nullptr); ~Scriptface(); // Interface functions. Q_INVOKABLE QScriptValue load(); // actually has variable length argument list Q_INVOKABLE QScriptValue setcall(const QScriptValue &name, const QScriptValue &func, const QScriptValue &fval = QScriptValue::NullValue); Q_INVOKABLE QScriptValue hascall(const QScriptValue &name); Q_INVOKABLE QScriptValue acall(); // actually has variable length argument list Q_INVOKABLE QScriptValue setcallForall(const QScriptValue &name, const QScriptValue &func, const QScriptValue &fval = QScriptValue::NullValue); Q_INVOKABLE QScriptValue fallback(); Q_INVOKABLE QScriptValue nsubs(); Q_INVOKABLE QScriptValue subs(const QScriptValue &index); Q_INVOKABLE QScriptValue vals(const QScriptValue &index); Q_INVOKABLE QScriptValue msgctxt(); Q_INVOKABLE QScriptValue dynctxt(const QScriptValue &key); Q_INVOKABLE QScriptValue msgid(); Q_INVOKABLE QScriptValue msgkey(); Q_INVOKABLE QScriptValue msgstrf(); Q_INVOKABLE QScriptValue dbgputs(const QScriptValue &str); Q_INVOKABLE QScriptValue warnputs(const QScriptValue &str); Q_INVOKABLE QScriptValue localeCountry(); Q_INVOKABLE QScriptValue normKey(const QScriptValue &phrase); Q_INVOKABLE QScriptValue loadProps(); // actually has variable length argument list Q_INVOKABLE QScriptValue getProp(const QScriptValue &phrase, const QScriptValue &prop); Q_INVOKABLE QScriptValue setProp(const QScriptValue &phrase, const QScriptValue &prop, const QScriptValue &value); Q_INVOKABLE QScriptValue toUpperFirst(const QScriptValue &str, const QScriptValue &nalt = QScriptValue::NullValue); Q_INVOKABLE QScriptValue toLowerFirst(const QScriptValue &str, const QScriptValue &nalt = QScriptValue::NullValue); Q_INVOKABLE QScriptValue getConfString(const QScriptValue &key, const QScriptValue &dval = QScriptValue::NullValue); Q_INVOKABLE QScriptValue getConfBool(const QScriptValue &key, const QScriptValue &dval = QScriptValue::NullValue); Q_INVOKABLE QScriptValue getConfNumber(const QScriptValue &key, const QScriptValue &dval = QScriptValue::NullValue); // Helper methods to interface functions. QScriptValue load(const QScriptValueList &fnames); QString loadProps_text(const QString &fpath); QString loadProps_bin(const QString &fpath); QString loadProps_bin_00(const QString &fpath); QString loadProps_bin_01(const QString &fpath); void put(const QString &propertyName, const QScriptValue &value); // Link to its script engine QScriptEngine *const scriptEngine; // Current message data. const QString *msgcontext; const QHash *dyncontext; const QString *msgId; const QStringList *subList; const QList *valList; const QString *ftrans; const QString *ctry; // Fallback request handle. bool *fallbackRequest; // Function register. QHash funcs; QHash fvals; QHash fpaths; // Ordering of those functions which execute for all messages. QList nameForalls; // Property values per phrase (used by *Prop interface calls). // Not QStrings, in order to avoid conversion from UTF-8 when // loading compiled maps (less latency on startup). QHash > phraseProps; // Unresolved property values per phrase, // containing the pointer to compiled pmap file handle and offset in it. QHash > phraseUnparsedProps; QHash resolveUnparsedProps(const QByteArray &phrase); // Set of loaded pmap files by paths and file handle pointers. QSet loadedPmapPaths; QSet loadedPmapHandles; // User config. TsConfigGroup config; }; // ---------------------------------------------------------------------- // Custom debug and warning output (kdebug not available) #define DBGP "KTranscript: " void dbgout(const char *str) { #ifndef NDEBUG fprintf(stderr, DBGP"%s\n", str); #else Q_UNUSED(str); #endif } template void dbgout(const char *str, const T1 &a1) { #ifndef NDEBUG fprintf(stderr, DBGP"%s\n", QString::fromUtf8(str).arg(a1).toLocal8Bit().data()); #else Q_UNUSED(str); Q_UNUSED(a1); #endif } template void dbgout(const char *str, const T1 &a1, const T2 &a2) { #ifndef NDEBUG fprintf(stderr, DBGP"%s\n", QString::fromUtf8(str).arg(a1).arg(a2).toLocal8Bit().data()); #else Q_UNUSED(str); Q_UNUSED(a1); Q_UNUSED(a2); #endif } template void dbgout(const char *str, const T1 &a1, const T2 &a2, const T3 &a3) { #ifndef NDEBUG fprintf(stderr, DBGP"%s\n", QString::fromUtf8(str).arg(a1).arg(a2).arg(a3).toLocal8Bit().data()); #else Q_UNUSED(str); Q_UNUSED(a1); Q_UNUSED(a2); Q_UNUSED(a3); #endif } #define WARNP "KTranscript: " void warnout(const char *str) { fprintf(stderr, WARNP"%s\n", str); } template void warnout(const char *str, const T1 &a1) { fprintf(stderr, WARNP"%s\n", QString::fromUtf8(str).arg(a1).toLocal8Bit().data()); } // ---------------------------------------------------------------------- // Produces a string out of a script exception. QString expt2str(QScriptEngine *engine) { const QScriptValue expt = engine->uncaughtException(); if (expt.isObject()) { const QScriptValue message = expt.toObject().property(QStringLiteral("message")); if (message.isValid()) { return QStringLiteral("Error: %1").arg(message.toString()); } } QString strexpt = expt.toString(); return QStringLiteral("Caught exception: %1").arg(strexpt); } // ---------------------------------------------------------------------- // Count number of lines in the string, // up to and excluding the requested position. int countLines(const QString &s, int p) { int n = 1; int len = s.length(); for (int i = 0; i < p && i < len; ++i) { if (s[i] == QLatin1Char('\n')) { ++n; } } return n; } // ---------------------------------------------------------------------- // Normalize string key for hash lookups, QByteArray normKeystr(const QString &raw, bool mayHaveAcc = true) { // NOTE: Regexes should not be used here for performance reasons. // This function may potentially be called thousands of times // on application startup. QString key = raw; // Strip all whitespace. int len = key.length(); QString nkey; for (int i = 0; i < len; ++i) { QChar c = key[i]; if (!c.isSpace()) { nkey.append(c); } } key = nkey; // Strip accelerator marker. if (mayHaveAcc) { key = removeAcceleratorMarker(key); } // Convert to lower case. key = key.toLower(); return key.toUtf8(); } // ---------------------------------------------------------------------- // Trim multiline string in a "smart" way: // Remove leading and trailing whitespace up to and including first // newline from that side, if there is one; otherwise, don't touch. QString trimSmart(const QString &raw) { // NOTE: This could be done by a single regex, but is not due to // performance reasons. // This function may potentially be called thousands of times // on application startup. int len = raw.length(); int is = 0; while (is < len && raw[is].isSpace() && raw[is] != QLatin1Char('\n')) { ++is; } if (is >= len || raw[is] != QLatin1Char('\n')) { is = -1; } int ie = len - 1; while (ie >= 0 && raw[ie].isSpace() && raw[ie] != QLatin1Char('\n')) { --ie; } if (ie < 0 || raw[ie] != QLatin1Char('\n')) { ie = len; } return raw.mid(is + 1, ie - is - 1); } // ---------------------------------------------------------------------- // Produce a JavaScript object out of Qt variant. QScriptValue variantToJsValue(const QVariant &val) { QVariant::Type vtype = val.type(); if (vtype == QVariant::String) { return QScriptValue(val.toString()); } else if (vtype == QVariant::Bool) { return QScriptValue(val.toBool()); } else if (vtype == QVariant::Double || vtype == QVariant::Int || vtype == QVariant::UInt || vtype == QVariant::LongLong || vtype == QVariant::ULongLong) { return QScriptValue(val.toDouble()); } else { return QScriptValue::UndefinedValue; } } // ---------------------------------------------------------------------- // Parse ini-style config file, // returning content as hash of hashes by group and key. // Parsing is not fussy, it will read what it can. TsConfig readConfig(const QString &fname) { TsConfig config; // Add empty group. TsConfig::iterator configGroup; configGroup = config.insert(QString(), TsConfigGroup()); QFile file(fname); if (!file.open(QIODevice::ReadOnly)) { return config; } QTextStream stream(&file); stream.setCodec("UTF-8"); while (!stream.atEnd()) { QString line = stream.readLine(); int p1, p2; // Remove comment from the line. p1 = line.indexOf(QLatin1Char('#')); if (p1 >= 0) { line.truncate(p1); } line = line.trimmed(); if (line.isEmpty()) { continue; } if (line[0] == QLatin1Char('[')) { // Group switch. p1 = 0; p2 = line.indexOf(QLatin1Char(']'), p1 + 1); if (p2 < 0) { continue; } QString group = line.mid(p1 + 1, p2 - p1 - 1).trimmed(); configGroup = config.find(group); if (configGroup == config.end()) { // Add new group. configGroup = config.insert(group, TsConfigGroup()); } } else { // Field. p1 = line.indexOf(QLatin1Char('=')); if (p1 < 0) { continue; } QStringRef field = line.leftRef(p1).trimmed(); QStringRef value = line.midRef(p1 + 1).trimmed(); if (!field.isEmpty()) { (*configGroup)[field.toString()] = value.toString(); } } } file.close(); return config; } // ---------------------------------------------------------------------- // throw or log error, depending on context availability static QScriptValue throwError(QScriptContext *context, QScriptContext::Error errorCode, const QString &message) { if (context) { return context->throwError(errorCode, message); } qCritical() << "Script error" << errorCode << ":" << message; return QScriptValue::UndefinedValue; } // ---------------------------------------------------------------------- // convert variable number of call arguments to QScriptValueList static QScriptValueList callArgsFromContext(QScriptContext *context) { QScriptValueList list; if (context) { list.reserve(context->argumentCount()); for (int i = 0; i < context->argumentCount(); ++i) { list << context->argument(i); } } return list; } #ifdef KTRANSCRIPT_TESTBUILD // ---------------------------------------------------------------------- // Test build creation/destruction hooks -static KTranscriptImp *s_transcriptInstance = 0; +static KTranscriptImp *s_transcriptInstance = nullptr; KTranscriptImp *globalKTI() { return s_transcriptInstance; } KTranscript *autotestCreateKTranscriptImp() { - Q_ASSERT(s_transcriptInstance == 0); + Q_ASSERT(s_transcriptInstance == nullptr); s_transcriptInstance = new KTranscriptImp; return s_transcriptInstance; } void autotestDestroyKTranscriptImp() { - Q_ASSERT(s_transcriptInstance != 0); + Q_ASSERT(s_transcriptInstance != nullptr); delete s_transcriptInstance; - s_transcriptInstance = 0; + s_transcriptInstance = nullptr; } #else // ---------------------------------------------------------------------- // Dynamic loading. Q_GLOBAL_STATIC(KTranscriptImp, globalKTI) extern "C" { KTRANSCRIPT_EXPORT KTranscript *load_transcript() { return globalKTI(); } } #endif // ---------------------------------------------------------------------- // KTranscript definitions. KTranscriptImp::KTranscriptImp() { // Load user configuration. QString tsConfigPath = QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("ktranscript.ini")); if (tsConfigPath.isEmpty()) { tsConfigPath = QDir::homePath() + QLatin1Char('/') + QLatin1String(".transcriptrc"); } config = readConfig(tsConfigPath); } KTranscriptImp::~KTranscriptImp() { qDeleteAll(m_sface); } QString KTranscriptImp::eval(const QList &argv, const QString &lang, const QString &ctry, const QString &msgctxt, const QHash &dynctxt, const QString &msgid, const QStringList &subs, const QList &vals, const QString &ftrans, QList &mods, QString &error, bool &fallback) { //error = "debug"; return QString(); error.clear(); // empty error message means successful evaluation fallback = false; // fallback not requested #if 0 // FIXME: Maybe not needed, as QScriptEngine has no native outside access? // Unportable (needs unistd.h)? // If effective user id is root and real user id is not root. if (geteuid() == 0 && getuid() != 0) { // Since scripts are user input, and the program is running with // root permissions while real user is not root, do not invoke // scripting at all, to prevent exploits. error = "Security block: trying to execute a script in suid environment."; return QString(); } #endif // Load any new modules and clear the list. if (!mods.isEmpty()) { loadModules(mods, error); mods.clear(); if (!error.isEmpty()) { return QString(); } } // Add interpreters for new languages. // (though it should never happen here, but earlier when loading modules; // this also means there are no calls set, so the unregistered call error // below will be reported). if (!m_sface.contains(lang)) { setupInterpreter(lang); } // Shortcuts. Scriptface *sface = m_sface[lang]; QScriptEngine *engine = sface->scriptEngine; QScriptValue gobj = engine->globalObject(); // Link current message data for script-side interface. sface->msgcontext = &msgctxt; sface->dyncontext = &dynctxt; sface->msgId = &msgid; sface->subList = &subs; sface->valList = &vals; sface->ftrans = &ftrans; sface->fallbackRequest = &fallback; sface->ctry = &ctry; // Find corresponding JS function. int argc = argv.size(); if (argc < 1) { //error = "At least the call name must be supplied."; // Empty interpolation is OK, possibly used just to initialize // at a given point (e.g. for Ts.setForall() to start having effect). return QString(); } QString funcName = argv[0].toString(); if (!sface->funcs.contains(funcName)) { error = QStringLiteral("Unregistered call to '%1'.").arg(funcName); return QString(); } QScriptValue func = sface->funcs[funcName]; QScriptValue fval = sface->fvals[funcName]; // Recover module path from the time of definition of this call, // for possible load calls. currentModulePath = sface->fpaths[funcName]; // Execute function. QScriptValueList arglist; arglist.reserve(argc-1); for (int i = 1; i < argc; ++i) { arglist.append(variantToJsValue(argv[i])); } QScriptValue val; if (fval.isObject()) { val = func.call(fval.toObject(), arglist); } else { // no object associated to this function, use global val = func.call(gobj, arglist); } if (fallback) { // Fallback to ordinary translation requested. // Possibly clear exception state. if (engine->hasUncaughtException()) { engine->clearExceptions(); } return QString(); } else if (!engine->hasUncaughtException()) { // Evaluation successful. if (val.isString()) { // Good to go. return val.toString(); } else { // Accept only strings. QString strval = val.toString(); error = QStringLiteral("Non-string return value: %1").arg(strval); return QString(); } } else { // Exception raised. error = expt2str(engine); engine->clearExceptions(); return QString(); } } QStringList KTranscriptImp::postCalls(const QString &lang) { // Return no calls if scripting was not already set up for this language. // NOTE: This shouldn't happen, as postCalls cannot be called in such case. if (!m_sface.contains(lang)) { return QStringList(); } // Shortcuts. Scriptface *sface = m_sface[lang]; return sface->nameForalls; } void KTranscriptImp::loadModules(const QList &mods, QString &error) { QList modErrors; foreach (const QStringList &mod, mods) { QString mpath = mod[0]; QString mlang = mod[1]; // Add interpreters for new languages. if (!m_sface.contains(mlang)) { setupInterpreter(mlang); } // Setup current module path for loading submodules. // (sort of closure over invocations of loadf) int posls = mpath.lastIndexOf(QLatin1Char('/')); if (posls < 1) { modErrors.append(QStringLiteral( "Funny module path '%1', skipping.").arg(mpath)); continue; } currentModulePath = mpath.left(posls); QString fname = mpath.mid(posls + 1); // Scriptface::loadf() wants no extension on the filename fname = fname.left(fname.lastIndexOf(QLatin1Char('.'))); // Load the module. QScriptEngine *engine = m_sface[mlang]->scriptEngine; QScriptValueList alist; alist.append(QScriptValue(fname)); m_sface[mlang]->load(alist); // Handle any exception. if (engine->hasUncaughtException()) { modErrors.append(expt2str(engine)); engine->clearExceptions(); } } // Unset module path. currentModulePath.clear(); foreach (const QString &merr, modErrors) { error.append(merr + QLatin1Char('\n')); } } #define SFNAME "Ts" void KTranscriptImp::setupInterpreter(const QString &lang) { // Add scripting interface // Creates its own script engine and registers with it // NOTE: Config may not contain an entry for the language, in which case // it is automatically constructed as an empty hash. This is intended. Scriptface *sface = new Scriptface(config[lang]); // Store scriptface m_sface[lang] = sface; //dbgout("=====> Created interpreter for '%1'", lang); } Scriptface::Scriptface(const TsConfigGroup &config_, QObject *parent) - : QObject(parent), scriptEngine(new QScriptEngine(this)), fallbackRequest(0), config(config_) + : QObject(parent), scriptEngine(new QScriptEngine(this)), fallbackRequest(nullptr), config(config_) { QScriptEngine::QObjectWrapOptions wrapOptions; wrapOptions |= QScriptEngine::ExcludeSuperClassContents; wrapOptions |= QScriptEngine::ExcludeDeleteLater; wrapOptions |= QScriptEngine::ExcludeChildObjects; wrapOptions |= QScriptEngine::ExcludeSlots; QScriptValue object = scriptEngine->newQObject(this, QScriptEngine::QtOwnership, wrapOptions); scriptEngine->globalObject().setProperty(QStringLiteral(SFNAME), object); } Scriptface::~Scriptface() { qDeleteAll(loadedPmapHandles); } void Scriptface::put(const QString &propertyName, const QScriptValue &value) { QScriptValue internalObject = scriptEngine->globalObject().property(QStringLiteral("ScriptfaceInternal")); if (!internalObject.isValid()) { internalObject = scriptEngine->newObject(); scriptEngine->globalObject().setProperty(QStringLiteral("ScriptfaceInternal"), internalObject); } internalObject.setProperty(propertyName, value); } // ---------------------------------------------------------------------- // Scriptface interface functions. #ifdef _MSC_VER // Work around bizarre MSVC (2013) bug preventing use of QStringLiteral for concatenated string literals # define SPREF(X) QString::fromLatin1(SFNAME "." X) #else # define SPREF(X) QStringLiteral(SFNAME "." X) #endif QScriptValue Scriptface::load() { const QScriptValueList fnames = callArgsFromContext(context()); return load(fnames); } QScriptValue Scriptface::setcall(const QScriptValue &name, const QScriptValue &func, const QScriptValue &fval) { if (!name.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("setcall: expected string as first argument")); } if (!func.isFunction()) { return throwError(context(), QScriptContext::TypeError, SPREF("setcall: expected function as second argument")); } if (!(fval.isObject() || fval.isNull())) { return throwError(context(), QScriptContext::TypeError, SPREF("setcall: expected object or null as third argument")); } QString qname = name.toString(); funcs[qname] = func; fvals[qname] = fval; // Register values to keep GC from collecting them. Is this needed? put(QStringLiteral("#:f<%1>").arg(qname), func); put(QStringLiteral("#:o<%1>").arg(qname), fval); // Set current module path as module path for this call, // in case it contains load subcalls. fpaths[qname] = globalKTI()->currentModulePath; return QScriptValue::UndefinedValue; } QScriptValue Scriptface::hascall(const QScriptValue &name) { if (!name.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("hascall: expected string as first argument")); } QString qname = name.toString(); return QScriptValue(funcs.contains(qname)); } QScriptValue Scriptface::acall() { const QScriptValueList argv = callArgsFromContext(context()); if (argv.size() < 1) { return throwError(context(), QScriptContext::SyntaxError, SPREF("acall: expected at least one argument (call name)")); } if (!argv[0].isString()) { return throwError(context(), QScriptContext::SyntaxError, SPREF("acall: expected string as first argument (call name)")); } // Get the function and its context object. QString callname = argv[0].toString(); if (!funcs.contains(callname)) { return throwError(context(), QScriptContext::ReferenceError, SPREF("acall: unregistered call to '%1'").arg(callname)); } QScriptValue func = funcs[callname]; QScriptValue fval = fvals[callname]; // Recover module path from the time of definition of this call, // for possible load calls. globalKTI()->currentModulePath = fpaths[callname]; // Execute function. QScriptValueList arglist; arglist.reserve(argv.size()-1); for (int i = 1; i < argv.size(); ++i) { arglist.append(argv[i]); } QScriptValue val; if (fval.isObject()) { // Call function with the context object. val = func.call(fval.toObject(), arglist); } else { // No context object associated to this function, use global. val = func.call(scriptEngine->globalObject(), arglist); } return val; } QScriptValue Scriptface::setcallForall(const QScriptValue &name, const QScriptValue &func, const QScriptValue &fval) { if (!name.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("setcallForall: expected string as first argument")); } if (!func.isFunction()) { return throwError(context(), QScriptContext::TypeError, SPREF("setcallForall: expected function as second argument")); } if (!(fval.isObject() || fval.isNull())) { return throwError(context(), QScriptContext::TypeError, SPREF("setcallForall: expected object or null as third argument")); } QString qname = name.toString(); funcs[qname] = func; fvals[qname] = fval; // Register values to keep GC from collecting them. Is this needed? put(QStringLiteral("#:fall<%1>").arg(qname), func); put(QStringLiteral("#:oall<%1>").arg(qname), fval); // Set current module path as module path for this call, // in case it contains load subcalls. fpaths[qname] = globalKTI()->currentModulePath; // Put in the queue order for execution on all messages. nameForalls.append(qname); return QScriptValue::UndefinedValue; } QScriptValue Scriptface::fallback() { if (fallbackRequest) { *fallbackRequest = true; } return QScriptValue::UndefinedValue; } QScriptValue Scriptface::nsubs() { return QScriptValue(subList->size()); } QScriptValue Scriptface::subs(const QScriptValue &index) { if (!index.isNumber()) { return throwError(context(), QScriptContext::TypeError, SPREF("subs: expected number as first argument")); } int i = qRound(index.toNumber()); if (i < 0 || i >= subList->size()) { return throwError(context(), QScriptContext::RangeError, SPREF("subs: index out of range")); } return QScriptValue(subList->at(i)); } QScriptValue Scriptface::vals(const QScriptValue &index) { if (!index.isNumber()) { return throwError(context(), QScriptContext::TypeError, SPREF("vals: expected number as first argument")); } int i = qRound(index.toNumber()); if (i < 0 || i >= valList->size()) { return throwError(context(), QScriptContext::RangeError, SPREF("vals: index out of range")); } return variantToJsValue(valList->at(i)); } QScriptValue Scriptface::msgctxt() { return QScriptValue(*msgcontext); } QScriptValue Scriptface::dynctxt(const QScriptValue &key) { if (!key.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("dynctxt: expected string as first argument")); } QString qkey = key.toString(); if (dyncontext->contains(qkey)) { return QScriptValue(dyncontext->value(qkey)); } return QScriptValue::UndefinedValue; } QScriptValue Scriptface::msgid() { return QScriptValue(*msgId); } QScriptValue Scriptface::msgkey() { return QScriptValue(QString(*msgcontext + QLatin1Char('|') + *msgId)); } QScriptValue Scriptface::msgstrf() { return QScriptValue(*ftrans); } QScriptValue Scriptface::dbgputs(const QScriptValue &str) { if (!str.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("dbgputs: expected string as first argument")); } QString qstr = str.toString(); dbgout("[JS-debug] %1", qstr); return QScriptValue::UndefinedValue; } QScriptValue Scriptface::warnputs(const QScriptValue &str) { if (!str.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("warnputs: expected string as first argument")); } QString qstr = str.toString(); warnout("[JS-warning] %1", qstr); return QScriptValue::UndefinedValue; } QScriptValue Scriptface::localeCountry() { return QScriptValue(*ctry); } QScriptValue Scriptface::normKey(const QScriptValue &phrase) { if (!phrase.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("normKey: expected string as argument")); } QByteArray nqphrase = normKeystr(phrase.toString()); return QScriptValue(QString::fromUtf8(nqphrase)); } QScriptValue Scriptface::loadProps() { const QScriptValueList fnames = callArgsFromContext(context()); if (globalKTI()->currentModulePath.isEmpty()) { return throwError(context(), QScriptContext::UnknownError, SPREF("loadProps: no current module path, aiiie...")); } for (int i = 0; i < fnames.size(); ++i) { if (!fnames[i].isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("loadProps: expected string as file name")); } } for (int i = 0; i < fnames.size(); ++i) { QString qfname = fnames[i].toString(); QString qfpath_base = globalKTI()->currentModulePath + QLatin1Char('/') + qfname; // Determine which kind of map is available. // Give preference to compiled map. QString qfpath = qfpath_base + QLatin1String(".pmapc"); bool haveCompiled = true; QFile file_check(qfpath); if (!file_check.open(QIODevice::ReadOnly)) { haveCompiled = false; qfpath = qfpath_base + QLatin1String(".pmap"); QFile file_check(qfpath); if (!file_check.open(QIODevice::ReadOnly)) { return throwError(context(), QScriptContext::UnknownError, SPREF("loadProps: cannot read map '%1'") .arg(qfpath)); } } file_check.close(); // Load from appropriate type of map. if (!loadedPmapPaths.contains(qfpath)) { QString errorString; if (haveCompiled) { errorString = loadProps_bin(qfpath); } else { errorString = loadProps_text(qfpath); } if (!errorString.isEmpty()) { return throwError(context(), QScriptContext::SyntaxError, errorString); } dbgout("Loaded property map: %1", qfpath); loadedPmapPaths.insert(qfpath); } } return QScriptValue::UndefinedValue; } QScriptValue Scriptface::getProp(const QScriptValue &phrase, const QScriptValue &prop) { if (!phrase.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("getProp: expected string as first argument")); } if (!prop.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("getProp: expected string as second argument")); } QByteArray qphrase = normKeystr(phrase.toString()); QHash props = phraseProps.value(qphrase); if (props.isEmpty()) { props = resolveUnparsedProps(qphrase); } if (!props.isEmpty()) { QByteArray qprop = normKeystr(prop.toString()); QByteArray qval = props.value(qprop); if (!qval.isEmpty()) { return QScriptValue(QString::fromUtf8(qval)); } } return QScriptValue::UndefinedValue; } QScriptValue Scriptface::setProp(const QScriptValue &phrase, const QScriptValue &prop, const QScriptValue &value) { if (!phrase.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("setProp: expected string as first argument")); } if (!prop.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("setProp: expected string as second argument")); } if (!value.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("setProp: expected string as third argument")); } QByteArray qphrase = normKeystr(phrase.toString()); QByteArray qprop = normKeystr(prop.toString()); QByteArray qvalue = value.toString().toUtf8(); // Any non-existent key in first or second-level hash will be created. phraseProps[qphrase][qprop] = qvalue; return QScriptValue::UndefinedValue; } static QString toCaseFirst(const QString &qstr, int qnalt, bool toupper) { static const QLatin1String head("~@"); static const int hlen = 2; //head.length() // If the first letter is found within an alternatives directive, // change case of the first letter in each of the alternatives. QString qstrcc = qstr; int len = qstr.length(); QChar altSep; int remainingAlts = 0; bool checkCase = true; int numChcased = 0; int i = 0; while (i < len) { QChar c = qstr[i]; if (qnalt && !remainingAlts && qstr.midRef(i, hlen) == head) { // An alternatives directive is just starting. i += 2; if (i >= len) { break; // malformed directive, bail out } // Record alternatives separator, set number of remaining // alternatives, reactivate case checking. altSep = qstrcc[i]; remainingAlts = qnalt; checkCase = true; } else if (remainingAlts && c == altSep) { // Alternative separator found, reduce number of remaining // alternatives and reactivate case checking. --remainingAlts; checkCase = true; } else if (checkCase && c.isLetter()) { // Case check is active and the character is a letter; change case. if (toupper) { qstrcc[i] = c.toUpper(); } else { qstrcc[i] = c.toLower(); } ++numChcased; // No more case checks until next alternatives separator. checkCase = false; } // If any letter has been changed, and there are no more alternatives // to be processed, we're done. if (numChcased > 0 && remainingAlts == 0) { break; } // Go to next character. ++i; } return qstrcc; } QScriptValue Scriptface::toUpperFirst(const QScriptValue &str, const QScriptValue &nalt) { if (!str.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("toUpperFirst: expected string as first argument")); } if (!(nalt.isNumber() || nalt.isNull())) { return throwError(context(), QScriptContext::TypeError, SPREF("toUpperFirst: expected number as second argument")); } QString qstr = str.toString(); int qnalt = nalt.isNull() ? 0 : nalt.toInteger(); QString qstruc = toCaseFirst(qstr, qnalt, true); return QScriptValue(qstruc); } QScriptValue Scriptface::toLowerFirst(const QScriptValue &str, const QScriptValue &nalt) { if (!str.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("toLowerFirst: expected string as first argument")); } if (!(nalt.isNumber() || nalt.isNull())) { return throwError(context(), QScriptContext::TypeError, SPREF("toLowerFirst: expected number as second argument")); } QString qstr = str.toString(); int qnalt = nalt.isNull() ? 0 : nalt.toInteger(); QString qstrlc = toCaseFirst(qstr, qnalt, false); return QScriptValue(qstrlc); } QScriptValue Scriptface::getConfString(const QScriptValue &key, const QScriptValue &dval) { if (!key.isString()) { return throwError(context(), QScriptContext::TypeError, QStringLiteral("getConfString: expected string as first argument")); } if (!(dval.isString() || dval.isNull())) { return throwError(context(), QScriptContext::TypeError, SPREF("getConfString: expected string as second argument (when given)")); } QString qkey = key.toString(); if (config.contains(qkey)) { return QScriptValue(config.value(qkey)); } return dval.isNull() ? QScriptValue::UndefinedValue : dval; } QScriptValue Scriptface::getConfBool(const QScriptValue &key, const QScriptValue &dval) { if (!key.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("getConfBool: expected string as first argument")); } if (!(dval.isBoolean() || dval.isNull())) { return throwError(context(), QScriptContext::TypeError, SPREF("getConfBool: expected boolean as second argument (when given)")); } static QStringList falsities; if (falsities.isEmpty()) { falsities.append(QString(QLatin1Char('0'))); falsities.append(QStringLiteral("no")); falsities.append(QStringLiteral("false")); } QString qkey = key.toString(); if (config.contains(qkey)) { QString qval = config.value(qkey).toLower(); return QScriptValue(!falsities.contains(qval)); } return dval.isNull() ? QScriptValue::UndefinedValue : dval; } QScriptValue Scriptface::getConfNumber(const QScriptValue &key, const QScriptValue &dval) { if (!key.isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("getConfNumber: expected string " "as first argument")); } if (!(dval.isNumber() || dval.isNull())) { return throwError(context(), QScriptContext::TypeError, SPREF("getConfNumber: expected number " "as second argument (when given)")); } QString qkey = key.toString(); if (config.contains(qkey)) { QString qval = config.value(qkey); bool convOk; double qnum = qval.toDouble(&convOk); if (convOk) { return QScriptValue(qnum); } } return dval.isNull() ? QScriptValue::UndefinedValue : dval; } // ---------------------------------------------------------------------- // Scriptface helpers to interface functions. QScriptValue Scriptface::load(const QScriptValueList &fnames) { if (globalKTI()->currentModulePath.isEmpty()) { return throwError(context(), QScriptContext::UnknownError, SPREF("load: no current module path, aiiie...")); } for (int i = 0; i < fnames.size(); ++i) { if (!fnames[i].isString()) { return throwError(context(), QScriptContext::TypeError, SPREF("load: expected string as file name")); } } for (int i = 0; i < fnames.size(); ++i) { QString qfname = fnames[i].toString(); QString qfpath = globalKTI()->currentModulePath + QLatin1Char('/') + qfname + QLatin1String(".js"); QFile file(qfpath); if (!file.open(QIODevice::ReadOnly)) { return throwError(context(), QScriptContext::UnknownError, SPREF("load: cannot read file '%1'") \ .arg(qfpath)); } QTextStream stream(&file); stream.setCodec("UTF-8"); QString source = stream.readAll(); file.close(); /** * fixup QScriptContext * else the nested evaluate will not work :/ * see http://www.qtcentre.org/threads/31027-QtScript-nesting-with-include-imports-or-spawned-script-engines * http://www.qtcentre.org/threads/20432-Can-I-include-a-script-from-script */ QScriptContext *currentContext = scriptEngine->currentContext(); QScriptContext *parentContext = currentContext->parentContext(); if (parentContext) { currentContext->setActivationObject(currentContext->parentContext()->activationObject()); currentContext->setThisObject(currentContext->parentContext()->thisObject()); } QScriptValue comp = scriptEngine->evaluate(source, qfpath, 0); if (comp.isError()) { QString msg = comp.toString(); QString line; if (comp.isObject()) { QScriptValue lval = comp.toObject().property(QStringLiteral("line")); if (lval.isNumber()) { line = QString::number(lval.toInt32()); } } return throwError(context(), QScriptContext::TypeError, QStringLiteral("at %1:%2: %3") .arg(qfpath, line, msg)); } dbgout("Loaded module: %1", qfpath); } return QScriptValue::UndefinedValue; } QString Scriptface::loadProps_text(const QString &fpath) { QFile file(fpath); if (!file.open(QIODevice::ReadOnly)) { return SPREF("loadProps_text: cannot read file '%1'") .arg(fpath); } QTextStream stream(&file); stream.setCodec("UTF-8"); QString s = stream.readAll(); file.close(); // Parse the map. // Should care about performance: possibly executed on each KDE // app startup and reading houndreds of thousands of characters. enum {s_nextEntry, s_nextKey, s_nextValue}; QList ekeys; // holds keys for current entry QHash props; // holds properties for current entry int slen = s.length(); int state = s_nextEntry; QByteArray pkey; QChar prop_sep, key_sep; int i = 0; while (1) { int i_checkpoint = i; if (state == s_nextEntry) { while (s[i].isSpace()) { ++i; if (i >= slen) { goto END_PROP_PARSE; } } if (i + 1 >= slen) { return SPREF("loadProps_text: unexpected end " "of file in %1").arg(fpath); } if (s[i] != QLatin1Char('#')) { // Separator characters for this entry. key_sep = s[i]; prop_sep = s[i + 1]; if (key_sep.isLetter() || prop_sep.isLetter()) { return SPREF("loadProps_text: separator " "characters must not be letters at %1:%2") .arg(fpath).arg(countLines(s, i)); } // Reset all data for current entry. ekeys.clear(); props.clear(); pkey.clear(); i += 2; state = s_nextKey; } else { // This is a comment, skip to EOL, don't change state. while (s[i] != QLatin1Char('\n')) { ++i; if (i >= slen) { goto END_PROP_PARSE; } } } } else if (state == s_nextKey) { int ip = i; // Proceed up to next key or property separator. while (s[i] != key_sep && s[i] != prop_sep) { ++i; if (i >= slen) { goto END_PROP_PARSE; } } if (s[i] == key_sep) { // This is a property key, // record for when the value gets parsed. pkey = normKeystr(s.mid(ip, i - ip), false); i += 1; state = s_nextValue; } else { // if (s[i] == prop_sep) { // This is an entry key, or end of entry. QByteArray ekey = normKeystr(s.mid(ip, i - ip), false); if (!ekey.isEmpty()) { // An entry key. ekeys.append(ekey); i += 1; state = s_nextKey; } else { // End of entry. if (ekeys.size() < 1) { return SPREF("loadProps_text: no entry key " "for entry ending at %1:%2") .arg(fpath).arg(countLines(s, i)); } // Add collected entry into global store, // once for each entry key (QHash implicitly shared). foreach (const QByteArray &ekey, ekeys) { phraseProps[ekey] = props; } i += 1; state = s_nextEntry; } } } else if (state == s_nextValue) { int ip = i; // Proceed up to next property separator. while (s[i] != prop_sep) { ++i; if (i >= slen) { goto END_PROP_PARSE; } if (s[i] == key_sep) { return SPREF("loadProps_text: property separator " "inside property value at %1:%2") .arg(fpath).arg(countLines(s, i)); } } // Extract the property value and store the property. QByteArray pval = trimSmart(s.mid(ip, i - ip)).toUtf8(); props[pkey] = pval; i += 1; state = s_nextKey; } else { return SPREF("loadProps: internal error 10 at %1:%2") .arg(fpath).arg(countLines(s, i)); } // To avoid infinite looping and stepping out. if (i == i_checkpoint || i >= slen) { return SPREF("loadProps: internal error 20 at %1:%2") .arg(fpath).arg(countLines(s, i)); } } END_PROP_PARSE: if (state != s_nextEntry) { return SPREF("loadProps: unexpected end of file in %1") .arg(fpath); } return QString(); } // Read big-endian integer of nbytes length at position pos // in character array fc of length len. // Update position to point after the number. // In case of error, pos is set to -1. template static int bin_read_int_nbytes(const char *fc, qlonglong len, qlonglong &pos, int nbytes) { if (pos + nbytes > len) { pos = -1; return 0; } T num = qFromBigEndian((uchar *) fc + pos); pos += nbytes; return num; } // Read 64-bit big-endian integer. static quint64 bin_read_int64(const char *fc, qlonglong len, qlonglong &pos) { return bin_read_int_nbytes(fc, len, pos, 8); } // Read 32-bit big-endian integer. static quint32 bin_read_int(const char *fc, qlonglong len, qlonglong &pos) { return bin_read_int_nbytes(fc, len, pos, 4); } // Read string at position pos of character array fc of length n. // String is represented as 32-bit big-endian byte length followed by bytes. // Update position to point after the string. // In case of error, pos is set to -1. static QByteArray bin_read_string(const char *fc, qlonglong len, qlonglong &pos) { // Binary format stores strings as length followed by byte sequence. // No null-termination. int nbytes = bin_read_int(fc, len, pos); if (pos < 0) { return QByteArray(); } if (nbytes < 0 || pos + nbytes > len) { pos = -1; return QByteArray(); } QByteArray s(fc + pos, nbytes); pos += nbytes; return s; } QString Scriptface::loadProps_bin(const QString &fpath) { QFile file(fpath); if (!file.open(QIODevice::ReadOnly)) { return SPREF("loadProps: cannot read file '%1'") .arg(fpath); } // Collect header. QByteArray head(8, '0'); file.read(head.data(), head.size()); file.close(); // Choose pmap loader based on header. if (head == "TSPMAP00") { return loadProps_bin_00(fpath); } else if (head == "TSPMAP01") { return loadProps_bin_01(fpath); } else { return SPREF("loadProps: unknown version of compiled map '%1'") .arg(fpath); } } QString Scriptface::loadProps_bin_00(const QString &fpath) { QFile file(fpath); if (!file.open(QIODevice::ReadOnly)) { return SPREF("loadProps: cannot read file '%1'") .arg(fpath); } QByteArray fctmp = file.readAll(); file.close(); const char *fc = fctmp.data(); const int fclen = fctmp.size(); // Indicates stream state. qlonglong pos = 0; // Match header. QByteArray head(fc, 8); pos += 8; if (head != "TSPMAP00") { goto END_PROP_PARSE; } // Read total number of entries. int nentries; nentries = bin_read_int(fc, fclen, pos); if (pos < 0) { goto END_PROP_PARSE; } // Read all entries. for (int i = 0; i < nentries; ++i) { // Read number of entry keys and all entry keys. QList ekeys; int nekeys = bin_read_int(fc, fclen, pos); if (pos < 0) { goto END_PROP_PARSE; } ekeys.reserve(nekeys); //nekeys are appended if data is not corrupted for (int j = 0; j < nekeys; ++j) { QByteArray ekey = bin_read_string(fc, fclen, pos); if (pos < 0) { goto END_PROP_PARSE; } ekeys.append(ekey); } //dbgout("--------> ekey[0]={%1}", QString::fromUtf8(ekeys[0])); // Read number of properties and all properties. QHash props; int nprops = bin_read_int(fc, fclen, pos); if (pos < 0) { goto END_PROP_PARSE; } for (int j = 0; j < nprops; ++j) { QByteArray pkey = bin_read_string(fc, fclen, pos); if (pos < 0) { goto END_PROP_PARSE; } QByteArray pval = bin_read_string(fc, fclen, pos); if (pos < 0) { goto END_PROP_PARSE; } props[pkey] = pval; } // Add collected entry into global store, // once for each entry key (QHash implicitly shared). foreach (const QByteArray &ekey, ekeys) { phraseProps[ekey] = props; } } END_PROP_PARSE: if (pos < 0) { return SPREF("loadProps: corrupt compiled map '%1'") .arg(fpath); } return QString(); } QString Scriptface::loadProps_bin_01(const QString &fpath) { QFile *file = new QFile(fpath); if (!file->open(QIODevice::ReadOnly)) { return SPREF("loadProps: cannot read file '%1'") .arg(fpath); } QByteArray fstr; qlonglong pos; // Read the header and number and length of entry keys. fstr = file->read(8 + 4 + 8); pos = 0; QByteArray head = fstr.left(8); pos += 8; if (head != "TSPMAP01") { return SPREF("loadProps: corrupt compiled map '%1'") .arg(fpath); } quint32 numekeys = bin_read_int(fstr, fstr.size(), pos); quint64 lenekeys = bin_read_int64(fstr, fstr.size(), pos); // Read entry keys. fstr = file->read(lenekeys); pos = 0; for (quint32 i = 0; i < numekeys; ++i) { QByteArray ekey = bin_read_string(fstr, lenekeys, pos); quint64 offset = bin_read_int64(fstr, lenekeys, pos); phraseUnparsedProps[ekey] = QPair(file, offset); } // // Read property keys. // ...when it becomes necessary loadedPmapHandles.insert(file); return QString(); } QHash Scriptface::resolveUnparsedProps(const QByteArray &phrase) { QPair ref = phraseUnparsedProps.value(phrase); QFile *file = ref.first; quint64 offset = ref.second; QHash props; if (file && file->seek(offset)) { QByteArray fstr = file->read(4 + 4); qlonglong pos = 0; quint32 numpkeys = bin_read_int(fstr, fstr.size(), pos); quint32 lenpkeys = bin_read_int(fstr, fstr.size(), pos); fstr = file->read(lenpkeys); pos = 0; for (quint32 i = 0; i < numpkeys; ++i) { QByteArray pkey = bin_read_string(fstr, lenpkeys, pos); QByteArray pval = bin_read_string(fstr, lenpkeys, pos); props[pkey] = pval; } phraseProps[phrase] = props; phraseUnparsedProps.remove(phrase); } return props; } #include "ktranscript.moc" diff --git a/src/kuitmarkup.cpp b/src/kuitmarkup.cpp index fce51f6..040b63e 100644 --- a/src/kuitmarkup.cpp +++ b/src/kuitmarkup.cpp @@ -1,1693 +1,1693 @@ /* This file is part of the KDE libraries Copyright (C) 2007, 2013 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #define QL1S(x) QLatin1String(x) #define QSL(x) QStringLiteral(x) #define QL1C(x) QLatin1Char(x) QString Kuit::escape(const QString &text) { int tlen = text.length(); QString ntext; ntext.reserve(tlen); for (int i = 0; i < tlen; ++i) { QChar c = text[i]; if (c == QL1C('&')) { ntext += QStringLiteral("&"); } else if (c == QL1C('<')) { ntext += QStringLiteral("<"); } else if (c == QL1C('>')) { ntext += QStringLiteral(">"); } else if (c == QL1C('\'')) { ntext += QStringLiteral("'"); } else if (c == QL1C('"')) { ntext += QStringLiteral("""); } else { ntext += c; } } return ntext; } // Truncates the string, for output of long messages. // (But don't truncate too much otherwise it's impossible to determine // which message is faulty if many messages have the same beginning). static QString shorten(const QString &str) { const int maxlen = 80; if (str.length() <= maxlen) { return str; } else { return str.leftRef(maxlen) + QSL("..."); } } static void parseUiMarker(const QString &context_, QString &roleName, QString &cueName, QString &formatName) { // UI marker is in the form @role:cue/format, // and must start just after any leading whitespace in the context string. QString context = context_.trimmed(); if (context.startsWith(QL1C('@'))) { // found UI marker static QRegExp staticWsRx(QStringLiteral("\\s")); QRegExp wsRx = staticWsRx; // QRegExp not thread-safe context = context.mid(1, wsRx.indexIn(context) - 1); // Possible format. int pfmt = context.indexOf(QL1C('/')); if (pfmt >= 0) { formatName = context.mid(pfmt + 1); context.truncate(pfmt); } // Possible subcue. int pcue = context.indexOf(QL1C(':')); if (pcue >= 0) { cueName = context.mid(pcue + 1); context.truncate(pcue); } // Role. roleName = context; } // Names remain untouched if marker was not found, which is fine. // Normalize names. roleName = roleName.trimmed().toLower(); cueName = cueName.trimmed().toLower(); formatName = formatName.trimmed().toLower(); } // Custom entity resolver for QXmlStreamReader. class KuitEntityResolver : public QXmlStreamEntityResolver { public: void setEntities(const QHash &entities) { entityMap = entities; } QString resolveUndeclaredEntity(const QString &name) Q_DECL_OVERRIDE { QString value = entityMap.value(name); // This will return empty string if the entity name is not known, // which will make QXmlStreamReader signal unknown entity error. return value; } private: QHash entityMap; }; namespace Kuit { enum Role { // UI marker roles UndefinedRole, ActionRole, TitleRole, OptionRole, LabelRole, ItemRole, InfoRole }; enum Cue { // UI marker subcues UndefinedCue, ButtonCue, InmenuCue, IntoolbarCue, WindowCue, MenuCue, TabCue, GroupCue, ColumnCue, RowCue, SliderCue, SpinboxCue, ListboxCue, TextboxCue, ChooserCue, CheckCue, RadioCue, InlistboxCue, IntableCue, InrangeCue, IntextCue, TooltipCue, WhatsthisCue, StatusCue, ProgressCue, TipofthedayCue, CreditCue, ShellCue }; } class KuitStaticData { public: QHash xmlEntities; QHash xmlEntitiesInverse; KuitEntityResolver xmlEntityResolver; QHash rolesByName; QHash cuesByName; QHash formatsByName; QHash namesByFormat; QHash > knownRoleCues; QHash comboKeyDelim; QHash guiPathDelim; QHash keyNames; QHash domainSetups; KuitStaticData(); void setXmlEntityData(); void setUiMarkerData(); void setTextTransformData(); QString toKeyCombo(const QStringList &languages, const QString &shstr, Kuit::VisualFormat format); QString toInterfacePath(const QStringList &languages, const QString &inpstr, Kuit::VisualFormat format); }; KuitStaticData::KuitStaticData() { setXmlEntityData(); setUiMarkerData(); setTextTransformData(); } void KuitStaticData::setXmlEntityData() { QString LT = QStringLiteral("lt"); QString GT = QStringLiteral("gt"); QString AMP = QStringLiteral("amp"); QString APOS = QStringLiteral("apos"); QString QUOT = QStringLiteral("quot"); // Default XML entities, direct and inverse mapping. xmlEntities[LT] = QString(QL1C('<')); xmlEntities[GT] = QString(QL1C('>')); xmlEntities[AMP] = QString(QL1C('&')); xmlEntities[APOS] = QString(QL1C('\'')); xmlEntities[QUOT] = QString(QL1C('"')); xmlEntitiesInverse[QString(QL1C('<'))] = LT; xmlEntitiesInverse[QString(QL1C('>'))] = GT; xmlEntitiesInverse[QString(QL1C('&'))] = AMP; xmlEntitiesInverse[QString(QL1C('\''))] = APOS; xmlEntitiesInverse[QString(QL1C('"'))] = QUOT; // Custom XML entities. xmlEntities[QStringLiteral("nbsp")] = QString(QChar(0xa0)); xmlEntityResolver.setEntities(xmlEntities); } void KuitStaticData::setUiMarkerData() { using namespace Kuit; // Role names and their available subcues. #undef SET_ROLE #define SET_ROLE(role, name, cues) do { \ rolesByName[name] = role; \ knownRoleCues[role] << cues; \ } while (0) SET_ROLE(ActionRole, QStringLiteral("action"), ButtonCue << InmenuCue << IntoolbarCue); SET_ROLE(TitleRole, QStringLiteral("title"), WindowCue << MenuCue << TabCue << GroupCue << ColumnCue << RowCue); SET_ROLE(LabelRole, QStringLiteral("label"), SliderCue << SpinboxCue << ListboxCue << TextboxCue << ChooserCue); SET_ROLE(OptionRole, QStringLiteral("option"), CheckCue << RadioCue); SET_ROLE(ItemRole, QStringLiteral("item"), InmenuCue << InlistboxCue << IntableCue << InrangeCue << IntextCue); SET_ROLE(InfoRole, QStringLiteral("info"), TooltipCue << WhatsthisCue << StatusCue << ProgressCue << TipofthedayCue << CreditCue << ShellCue); // Cue names. #undef SET_CUE #define SET_CUE(cue, name) do { \ cuesByName[name] = cue; \ } while (0) SET_CUE(ButtonCue, QStringLiteral("button")); SET_CUE(InmenuCue, QStringLiteral("inmenu")); SET_CUE(IntoolbarCue, QStringLiteral("intoolbar")); SET_CUE(WindowCue, QStringLiteral("window")); SET_CUE(MenuCue, QStringLiteral("menu")); SET_CUE(TabCue, QStringLiteral("tab")); SET_CUE(GroupCue, QStringLiteral("group")); SET_CUE(ColumnCue, QStringLiteral("column")); SET_CUE(RowCue, QStringLiteral("row")); SET_CUE(SliderCue, QStringLiteral("slider")); SET_CUE(SpinboxCue, QStringLiteral("spinbox")); SET_CUE(ListboxCue, QStringLiteral("listbox")); SET_CUE(TextboxCue, QStringLiteral("textbox")); SET_CUE(ChooserCue, QStringLiteral("chooser")); SET_CUE(CheckCue, QStringLiteral("check")); SET_CUE(RadioCue, QStringLiteral("radio")); SET_CUE(InlistboxCue, QStringLiteral("inlistbox")); SET_CUE(IntableCue, QStringLiteral("intable")); SET_CUE(InrangeCue, QStringLiteral("inrange")); SET_CUE(IntextCue, QStringLiteral("intext")); SET_CUE(TooltipCue, QStringLiteral("tooltip")); SET_CUE(WhatsthisCue, QStringLiteral("whatsthis")); SET_CUE(StatusCue, QStringLiteral("status")); SET_CUE(ProgressCue, QStringLiteral("progress")); SET_CUE(TipofthedayCue, QStringLiteral("tipoftheday")); SET_CUE(CreditCue, QStringLiteral("credit")); SET_CUE(ShellCue, QStringLiteral("shell")); // Format names. #undef SET_FORMAT #define SET_FORMAT(format, name) do { \ formatsByName[name] = format; \ namesByFormat[format] = name; \ } while (0) SET_FORMAT(UndefinedFormat, QStringLiteral("undefined")); SET_FORMAT(PlainText, QStringLiteral("plain")); SET_FORMAT(RichText, QStringLiteral("rich")); SET_FORMAT(TermText, QStringLiteral("term")); } void KuitStaticData::setTextTransformData() { // i18n: Decide which string is used to delimit keys in a keyboard // shortcut (e.g. + in Ctrl+Alt+Tab) in plain text. comboKeyDelim[Kuit::PlainText] = ki18nc("shortcut-key-delimiter/plain", "+"); comboKeyDelim[Kuit::TermText] = comboKeyDelim[Kuit::PlainText]; // i18n: Decide which string is used to delimit keys in a keyboard // shortcut (e.g. + in Ctrl+Alt+Tab) in rich text. comboKeyDelim[Kuit::RichText] = ki18nc("shortcut-key-delimiter/rich", "+"); // i18n: Decide which string is used to delimit elements in a GUI path // (e.g. -> in "Go to Settings->Advanced->Core tab.") in plain text. guiPathDelim[Kuit::PlainText] = ki18nc("gui-path-delimiter/plain", "→"); guiPathDelim[Kuit::TermText] = guiPathDelim[Kuit::PlainText]; // i18n: Decide which string is used to delimit elements in a GUI path // (e.g. -> in "Go to Settings->Advanced->Core tab.") in rich text. guiPathDelim[Kuit::RichText] = ki18nc("gui-path-delimiter/rich", "→"); // NOTE: The '→' glyph seems to be available in all widespread fonts. // Collect keyboard key names. #undef SET_KEYNAME #define SET_KEYNAME(rawname) do { \ /* Normalize key, trim and all lower-case. */ \ QString normname = QStringLiteral(rawname).trimmed().toLower(); \ keyNames[normname] = ki18nc("keyboard-key-name", rawname); \ } while (0) // Now we need I18NC_NOOP that does remove context. #undef I18NC_NOOP #define I18NC_NOOP(ctxt, msg) msg SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Alt")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "AltGr")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Backspace")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "CapsLock")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Control")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Ctrl")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Del")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Delete")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Down")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "End")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Enter")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Esc")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Escape")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Home")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Hyper")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Ins")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Insert")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Left")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Menu")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Meta")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "NumLock")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PageDown")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PageUp")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PgDown")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PgUp")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PauseBreak")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PrintScreen")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "PrtScr")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Return")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Right")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "ScrollLock")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Shift")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Space")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Super")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "SysReq")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Tab")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Up")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "Win")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F1")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F2")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F3")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F4")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F5")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F6")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F7")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F8")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F9")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F10")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F11")); SET_KEYNAME(I18NC_NOOP("keyboard-key-name", "F12")); // TODO: Add rest of the key names? } QString KuitStaticData::toKeyCombo(const QStringList &languages, const QString &shstr, Kuit::VisualFormat format) { // Take '+' or '-' as input shortcut delimiter, // whichever is first encountered. static QRegExp staticDelimRx(QStringLiteral("[+-]")); QRegExp delimRx = staticDelimRx; // QRegExp not thread-safe int p = delimRx.indexIn(shstr); // find delimiter QStringList keys; if (p < 0) { // single-key shortcut, no delimiter found keys.append(shstr); } else { // multi-key shortcut QChar oldDelim = shstr[p]; keys = shstr.split(oldDelim, QString::SkipEmptyParts); } for (int i = 0; i < keys.size(); ++i) { // Normalize key, trim and all lower-case. QString nkey = keys[i].trimmed().toLower(); keys[i] = keyNames.contains(nkey) ? keyNames[nkey].toString(languages) : keys[i].trimmed(); } QString delim = comboKeyDelim.value(format).toString(languages); return keys.join(delim); } QString KuitStaticData::toInterfacePath(const QStringList &languages, const QString &inpstr, Kuit::VisualFormat format) { // Take '/', '|' or "->" as input path delimiter, // whichever is first encountered. static QRegExp staticDelimRx(QStringLiteral("\\||->")); QRegExp delimRx = staticDelimRx; // QRegExp not thread-safe int p = delimRx.indexIn(inpstr); // find delimiter if (p < 0) { // single-element path, no delimiter found return inpstr; } else { // multi-element path QString oldDelim = delimRx.capturedTexts().at(0); QStringList guiels = inpstr.split(oldDelim, QString::SkipEmptyParts); QString delim = guiPathDelim.value(format).toString(languages); return guiels.join(delim); } } Q_GLOBAL_STATIC(KuitStaticData, staticData) static QString attributeSetKey(const QStringList &attribNames_) { QStringList attribNames = attribNames_; qSort(attribNames); QString key = QL1C('[') + attribNames.join(QL1C(' ')) + QL1C(']'); return key; } class KuitTag { public: QString name; Kuit::TagClass type; QSet knownAttribs; QHash > attributeOrders; QHash > patterns; QHash > formatters; int leadingNewlines; QString format(const QStringList &languages, const QHash &attributes, const QString &text, const QStringList &tagPath, Kuit::VisualFormat format) const; }; QString KuitTag::format(const QStringList &languages, const QHash &attributes, const QString &text, const QStringList &tagPath, Kuit::VisualFormat format) const { KuitStaticData *s = staticData(); QString formattedText = text; QString attribKey = attributeSetKey(attributes.keys()); if (patterns.contains(attribKey) && patterns.value(attribKey).contains(format)) { QString modText; Kuit::TagFormatter formatter = formatters.value(attribKey).value(format); - if (formatter != NULL) { + if (formatter != nullptr) { modText = formatter(languages, name, attributes, text, tagPath, format); } else { modText = text; } KLocalizedString aggText = patterns.value(attribKey).value(format); // line below is first-aid fix.for e.g. . // TODO: proper handling of boolean attributes still needed aggText = aggText.relaxSubs(); if (!aggText.isEmpty()) { aggText = aggText.subs(modText); QStringList attributeOrder = attributeOrders.value(attribKey).value(format); foreach (const QString &attribName, attributeOrder) { aggText = aggText.subs(attributes.value(attribName)); } formattedText = aggText.ignoreMarkup().toString(languages); } else { formattedText = modText; } } else if (patterns.contains(attribKey)) { qWarning() << QStringLiteral( "Undefined visual format for tag <%1> and attribute combination %2: %3.") .arg(name, attribKey, s->namesByFormat.value(format)); } else { qWarning() << QStringLiteral( "Undefined attribute combination for tag <%1>: %2.") .arg(name, attribKey); } return formattedText; } KuitSetup &Kuit::setupForDomain(const QByteArray& domain) { KuitStaticData *s = staticData(); KuitSetup *setup; if (s->domainSetups.contains(domain)) { setup = s->domainSetups.value(domain); } else { setup = new KuitSetup(domain); s->domainSetups.insert(domain, setup); } return *setup; } KuitSetup &Kuit::setupForDomain(const char *domain) { return setupForDomain(QByteArray(domain)); } class KuitSetupPrivate { public: void setTagPattern(const QString &tagName, const QStringList &attribNames, Kuit::VisualFormat format, const KLocalizedString &pattern, Kuit::TagFormatter formatter, int leadingNewlines); void setTagClass(const QString &tagName, Kuit::TagClass aClass); void setFormatForMarker(const QString &marker, Kuit::VisualFormat format); void setDefaultMarkup(); void setDefaultFormats(); QByteArray domain; QHash knownTags; QHash > formatsByRoleCue; }; void KuitSetupPrivate::setTagPattern(const QString &tagName, const QStringList &attribNames_, Kuit::VisualFormat format, const KLocalizedString &pattern, Kuit::TagFormatter formatter, int leadingNewlines_) { bool isNewTag = knownTags.contains(tagName); KuitTag &tag = knownTags[tagName]; if (isNewTag) { tag.name = tagName; tag.type = Kuit::PhraseTag; } QStringList attribNames = attribNames_; attribNames.removeAll(QString()); foreach (const QString &attribName, attribNames) { tag.knownAttribs.insert(attribName); } QString attribKey = attributeSetKey(attribNames); tag.attributeOrders[attribKey][format] = attribNames; tag.patterns[attribKey][format] = pattern; tag.formatters[attribKey][format] = formatter; tag.leadingNewlines = leadingNewlines_; } void KuitSetupPrivate::setTagClass(const QString &tagName, Kuit::TagClass aClass) { bool isNewTag = knownTags.contains(tagName); KuitTag &tag = knownTags[tagName]; if (isNewTag) { tag.name = tagName; } tag.type = aClass; } void KuitSetupPrivate::setFormatForMarker(const QString &marker, Kuit::VisualFormat format) { KuitStaticData *s = staticData(); QString roleName, cueName, formatName; parseUiMarker(marker, roleName, cueName, formatName); Kuit::Role role; if (s->rolesByName.contains(roleName)) { role = s->rolesByName.value(roleName); } else if (!roleName.isEmpty()) { qWarning() << QStringLiteral( "Unknown role '@%1' in UI marker {%2}, visual format not set.") .arg(roleName, marker); return; } else { qWarning() << QStringLiteral( "Empty role in UI marker {%1}, visual format not set.") .arg(marker); return; } Kuit::Cue cue; if (s->cuesByName.contains(cueName)) { cue = s->cuesByName.value(cueName); if (!s->knownRoleCues.value(role).contains(cue)) { qWarning() << QStringLiteral( "Subcue ':%1' does not belong to role '@%2' in UI marker {%3}, visual format not set.") .arg(cueName, roleName, marker); return; } } else if (!cueName.isEmpty()) { qWarning() << QStringLiteral( "Unknown subcue ':%1' in UI marker {%2}, visual format not set.") .arg(cueName, marker); return; } else { cue = Kuit::UndefinedCue; } formatsByRoleCue[role][cue] = format; } #define TAG_FORMATTER_ARGS \ const QStringList &languages, \ const QString &tagName, \ const QHash &attributes, \ const QString &text, \ const QStringList &tagPath, \ Kuit::VisualFormat format static QString tagFormatterFilename(TAG_FORMATTER_ARGS) { Q_UNUSED(languages); Q_UNUSED(tagName); Q_UNUSED(attributes); Q_UNUSED(tagPath); #ifdef Q_OS_WIN // with rich text the path can include ... which will be replaced by ...<\foo> on Windows! // the same problem also happens for tags such as
-> if (format == Kuit::RichText) { // replace all occurrences of "" to make sure toNativeSeparators() doesn't destroy XML markup const auto KUIT_CLOSE_XML_REPLACEMENT = QStringLiteral("__kuit_close_xml_tag__"); const auto KUIT_NOTEXT_XML_REPLACEMENT = QStringLiteral("__kuit_notext_xml_tag__"); QString result = text; result.replace(QStringLiteral(""), KUIT_NOTEXT_XML_REPLACEMENT); result = QDir::toNativeSeparators(result); result.replace(KUIT_CLOSE_XML_REPLACEMENT, QStringLiteral("")); return result; } #else Q_UNUSED(format); #endif return QDir::toNativeSeparators(text); } static QString tagFormatterShortcut(TAG_FORMATTER_ARGS) { Q_UNUSED(tagName); Q_UNUSED(attributes); Q_UNUSED(tagPath); KuitStaticData *s = staticData(); return s->toKeyCombo(languages, text, format); } static QString tagFormatterInterface(TAG_FORMATTER_ARGS) { Q_UNUSED(tagName); Q_UNUSED(attributes); Q_UNUSED(tagPath); KuitStaticData *s = staticData(); return s->toInterfacePath(languages, text, format); } void KuitSetupPrivate::setDefaultMarkup() { using namespace Kuit; const QString INTERNAL_TOP_TAG_NAME = QStringLiteral("__kuit_internal_top__"); const QString TITLE = QStringLiteral("title"); const QString EMPHASIS = QStringLiteral("emphasis"); const QString COMMAND = QStringLiteral("command"); const QString WARNING = QStringLiteral("warning"); const QString LINK = QStringLiteral("link"); const QString NOTE = QStringLiteral("note"); // Macro to hide message from extraction. #define HI18NC ki18nc // Macro to expedite setting the patterns. #undef SET_PATTERN #define SET_PATTERN(tagName, attribNames_, format, pattern, formatter, leadNl) \ do { \ QStringList attribNames; \ attribNames << attribNames_; \ setTagPattern(tagName, attribNames, format, pattern, formatter, leadNl); \ /* Make TermText pattern same as PlainText if not explicitly given. */ \ KuitTag &tag = knownTags[tagName]; \ QString attribKey = attributeSetKey(attribNames); \ if (format == PlainText && !tag.patterns[attribKey].contains(TermText)) { \ setTagPattern(tagName, attribNames, TermText, pattern, formatter, leadNl); \ } \ } while (0) // NOTE: The following "i18n:" comments are oddly placed in order that // xgettext extracts them properly. // -------> Internal top tag setTagClass(INTERNAL_TOP_TAG_NAME, StructTag); setTagClass(INTERNAL_TOP_TAG_NAME, StructTag); SET_PATTERN(INTERNAL_TOP_TAG_NAME, QString(), PlainText, HI18NC("tag-format-pattern <> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), - NULL, 0); + nullptr, 0); SET_PATTERN(INTERNAL_TOP_TAG_NAME, QString(), RichText, HI18NC("tag-format-pattern <> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), - NULL, 0); + nullptr, 0); // -------> Title setTagClass(TITLE, StructTag); SET_PATTERN(TITLE, QString(), PlainText, ki18nc("tag-format-pattern plain", // i18n: The messages with context "tag-format-pattern <tag ...> format" // are KUIT patterns for formatting the text found inside KUIT tags. // The format is either "plain" or "rich", and tells if the pattern // is used for plain text or rich text (which can use HTML tags). // You may be in general satisfied with the patterns as they are in the // original. Some things you may consider changing: // - the proper quotes, those used in msgid are English-standard // - the <i> and <b> tags, does your language script work well with them? "== %1 =="), - NULL, 2); + nullptr, 2); SET_PATTERN(TITLE, QString(), RichText, ki18nc("tag-format-pattern <title> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<h2>%1</h2>"), - NULL, 2); + nullptr, 2); // -------> Subtitle setTagClass(QSL("subtitle"), StructTag); SET_PATTERN(QSL("subtitle"), QString(), PlainText, ki18nc("tag-format-pattern <subtitle> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "~ %1 ~"), - NULL, 2); + nullptr, 2); SET_PATTERN(QSL("subtitle"), QString(), RichText, ki18nc("tag-format-pattern <subtitle> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<h3>%1</h3>"), - NULL, 2); + nullptr, 2); // -------> Para setTagClass(QSL("para"), StructTag); SET_PATTERN(QSL("para"), QString(), PlainText, ki18nc("tag-format-pattern <para> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), - NULL, 2); + nullptr, 2); SET_PATTERN(QSL("para"), QString(), RichText, ki18nc("tag-format-pattern <para> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<p>%1</p>"), - NULL, 2); + nullptr, 2); // -------> List setTagClass(QSL("list"), StructTag); SET_PATTERN(QSL("list"), QString(), PlainText, ki18nc("tag-format-pattern <list> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), - NULL, 1); + nullptr, 1); SET_PATTERN(QSL("list"), QString(), RichText, ki18nc("tag-format-pattern <list> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<ul>%1</ul>"), - NULL, 1); + nullptr, 1); // -------> Item setTagClass(QSL("item"), StructTag); SET_PATTERN(QSL("item"), QString(), PlainText, ki18nc("tag-format-pattern <item> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. " * %1"), - NULL, 1); + nullptr, 1); SET_PATTERN(QSL("item"), QString(), RichText, ki18nc("tag-format-pattern <item> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<li>%1</li>"), - NULL, 1); + nullptr, 1); // -------> Note SET_PATTERN(NOTE, QString(), PlainText, ki18nc("tag-format-pattern <note> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "Note: %1"), - NULL, 0); + nullptr, 0); SET_PATTERN(NOTE, QString(), RichText, ki18nc("tag-format-pattern <note> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<i>Note</i>: %1"), - NULL, 0); + nullptr, 0); SET_PATTERN(NOTE, QSL("label"), PlainText, ki18nc("tag-format-pattern <note label=> plain\n" "%1 is the text, %2 is the note label", // i18n: KUIT pattern, see the comment to the first of these entries above. "%2: %1"), - NULL, 0); + nullptr, 0); SET_PATTERN(NOTE, QSL("label"), RichText, ki18nc("tag-format-pattern <note label=> rich\n" "%1 is the text, %2 is the note label", // i18n: KUIT pattern, see the comment to the first of these entries above. "<i>%2</i>: %1"), - NULL, 0); + nullptr, 0); // -------> Warning SET_PATTERN(WARNING, QString(), PlainText, ki18nc("tag-format-pattern <warning> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "WARNING: %1"), - NULL, 0); + nullptr, 0); SET_PATTERN(WARNING, QString(), RichText, ki18nc("tag-format-pattern <warning> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<b>Warning</b>: %1"), - NULL, 0); + nullptr, 0); SET_PATTERN(WARNING, QSL("label"), PlainText, ki18nc("tag-format-pattern <warning label=> plain\n" "%1 is the text, %2 is the warning label", // i18n: KUIT pattern, see the comment to the first of these entries above. "%2: %1"), - NULL, 0); + nullptr, 0); SET_PATTERN(WARNING, QSL("label"), RichText, ki18nc("tag-format-pattern <warning label=> rich\n" "%1 is the text, %2 is the warning label", // i18n: KUIT pattern, see the comment to the first of these entries above. "<b>%2</b>: %1"), - NULL, 0); + nullptr, 0); // -------> Link SET_PATTERN(LINK, QString(), PlainText, ki18nc("tag-format-pattern <link> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), - NULL, 0); + nullptr, 0); SET_PATTERN(LINK, QString(), RichText, ki18nc("tag-format-pattern <link> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<a href=\"%1\">%1</a>"), - NULL, 0); + nullptr, 0); SET_PATTERN(LINK, QSL("url"), PlainText, ki18nc("tag-format-pattern <link url=> plain\n" "%1 is the descriptive text, %2 is the URL", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1 (%2)"), - NULL, 0); + nullptr, 0); SET_PATTERN(LINK, QSL("url"), RichText, ki18nc("tag-format-pattern <link url=> rich\n" "%1 is the descriptive text, %2 is the URL", // i18n: KUIT pattern, see the comment to the first of these entries above. "<a href=\"%2\">%1</a>"), - NULL, 0); + nullptr, 0); // -------> Filename SET_PATTERN(QSL("filename"), QString(), PlainText, ki18nc("tag-format-pattern <filename> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "‘%1’"), tagFormatterFilename, 0); SET_PATTERN(QSL("filename"), QString(), RichText, ki18nc("tag-format-pattern <filename> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<tt>%1</tt>"), tagFormatterFilename, 0); // -------> Application SET_PATTERN(QSL("application"), QString(), PlainText, ki18nc("tag-format-pattern <application> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), - NULL, 0); + nullptr, 0); SET_PATTERN(QSL("application"), QString(), RichText, ki18nc("tag-format-pattern <application> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), - NULL, 0); + nullptr, 0); // -------> Command SET_PATTERN(COMMAND, QString(), PlainText, ki18nc("tag-format-pattern <command> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), - NULL, 0); + nullptr, 0); SET_PATTERN(COMMAND, QString(), RichText, ki18nc("tag-format-pattern <command> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<tt>%1</tt>"), - NULL, 0); + nullptr, 0); SET_PATTERN(COMMAND, QSL("section"), PlainText, ki18nc("tag-format-pattern <command section=> plain\n" "%1 is the command name, %2 is its man section", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1(%2)"), - NULL, 0); + nullptr, 0); SET_PATTERN(COMMAND, QSL("section"), RichText, ki18nc("tag-format-pattern <command section=> rich\n" "%1 is the command name, %2 is its man section", // i18n: KUIT pattern, see the comment to the first of these entries above. "<tt>%1(%2)</tt>"), - NULL, 0); + nullptr, 0); // -------> Resource SET_PATTERN(QSL("resource"), QString(), PlainText, ki18nc("tag-format-pattern <resource> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "“%1”"), - NULL, 0); + nullptr, 0); SET_PATTERN(QSL("resource"), QString(), RichText, ki18nc("tag-format-pattern <resource> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "“%1”"), - NULL, 0); + nullptr, 0); // -------> Icode SET_PATTERN(QSL("icode"), QString(), PlainText, ki18nc("tag-format-pattern <icode> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "“%1”"), - NULL, 0); + nullptr, 0); SET_PATTERN(QSL("icode"), QString(), RichText, ki18nc("tag-format-pattern <icode> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<tt>%1</tt>"), - NULL, 0); + nullptr, 0); // -------> Bcode SET_PATTERN(QSL("bcode"), QString(), PlainText, ki18nc("tag-format-pattern <bcode> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "\n%1\n"), - NULL, 2); + nullptr, 2); SET_PATTERN(QSL("bcode"), QString(), RichText, ki18nc("tag-format-pattern <bcode> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<pre>%1</pre>"), - NULL, 2); + nullptr, 2); // -------> Shortcut SET_PATTERN(QSL("shortcut"), QString(), PlainText, ki18nc("tag-format-pattern <shortcut> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1"), tagFormatterShortcut, 0); SET_PATTERN(QSL("shortcut"), QString(), RichText, ki18nc("tag-format-pattern <shortcut> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<b>%1</b>"), tagFormatterShortcut, 0); // -------> Interface SET_PATTERN(QSL("interface"), QString(), PlainText, ki18nc("tag-format-pattern <interface> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "|%1|"), tagFormatterInterface, 0); SET_PATTERN(QSL("interface"), QString(), RichText, ki18nc("tag-format-pattern <interface> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<i>%1</i>"), tagFormatterInterface, 0); // -------> Emphasis SET_PATTERN(EMPHASIS, QString(), PlainText, ki18nc("tag-format-pattern <emphasis> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "*%1*"), - NULL, 0); + nullptr, 0); SET_PATTERN(EMPHASIS, QString(), RichText, ki18nc("tag-format-pattern <emphasis> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<i>%1</i>"), - NULL, 0); + nullptr, 0); SET_PATTERN(EMPHASIS, QSL("strong"), PlainText, ki18nc("tag-format-pattern <emphasis-strong> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "**%1**"), - NULL, 0); + nullptr, 0); SET_PATTERN(EMPHASIS, QSL("strong"), RichText, ki18nc("tag-format-pattern <emphasis-strong> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<b>%1</b>"), - NULL, 0); + nullptr, 0); // -------> Placeholder SET_PATTERN(QSL("placeholder"), QString(), PlainText, ki18nc("tag-format-pattern <placeholder> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "<%1>"), - NULL, 0); + nullptr, 0); SET_PATTERN(QSL("placeholder"), QString(), RichText, ki18nc("tag-format-pattern <placeholder> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<<i>%1</i>>"), - NULL, 0); + nullptr, 0); // -------> Email SET_PATTERN(QSL("email"), QString(), PlainText, ki18nc("tag-format-pattern <email> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "<%1>"), - NULL, 0); + nullptr, 0); SET_PATTERN(QSL("email"), QString(), RichText, ki18nc("tag-format-pattern <email> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<<a href=\"mailto:%1\">%1</a>>"), - NULL, 0); + nullptr, 0); SET_PATTERN(QSL("email"), QSL("address"), PlainText, ki18nc("tag-format-pattern <email address=> plain\n" "%1 is name, %2 is address", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1 <%2>"), - NULL, 0); + nullptr, 0); SET_PATTERN(QSL("email"), QSL("address"), RichText, ki18nc("tag-format-pattern <email address=> rich\n" "%1 is name, %2 is address", // i18n: KUIT pattern, see the comment to the first of these entries above. "<a href=\"mailto:%2\">%1</a>"), - NULL, 0); + nullptr, 0); // -------> Envar SET_PATTERN(QSL("envar"), QString(), PlainText, ki18nc("tag-format-pattern <envar> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "$%1"), - NULL, 0); + nullptr, 0); SET_PATTERN(QSL("envar"), QString(), RichText, ki18nc("tag-format-pattern <envar> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<tt>$%1</tt>"), - NULL, 0); + nullptr, 0); // -------> Message SET_PATTERN(QSL("message"), QString(), PlainText, ki18nc("tag-format-pattern <message> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "/%1/"), - NULL, 0); + nullptr, 0); SET_PATTERN(QSL("message"), QString(), RichText, ki18nc("tag-format-pattern <message> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "<i>%1</i>"), - NULL, 0); + nullptr, 0); // -------> Nl SET_PATTERN(QSL("nl"), QString(), PlainText, ki18nc("tag-format-pattern <nl> plain", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1\n"), - NULL, 0); + nullptr, 0); SET_PATTERN(QSL("nl"), QString(), RichText, ki18nc("tag-format-pattern <nl> rich", // i18n: KUIT pattern, see the comment to the first of these entries above. "%1<br/>"), - NULL, 0); + nullptr, 0); } void KuitSetupPrivate::setDefaultFormats() { using namespace Kuit; // Setup formats by role. formatsByRoleCue[ActionRole][UndefinedCue] = PlainText; formatsByRoleCue[TitleRole][UndefinedCue] = PlainText; formatsByRoleCue[LabelRole][UndefinedCue] = PlainText; formatsByRoleCue[OptionRole][UndefinedCue] = PlainText; formatsByRoleCue[ItemRole][UndefinedCue] = PlainText; formatsByRoleCue[InfoRole][UndefinedCue] = RichText; // Setup override formats by subcue. formatsByRoleCue[InfoRole][StatusCue] = PlainText; formatsByRoleCue[InfoRole][ProgressCue] = PlainText; formatsByRoleCue[InfoRole][CreditCue] = PlainText; formatsByRoleCue[InfoRole][ShellCue] = TermText; } KuitSetup::KuitSetup(const QByteArray &domain) : d(new KuitSetupPrivate) { d->domain = domain; d->setDefaultMarkup(); d->setDefaultFormats(); } KuitSetup::~KuitSetup() { delete d; } void KuitSetup::setTagPattern(const QString &tagName, const QStringList &attribNames, Kuit::VisualFormat format, const KLocalizedString &pattern, Kuit::TagFormatter formatter, int leadingNewlines) { d->setTagPattern(tagName, attribNames, format, pattern, formatter, leadingNewlines); } void KuitSetup::setTagClass(const QString &tagName, Kuit::TagClass aClass) { d->setTagClass(tagName, aClass); } void KuitSetup::setFormatForMarker(const QString &marker, Kuit::VisualFormat format) { d->setFormatForMarker(marker, format); } class KuitFormatterPrivate { public: KuitFormatterPrivate(const QString &language); QString format(const QByteArray &domain, const QString &context, const QString &text, Kuit::VisualFormat format) const; // Get metatranslation (formatting patterns, etc.) QString metaTr(const char *context, const char *text) const; // Set visual formatting patterns for text within tags. void setFormattingPatterns(); // Set data used in transformation of text within tags. void setTextTransformData(); // Determine visual format by parsing the UI marker in the context. static Kuit::VisualFormat formatFromUiMarker(const QString &context, const KuitSetup &setup); // Determine if text has block structure (multiple paragraphs, etc). static bool determineIsStructured(const QString &text, const KuitSetup &setup); // Format KUIT text into visual text. QString toVisualText(const QString &text, Kuit::VisualFormat format, const KuitSetup &setup) const; // Final touches to the formatted text. QString finalizeVisualText(const QString &ftext, Kuit::VisualFormat format) const; // In case of markup errors, try to make result not look too bad. QString salvageMarkup(const QString &text, Kuit::VisualFormat format, const KuitSetup &setup) const; // Data for XML parsing state. class OpenEl { public: enum Handling { Proper, Ignored, Dropout }; KuitTag tag; QString name; QHash<QString, QString> attributes; QString attribStr; Handling handling; QString formattedText; QStringList tagPath; }; // Gather data about current element for the parse state. KuitFormatterPrivate::OpenEl parseOpenEl(const QXmlStreamReader &xml, const OpenEl &enclosingOel, const QString &text, const KuitSetup &setup) const; // Format text of the element. QString formatSubText(const QString &ptext, const OpenEl &oel, Kuit::VisualFormat format, const KuitSetup &setup) const; // Count number of newlines at start and at end of text. static void countWrappingNewlines(const QString &ptext, int &numle, int &numtr); private: QString language; QStringList languageAsList; QHash<Kuit::VisualFormat, QString> comboKeyDelim; QHash<Kuit::VisualFormat, QString> guiPathDelim; QHash<QString, QString> keyNames; }; KuitFormatterPrivate::KuitFormatterPrivate(const QString &language_) : language(language_) { } QString KuitFormatterPrivate::format(const QByteArray &domain, const QString &context, const QString &text, Kuit::VisualFormat format) const { const KuitSetup &setup = Kuit::setupForDomain(domain); // If format is undefined, determine it based on UI marker inside context. Kuit::VisualFormat resolvedFormat = format; if (resolvedFormat == Kuit::UndefinedFormat) { resolvedFormat = formatFromUiMarker(context, setup); } // Quick check: are there any tags at all? QString ftext; if (text.indexOf(QL1C('<')) < 0) { ftext = finalizeVisualText(text, resolvedFormat); } else { // Format the text. ftext = toVisualText(text, resolvedFormat, setup); if (ftext.isEmpty()) { // error while processing markup ftext = salvageMarkup(text, resolvedFormat, setup); } } return ftext; } Kuit::VisualFormat KuitFormatterPrivate::formatFromUiMarker(const QString &context, const KuitSetup &setup) { KuitStaticData *s = staticData(); QString roleName, cueName, formatName; parseUiMarker(context, roleName, cueName, formatName); // Set role from name. Kuit::Role role; if (s->rolesByName.contains(roleName)) { // known role role = s->rolesByName.value(roleName); } else { // unknown role role = Kuit::UndefinedRole; if (!roleName.isEmpty()) { qWarning() << QStringLiteral( "Unknown role '@%1' in UI marker in context {%2}.") .arg(roleName, shorten(context)); } } // Set subcue from name. Kuit::Cue cue; if (role != Kuit::UndefinedRole) { if (s->cuesByName.contains(cueName)) { // known subcue cue = s->cuesByName.value(cueName); if (!s->knownRoleCues.value(role).contains(cue)) { cue = Kuit::UndefinedCue; qWarning() << QStringLiteral( "Subcue ':%1' does not belong to role '@%2' in UI marker in context {%3}.") .arg(cueName, roleName, shorten(context)); } } else { // unknown or not given subcue cue = Kuit::UndefinedCue; if (!cueName.isEmpty()) { qWarning() << QStringLiteral( "Unknown subcue ':%1' in UI marker in context {%2}.") .arg(cueName, shorten(context)); } } } else { // Bad role, silently ignore the cue. cue = Kuit::UndefinedCue; } // Set format from name, or by derivation from contex/subcue. Kuit::VisualFormat format; if (s->formatsByName.contains(formatName)) { // known format format = s->formatsByName.value(formatName); } else { // unknown or not given format // Check first if there is a format defined for role/subcue // combination, then for role only, then default to undefined. if (setup.d->formatsByRoleCue.contains(role)) { if (setup.d->formatsByRoleCue.value(role).contains(cue)) { format = setup.d->formatsByRoleCue.value(role).value(cue); } else { format = setup.d->formatsByRoleCue.value(role).value(Kuit::UndefinedCue); } } else { format = Kuit::UndefinedFormat; } if (!formatName.isEmpty()) { qWarning() << QStringLiteral( "Unknown format '/%1' in UI marker for message {%2}.") .arg(formatName, shorten(context)); } } if (format == Kuit::UndefinedFormat) { format = Kuit::PlainText; } return format; } bool KuitFormatterPrivate::determineIsStructured(const QString &text, const KuitSetup &setup) { // If the text opens with a structuring tag, then it is structured, // otherwise not. Leading whitespace is ignored for this purpose. static QRegExp staticOpensWithTagRx(QStringLiteral("^\\s*<\\s*(\\w+)[^>]*>")); QRegExp opensWithTagRx = staticOpensWithTagRx; // QRegExp not thread-safe bool isStructured = false; int p = opensWithTagRx.indexIn(text); if (p >= 0) { QString tagName = opensWithTagRx.capturedTexts().at(1).toLower(); if (setup.d->knownTags.contains(tagName)) { const KuitTag &tag = setup.d->knownTags.value(tagName); isStructured = (tag.type == Kuit::StructTag); } } return isStructured; } #define ENTITY_SUBRX "[a-z]+|#[0-9]+|#x[0-9a-fA-F]+" QString KuitFormatterPrivate::toVisualText(const QString &text_, Kuit::VisualFormat format, const KuitSetup &setup) const { KuitStaticData *s = staticData(); // Replace &-shortcut marker with "&", not to confuse the parser; // but do not touch & which forms an XML entity as it is. QString original = text_; QString text; int p = original.indexOf(QL1C('&')); while (p >= 0) { text.append(original.midRef(0, p + 1)); original.remove(0, p + 1); static QRegExp staticRestRx(QLatin1String("^(" ENTITY_SUBRX ");")); QRegExp restRx = staticRestRx; // QRegExp not thread-safe if (original.indexOf(restRx) != 0) { // not an entity text.append(QSL("amp;")); } p = original.indexOf(QL1C('&')); } text.append(original); // FIXME: Do this and then check proper use of structuring and phrase tags. #if 0 // Determine whether this is block-structured text. bool isStructured = determineIsStructured(text, setup); #endif const QString INTERNAL_TOP_TAG_NAME = QStringLiteral("__kuit_internal_top__"); // Add top tag, not to confuse the parser. text = QStringLiteral("<%2>%1</%2>").arg(text, INTERNAL_TOP_TAG_NAME); QStack<OpenEl> openEls; QXmlStreamReader xml(text); xml.setEntityResolver(&s->xmlEntityResolver); QStringRef lastElementName; while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement()) { lastElementName = xml.name(); // Find first proper enclosing element. OpenEl enclosingOel; for (int i = openEls.size() - 1; i >= 0; --i) { if (openEls[i].handling == OpenEl::Proper) { enclosingOel = openEls[i]; break; } } // Collect data about this element. OpenEl oel = parseOpenEl(xml, enclosingOel, text, setup); // Record the new element on the parse stack. openEls.push(oel); } else if (xml.isEndElement()) { // Get closed element data. OpenEl oel = openEls.pop(); // If this was closing of the top element, we're done. if (openEls.isEmpty()) { // Return with final touches applied. return finalizeVisualText(oel.formattedText, format); } // Append formatted text segment. QString ptext = openEls.top().formattedText; // preceding text openEls.top().formattedText += formatSubText(ptext, oel, format, setup); } else if (xml.isCharacters()) { // Stream reader will automatically resolve default XML entities, // which is not desired in this case, as the entities are to be // resolved in finalizeVisualText. Convert back into entities. QString ctext = xml.text().toString(); QString nctext; foreach (const QChar c, ctext) { if (s->xmlEntitiesInverse.contains(c)) { const QString entName = s->xmlEntitiesInverse[c]; nctext += QL1C('&') + entName + QL1C(';'); } else { nctext += c; } } openEls.top().formattedText += nctext; } } if (xml.hasError()) { qWarning() << QStringLiteral( "Markup error in message {%1}: %2. Last tag parsed: %3. Complete message follows:\n%4") .arg(shorten(text), xml.errorString(), lastElementName.toString(), text); return QString(); } // Cannot reach here. return text; } KuitFormatterPrivate::OpenEl KuitFormatterPrivate::parseOpenEl(const QXmlStreamReader &xml, const OpenEl &enclosingOel, const QString &text, const KuitSetup &setup) const { OpenEl oel; oel.name = xml.name().toString().toLower(); // Collect attribute names and values, and format attribute string. QStringList attribNames, attribValues; foreach (const QXmlStreamAttribute &xatt, xml.attributes()) { attribNames += xatt.name().toString().toLower(); attribValues += xatt.value().toString(); QChar qc = attribValues.last().indexOf(QL1C('\'')) < 0 ? QL1C('\'') : QL1C('"'); oel.attribStr += QL1C(' ') + attribNames.last() + QL1C('=') + qc + attribValues.last() + qc; } if (setup.d->knownTags.contains(oel.name)) { // known KUIT element const KuitTag &tag = setup.d->knownTags.value(oel.name); const KuitTag &etag = setup.d->knownTags.value(enclosingOel.name); // If this element can be contained within enclosing element, // mark it proper, otherwise mark it for removal. if (tag.name.isEmpty() || tag.type == Kuit::PhraseTag || etag.type == Kuit::StructTag) { oel.handling = OpenEl::Proper; } else { oel.handling = OpenEl::Dropout; qWarning() << QStringLiteral( "Structuring tag ('%1') cannot be subtag of phrase tag ('%2') in message {%3}.") .arg(tag.name, etag.name, shorten(text)); } // Resolve attributes and compute attribute set key. QSet<QString> attset; for (int i = 0; i < attribNames.size(); ++i) { QString att = attribNames[i]; if (tag.knownAttribs.contains(att)) { attset << att; oel.attributes[att] = attribValues[i]; } else { qWarning() << QStringLiteral( "Attribute '%1' not defined for tag '%2' in message {%3}.") .arg(att, tag.name, shorten(text)); } } // Continue tag path. oel.tagPath = enclosingOel.tagPath; oel.tagPath.prepend(enclosingOel.name); } else { // unknown element, leave it in verbatim oel.handling = OpenEl::Ignored; qWarning() << QStringLiteral( "Tag '%1' is not defined in message {%2}.") .arg(oel.name, shorten(text)); } return oel; } QString KuitFormatterPrivate::formatSubText(const QString &ptext, const OpenEl &oel, Kuit::VisualFormat format, const KuitSetup &setup) const { if (oel.handling == OpenEl::Proper) { const KuitTag &tag = setup.d->knownTags.value(oel.name); QString ftext = tag.format(languageAsList, oel.attributes, oel.formattedText, oel.tagPath, format); // Handle leading newlines, if this is not start of the text // (ptext is the preceding text). if (!ptext.isEmpty() && tag.leadingNewlines > 0) { // Count number of present newlines. int pnumle, pnumtr, fnumle, fnumtr; countWrappingNewlines(ptext, pnumle, pnumtr); countWrappingNewlines(ftext, fnumle, fnumtr); // Number of leading newlines already present. int numle = pnumtr + fnumle; // The required extra newlines. QString strle; if (numle < tag.leadingNewlines) { strle = QString(tag.leadingNewlines - numle, QL1C('\n')); } ftext = strle + ftext; } return ftext; } else if (oel.handling == OpenEl::Ignored) { return QL1C('<') + oel.name + oel.attribStr + QL1C('>') + oel.formattedText + QSL("</") + oel.name + QL1C('>'); } else { // oel.handling == OpenEl::Dropout return oel.formattedText; } } void KuitFormatterPrivate::countWrappingNewlines(const QString &text, int &numle, int &numtr) { int len = text.length(); // Number of newlines at start of text. numle = 0; while (numle < len && text[numle] == QL1C('\n')) { ++numle; } // Number of newlines at end of text. numtr = 0; while (numtr < len && text[len - numtr - 1] == QL1C('\n')) { ++numtr; } } QString KuitFormatterPrivate::finalizeVisualText(const QString &text_, Kuit::VisualFormat format) const { KuitStaticData *s = staticData(); QString text = text_; // Resolve XML entities. if (format != Kuit::RichText) { static QRegExp staticEntRx(QLatin1String("&(" ENTITY_SUBRX ");")); QRegExp entRx = staticEntRx; // QRegExp not thread-safe int p = entRx.indexIn(text); QString plain; while (p >= 0) { QString ent = entRx.capturedTexts().at(1); plain.append(text.midRef(0, p)); text.remove(0, p + ent.length() + 2); if (ent.startsWith(QL1C('#'))) { // numeric character entity QChar c; bool ok; if (ent[1] == QL1C('x')) { c = QChar(ent.midRef(2).toInt(&ok, 16)); } else { c = QChar(ent.midRef(1).toInt(&ok, 10)); } if (ok) { plain.append(c); } else { // unknown Unicode point, leave as is plain.append(QL1C('&') + ent + QL1C(';')); } } else if (s->xmlEntities.contains(ent)) { // known entity plain.append(s->xmlEntities[ent]); } else { // unknown entity, just leave as is plain.append(QL1C('&') + ent + QL1C(';')); } p = entRx.indexIn(text); } plain.append(text); text = plain; } // Add top tag. if (format == Kuit::RichText) { text = QStringLiteral("<html>") + text + QStringLiteral("</html>"); } return text; } QString KuitFormatterPrivate::salvageMarkup(const QString &text_, Kuit::VisualFormat format, const KuitSetup &setup) const { QString text = text_; QString ntext; int pos; // Resolve tags simple-mindedly. // - tags with content static QRegExp staticWrapRx(QStringLiteral("(<\\s*(\\w+)\\b([^>]*)>)(.*)(<\\s*/\\s*\\2\\s*>)")); QRegExp wrapRx = staticWrapRx; // QRegExp not thread-safe wrapRx.setMinimal(true); pos = 0; //ntext.clear(); while (true) { int previousPos = pos; pos = wrapRx.indexIn(text, previousPos); if (pos < 0) { ntext += text.midRef(previousPos); break; } ntext += text.midRef(previousPos, pos - previousPos); const QStringList capts = wrapRx.capturedTexts(); QString tagname = capts[2].toLower(); QString content = salvageMarkup(capts[4], format, setup); if (setup.d->knownTags.contains(tagname)) { const KuitTag &tag = setup.d->knownTags.value(tagname); QHash<QString, QString> attributes; // TODO: Do not ignore attributes (in capts[3]). ntext += tag.format(languageAsList, attributes, content, QStringList(), format); } else { ntext += capts[1] + content + capts[5]; } pos += wrapRx.matchedLength(); } text = ntext; // - tags without content static QRegExp staticNowrRx(QStringLiteral("<\\s*(\\w+)\\b([^>]*)/\\s*>")); QRegExp nowrRx = staticNowrRx; // QRegExp not thread-safe nowrRx.setMinimal(true); pos = 0; ntext.clear(); while (true) { int previousPos = pos; pos = nowrRx.indexIn(text, previousPos); if (pos < 0) { ntext += text.midRef(previousPos); break; } ntext += text.midRef(previousPos, pos - previousPos); const QStringList capts = nowrRx.capturedTexts(); QString tagname = capts[1].toLower(); if (setup.d->knownTags.contains(tagname)) { const KuitTag &tag = setup.d->knownTags.value(tagname); ntext += tag.format(languageAsList, QHash<QString, QString>(), QString(), QStringList(), format); } else { ntext += capts[0]; } pos += nowrRx.matchedLength(); } text = ntext; // Add top tag. if (format == Kuit::RichText) { text = QStringLiteral("<html>") + text + QStringLiteral("</html>"); } return text; } KuitFormatter::KuitFormatter(const QString &language) : d(new KuitFormatterPrivate(language)) { } KuitFormatter::~KuitFormatter() { delete d; } QString KuitFormatter::format(const QByteArray &domain, const QString &context, const QString &text, Kuit::VisualFormat format) const { return d->format(domain, context, text, format); } diff --git a/src/kuitmarkup.h b/src/kuitmarkup.h index 6f88c70..5eaebbb 100644 --- a/src/kuitmarkup.h +++ b/src/kuitmarkup.h @@ -1,199 +1,199 @@ /* This file is part of the KDE libraries Copyright (C) 2013 Chusslove Illich <caslav.ilic@gmx.net> 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. */ #ifndef KUITMARKUP_H #define KUITMARKUP_H #include <ki18n_export.h> #include <QtCore/QString> #include <QtCore/QStringList> #include <QtCore/QHash> class KuitSetup; /** * Global constants and functions related to KUIT markup. */ namespace Kuit { /** * Visual formats into which KUIT markup can be resolved. */ enum VisualFormat { /** * Visual format not defined. * This value can be explicitly set * (e.g. through \c KLocalizedString::withFormat) * to indicate that the format should be decided * by another mechanism (e.g. context UI marker). */ UndefinedFormat = 0, /** * Plain text. */ PlainText = 10, /** * Qt rich text (HTML subset). */ RichText = 20, /** * Terminal escape sequences. */ TermText = 30 }; /** * Classification of KUIT tags. */ enum TagClass { /** * Tags wrapping text inserted into running text. */ PhraseTag = 0, /** * Tags spliting text into paragraph-level blocks. */ StructTag = 1 }; /** * Functions accepted by tag formatting functions. * * \param languages the target languages (by decreasing priority) * \param tagName the wrapping tag name * \param attributes the attribute name-value pairs in the tag * \param text the wrapped text * \param tagPath the ordered list of ancestor tag names, parent first * \param format the target visual format * \return formatted text */ typedef QString(*TagFormatter)(const QStringList &languages, const QString &tagName, const QHash<QString, QString> &attributes, const QString &text, const QStringList &tagPath, Kuit::VisualFormat format); /** * Get hold of the KUIT setup object for a given domain. * * \param domain the translation domain * \return pointer to KUIT setup object */ KuitSetup &setupForDomain(const char *domain); KuitSetup &setupForDomain(const QByteArray& domain); } class KLocalizedString; class KuitSetupPrivate; class KuitFormatterPrivate; /** * Class for modifying KUIT markup in a given domain. * * Not directly constructed, but obtained through \c Kuit::setupForDomain. */ class KI18N_EXPORT KuitSetup { friend KuitSetup &Kuit::setupForDomain(const QByteArray& domain); friend class KuitFormatterPrivate; public: /** * Destructor. */ ~KuitSetup(); /** * Set the formatting string for a tag with attributes combination. * * If a new tag name is given, this effectively defines a new tag. * The same holds for attribute names. * * The pattern string \p pattern should contain placeholders * for inserting the text and the attribute values. * %1 will be replaced with the wrapped text, and %2 and upwards * with attribute values in the order given by \p attrNames. * Non markup-aware translation call with context (\c ki18nc) * should be used to create the pattern string. * * In addition to the pattern, a formatting function * of the type \c TagFormatter can be given. * This function receives the full markup parsing context, * so that it can do whatever is necessary with the wrapped text. * The result of this function is then substituted into the pattern. * You can also give an empty pattern (as <tt>KLocalizedString()</tt>) * together with the formatting function, in which case the function * is assumed to do everything and no substitution is performed. * * \param tagName the name of the tag * \param attribNames the names of the attributes (empty names are ignored) * \param format the target visual format * \param pattern the pattern string * \param leadingNewlines the number of new lines (\\n) to be maintained * between any preceding text and the text wrapped * with this tag (for formats where it matters) */ void setTagPattern(const QString &tagName, const QStringList &attribNames, Kuit::VisualFormat format, const KLocalizedString &pattern, - Kuit::TagFormatter formatter = NULL, + Kuit::TagFormatter formatter = nullptr, int leadingNewlines = 0); /** * Set the KUIT class of the tag. * * \param tagName the name of the tag * \param aClass the KUIT tag class */ void setTagClass(const QString &tagName, Kuit::TagClass aClass); /** * Set the default visual format for a given UI marker. * * Giving <tt>"@<major>"</tt> for \p marker means to set the format * only for standalone <tt>\@\<major\></tt> marker, * while <tt>"@<major>:"</tt> (with trailing colon) means to set * the same format for all <tt>\@\<major\>:\<minor\></tt> combinations. * * Defined UI marker major/minor combinations are listed in the section * \ref uimark_ctxt. If an UI marker combination outside of the defined * is given as \p marker, it will be ignored. * * Setting \c Kuit::UndefinedFormat as \p format * means to fall back to default format for the given UI marker. * * \param marker the UI marker * \param format the visual format */ void setFormatForMarker(const QString &marker, Kuit::VisualFormat format); private: KuitSetup(const QByteArray &domain); Q_DISABLE_COPY(KuitSetup) KuitSetupPrivate *const d; }; #endif // KUITMARKUP_H