File indexing completed on 2024-04-21 14:59:03

0001 // krazy:excludeall=i18ncheckarg
0002 /*  This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2006 Chusslove Illich <caslav.ilic@gmx.net>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 // Tests explicitly use their own test catalogs.
0009 #undef TRANSLATION_DOMAIN
0010 
0011 #include "klocalizedstringtest.h"
0012 #include "klocalizedtranslator.h"
0013 
0014 #include <locale.h>
0015 
0016 #include <QDebug>
0017 #include <QProcess>
0018 #include <QStandardPaths>
0019 #include <QTest>
0020 #include <QThread>
0021 
0022 #include <libintl.h>
0023 
0024 #include <klazylocalizedstring.h>
0025 #include <klocalizedstring.h>
0026 
0027 #include <QRegularExpression>
0028 #include <QSet>
0029 #include <QString>
0030 
0031 void initEnvironment()
0032 {
0033     // We need the default locale to be English otherwise the brokenTags test fails
0034     // since the "Opening and ending tag mismatch" text comes from Qt
0035     qputenv("LANG", "en_US.utf8");
0036     QStandardPaths::setTestModeEnabled(true);
0037 }
0038 
0039 Q_CONSTRUCTOR_FUNCTION(initEnvironment)
0040 
0041 void KLocalizedStringTest::initTestCase()
0042 {
0043     KLocalizedString::setApplicationDomain("ki18n-test");
0044 
0045     m_hasFrench = true;
0046     m_hasCatalan = true;
0047 
0048     setlocale(LC_ALL, "ca_ES.utf8");
0049     if (setlocale(LC_ALL, nullptr) != QByteArray("ca_ES.utf8")) {
0050         qDebug() << "Failed to set locale to ca_ES.utf8.";
0051         m_hasCatalan = false;
0052     }
0053 
0054     if (m_hasFrench) {
0055         setlocale(LC_ALL, "fr_FR.utf8");
0056         if (setlocale(LC_ALL, nullptr) != QByteArray("fr_FR.utf8")) {
0057             qDebug() << "Failed to set locale to fr_FR.utf8.";
0058             m_hasFrench = false;
0059         } else {
0060             QLocale::setDefault(QLocale("fr_FR")); // the setlocale is "too late" for Qt that already has created the default QLocale, so set it manually
0061         }
0062     }
0063     if (m_hasFrench) {
0064         if (!m_tempDir.isValid()) {
0065             qDebug() << "Failed to create temporary directory for test data.";
0066             m_hasFrench = false;
0067         }
0068     }
0069     QDir dataDir(m_tempDir.path());
0070     if (m_hasFrench) {
0071         m_hasFrench = compileCatalogs({QFINDTESTDATA("po/fr/ki18n-test.po"), QFINDTESTDATA("po/fr/ki18n-test-qt.po")}, dataDir, "fr");
0072     }
0073     if (m_hasCatalan) {
0074         m_hasCatalan = compileCatalogs({QFINDTESTDATA("po/ca/ki18n-test.po")}, dataDir, "ca");
0075     }
0076     if (m_hasFrench) {
0077         const QByteArray dataDirs = qgetenv("XDG_DATA_DIRS") + ":" + QFile::encodeName(dataDir.path());
0078         qputenv("XDG_DATA_DIRS", dataDirs);
0079         // bind... dataDir.path()
0080         QStringList languages;
0081         languages.append("fr");
0082         KLocalizedString::setLanguages(languages);
0083     }
0084 
0085 #if 0 // until locale system is ready
0086     if (m_hasFrench) {
0087         KLocale::global()->setLanguage(QStringList() << "fr" << "en_US");
0088     }
0089     KLocale::global()->setThousandsSeparator(QLatin1String(","));
0090     KLocale::global()->setDecimalSymbol(QLatin1String("."));
0091 #endif
0092 }
0093 
0094 bool KLocalizedStringTest::compileCatalogs(const QStringList &testPoPaths, const QDir &dataDir, const QString &lang)
0095 {
0096     const QString lcMessages = QString("locale/%1/LC_MESSAGES").arg(lang);
0097     if (!dataDir.mkpath(lcMessages)) {
0098         qDebug() << "Failed to create locale subdirectory "
0099                     "inside temporary directory.";
0100         return false;
0101     }
0102     QString msgfmt = QStandardPaths::findExecutable(QLatin1String("msgfmt"));
0103     if (msgfmt.isEmpty()) {
0104         qDebug() << "msgfmt(1) not found in path.";
0105         return false;
0106     }
0107     for (const QString &testPoPath : testPoPaths) {
0108         int pos_1 = testPoPath.lastIndexOf(QLatin1Char('/'));
0109         int pos_2 = testPoPath.lastIndexOf(QLatin1Char('.'));
0110         QString domain = testPoPath.mid(pos_1 + 1, pos_2 - pos_1 - 1);
0111         QString testMoPath;
0112         testMoPath = QString::fromLatin1("%1/%3/%2.mo").arg(dataDir.path(), domain, lcMessages);
0113         QProcess process;
0114         QStringList arguments;
0115         arguments << testPoPath << QLatin1String("-o") << testMoPath;
0116         process.start(msgfmt, arguments);
0117         process.waitForFinished(10000);
0118         if (process.exitCode() != 0) {
0119             qDebug() << QString::fromLatin1("msgfmt(1) could not compile %1.").arg(testPoPath);
0120             return false;
0121         }
0122     }
0123     return true;
0124 }
0125 
0126 void KLocalizedStringTest::correctSubs()
0127 {
0128     if (!m_hasFrench) {
0129         QSKIP("French test files not usable.");
0130     }
0131     // Warm up.
0132     QCOMPARE(i18n("Daisies, daisies"), QString("Daisies, daisies"));
0133 
0134     // Placeholder in the middle.
0135     QCOMPARE(i18n("Fault in %1 unit", QString("AE35")), QString("Fault in AE35 unit"));
0136     // Placeholder at the start.
0137     QCOMPARE(i18n("%1, Tycho Magnetic Anomaly 1", QString("TMA-1")), QString("TMA-1, Tycho Magnetic Anomaly 1"));
0138     // Placeholder at the end.
0139     QCOMPARE(i18n("...odd things happening at %1", QString("Clavius")), QString("...odd things happening at Clavius"));
0140     QCOMPARE(i18n("Group %1", 1), QString("Group 1"));
0141 
0142     // Two placeholders.
0143     QCOMPARE(i18n("%1 and %2", QString("Bowman"), QString("Poole")), QString("Bowman and Poole"));
0144     // Two placeholders in inverted order.
0145     QCOMPARE(i18n("%2 and %1", QString("Poole"), QString("Bowman")), QString("Bowman and Poole"));
0146 
0147     // % which is not of placeholder.
0148     QCOMPARE(i18n("It's going to go %1% failure in 72 hours.", 100), QString("It's going to go 100% failure in 72 hours."));
0149 
0150     // Usual plural.
0151     QCOMPARE(i18np("%1 pod", "%1 pods", 1), QString("1 pod"));
0152     QCOMPARE(i18np("%1 pod", "%1 pods", 10), QString("10 pods"));
0153 
0154     // No plural-number in singular.
0155     QCOMPARE(i18np("A pod", "%1 pods", 1), QString("A pod"));
0156     QCOMPARE(i18np("A pod", "%1 pods", 10), QString("10 pods"));
0157 
0158     // No plural-number in singular or plural.
0159     QCOMPARE(i18np("A pod", "Few pods", 1), QString("A pod"));
0160     QCOMPARE(i18np("A pod", "Few pods", 10), QString("Few pods"));
0161 
0162     // First of two arguments as plural-number.
0163     QCOMPARE(i18np("A pod left on %2", "%1 pods left on %2", 1, QString("Discovery")), QString("A pod left on Discovery"));
0164     QCOMPARE(i18np("A pod left on %2", "%1 pods left on %2", 2, QString("Discovery")), QString("2 pods left on Discovery"));
0165 
0166     // Second of two arguments as plural-number.
0167     QCOMPARE(i18np("%1 has a pod left", "%1 has %2 pods left", QString("Discovery"), 1), QString("Discovery has a pod left"));
0168     QCOMPARE(i18np("%1 has a pod left", "%1 has %2 pods left", QString("Discovery"), 2), QString("Discovery has 2 pods left"));
0169 
0170     // No plural-number in singular or plural, but another argument present.
0171     QCOMPARE(i18np("A pod left on %2", "Some pods left on %2", 1, QString("Discovery")), QString("A pod left on Discovery"));
0172     QCOMPARE(i18np("A pod left on %2", "Some pods left on %2", 2, QString("Discovery")), QString("Some pods left on Discovery"));
0173 
0174     // Visual formatting.
0175     // FIXME: Needs much more tests.
0176     QCOMPARE(xi18n("E = mc^2"), QString("E = mc^2"));
0177     QCOMPARE(xi18n("E &lt; mc^2"), QString("E < mc^2"));
0178     QCOMPARE(xi18n("E ? <emphasis>mc^2</emphasis>"), QString("E ? *mc^2*"));
0179     QCOMPARE(xi18n("E &lt; <emphasis>mc^2</emphasis>"), QString("E < *mc^2*"));
0180     QCOMPARE(xi18nc("@label", "E &lt; <emphasis>mc^2</emphasis>"), QString("E < *mc^2*"));
0181     QCOMPARE(xi18nc("@info", "E &lt; <emphasis>mc^2</emphasis>"), QString("<html>E &lt; <i>mc^2</i></html>"));
0182     QCOMPARE(xi18nc("@info:status", "E &lt; <emphasis>mc^2</emphasis>"), QString("E < *mc^2*"));
0183     QCOMPARE(xi18nc("@info:progress", "E &lt; <emphasis>mc^2</emphasis>"), QString("E < *mc^2*"));
0184     QCOMPARE(xi18nc("@info:tooltip", "E &lt; <emphasis>mc^2</emphasis>"), QString("<html>E &lt; <i>mc^2</i></html>"));
0185     QCOMPARE(xi18nc("@info:shell", "E &lt; <emphasis>mc^2</emphasis>"), QString("E < *mc^2*"));
0186     QCOMPARE(xi18n("E = mc^&#x0032;"), QString("E = mc^2"));
0187     QCOMPARE(xi18n("E = mc^&#0050;"), QString("E = mc^2"));
0188 
0189     // with additional whitespace
0190     QCOMPARE(xi18nc(" @info:progress ", "E &lt; <emphasis>mc^2</emphasis>"), QString("E < *mc^2*"));
0191     QCOMPARE(xi18nc(" @info:tooltip ", "E &lt; <emphasis>mc^2</emphasis>"), QString("<html>E &lt; <i>mc^2</i></html>"));
0192     QCOMPARE(xi18nc(" @info: progress ", "E &lt; <emphasis>mc^2</emphasis>"), // not parsed as a cue
0193              QString("<html>E &lt; <i>mc^2</i></html>"));
0194     QCOMPARE(xi18nc(" @info: tooltip ", "E &lt; <emphasis>mc^2</emphasis>"), // not parsed as a cue
0195              QString("<html>E &lt; <i>mc^2</i></html>"));
0196 
0197     QTest::ignoreMessage(QtWarningMsg, "\"Unknown subcue ':doesnotexist' in UI marker in context {@info:doesnotexist}.\"");
0198     QCOMPARE(xi18nc("@info:doesnotexist", "E &lt; <emphasis>mc^2</emphasis>"), QString("<html>E &lt; <i>mc^2</i></html>"));
0199 
0200     // Number formatting.
0201     QCOMPARE(ki18n("%1").subs(42).toString(), QString("42"));
0202     QCOMPARE(ki18n("%1").subs(42, 5).toString(), QString("   42"));
0203     QCOMPARE(ki18n("%1").subs(42, -5, 10, QChar('_')).toString(), QString("42___"));
0204     QCOMPARE(ki18n("%1").subs(4.2, 5, 'f', 2).toString(), QString(" 4,20"));
0205 }
0206 
0207 void KLocalizedStringTest::wrongSubs()
0208 {
0209 #ifndef NDEBUG
0210     // Too many arguments.
0211     QVERIFY(i18n("Europa", 1) != QString("Europa"));
0212 
0213     // Too few arguments.
0214     QVERIFY(i18n("%1, %2 and %3", QString("Hunter"), QString("Kimball")) != QString("Hunter, Kimball and %3"));
0215 
0216     // Gaps in placheholder numbering.
0217     QVERIFY(ki18n("Beyond the %2").subs("infinity").toString() != QString("Beyond the infinity"));
0218 
0219     // Plural argument not supplied.
0220     QVERIFY(ki18np("1 pod", "%1 pods").toString() != QString("1 pod"));
0221     QVERIFY(ki18np("1 pod", "%1 pods").toString() != QString("%1 pods"));
0222 #endif
0223 }
0224 
0225 void KLocalizedStringTest::semanticTags()
0226 {
0227     KLocalizedString::setLanguages({"en"});
0228 
0229     // <application/>
0230     QCOMPARE(xi18nc("@action:inmenu", "Open with <application>%1</application>", "Okteta"), QString("Open with Okteta"));
0231     QCOMPARE(xi18nc("@info", "Open with <application>%1</application>", "Okteta"), QString("<html>Open with Okteta</html>"));
0232     // <bcode/>
0233     QCOMPARE(xi18nc("@info:whatsthis",
0234                     "You can try the following snippet:<bcode>"
0235                     "\\begin{equation}\n"
0236                     "  C_{x_i} = \\frac{C_z^2}{e \\pi \\lambda}\n"
0237                     "\\end{equation}"
0238                     "</bcode>"),
0239              QString("<html>You can try the following snippet:\n\n<pre>"
0240                      "\\begin{equation}\n"
0241                      "  C_{x_i} = \\frac{C_z^2}{e \\pi \\lambda}\n"
0242                      "\\end{equation}"
0243                      "</pre></html>"));
0244     // <command/>
0245     QCOMPARE(xi18nc("@info", "This will call <command>%1</command> internally.", "true"), QString("<html>This will call <tt>true</tt> internally.</html>"));
0246     QCOMPARE(xi18nc("@info", "Consult man entry for <command section='%2'>%1</command>", "true", 1),
0247              QString("<html>Consult man entry for <tt>true(1)</tt></html>"));
0248     // <email/>
0249     QCOMPARE(xi18nc("@info", "Send bug reports to <email>%1</email>.", "konqi@kde.org"),
0250              QString("<html>Send bug reports to &lt;<a href=\"mailto:konqi@kde.org\">konqi@kde.org</a>&gt;.</html>"));
0251     QCOMPARE(xi18nc("@info", "Send praises to <email address='%1'>%2</email>.", "konqi@kde.org", "Konqi"),
0252              QString("<html>Send praises to <a href=\"mailto:konqi@kde.org\">Konqi</a>.</html>"));
0253     // <emphasis/>
0254     QCOMPARE(xi18nc("@info:progress", "Checking <emphasis>feedback</emphasis> circuits..."), QString("Checking *feedback* circuits..."));
0255     QCOMPARE(xi18nc("@info:progress", "Checking <emphasis strong='true'>feedback</emphasis> circuits..."), QString("Checking **feedback** circuits..."));
0256     // <envar/>
0257     QCOMPARE(xi18nc("@info", "Assure that your <envar>PATH</envar> is properly set."),
0258              QString("<html>Assure that your <tt>$PATH</tt> is properly set.</html>"));
0259     // <filename/>
0260     QCOMPARE(xi18nc("@info", "Cannot read <filename>%1</filename>.", "data.dat"), QString("<html>Cannot read \u2018<tt>data.dat</tt>\u2019.</html>"));
0261     // TODO: is nested <tt><tt></tt></tt> really wanted?
0262 #ifndef Q_OS_WIN
0263     QString homeFooRc("<html>\u2018<tt><tt>$HOME</tt>/.foorc</tt>\u2019 does not exist.</html>");
0264 #else
0265     // TODO $HOME -> %HOME% ?
0266     QString homeFooRc("<html>\u2018<tt><tt>$HOME</tt>\\.foorc</tt>\u2019 does not exist.</html>");
0267 #endif
0268     QCOMPARE(xi18nc("@info", "<filename><envar>HOME</envar>/.foorc</filename> does not exist."), homeFooRc);
0269 
0270     // <icode/>
0271     QCOMPARE(xi18nc("@info:tooltip", "Execute <icode>svn merge</icode> on selected revisions."),
0272              QString("<html>Execute <tt>svn merge</tt> on selected revisions.</html>"));
0273     // <interface/>
0274     QCOMPARE(xi18nc("@info:whatsthis", "If you make a mistake, click <interface>Reset</interface> to start again."),
0275              QString("<html>If you make a mistake, click <i>Reset</i> to start again.</html>"));
0276     QCOMPARE(xi18nc("@info:whatsthis", "The line colors can be changed under <interface>Settings->Visuals</interface>."),
0277              QString("<html>The line colors can be changed under <i>Settings-&gt;Visuals</i>.</html>"));
0278     // <link/>
0279     QCOMPARE(xi18nc("@info:tooltip", "Go to <link>%1</link> website.", "http://kde.org/"),
0280              QString("<html>Go to <a href=\"http://kde.org/\">http://kde.org/</a> website.</html>"));
0281     QCOMPARE(xi18nc("@info:tooltip", "Go to <link url='%1'>%2</link>.", "http://kde.org/", "the KDE website"),
0282              QString("<html>Go to <a href=\"http://kde.org/\">the KDE website</a>.</html>"));
0283     // <message/>
0284     QCOMPARE(xi18nc("@info", "The fortune cookie says: <message>%1</message>", "Nothing"), QString("<html>The fortune cookie says: <i>Nothing</i></html>"));
0285     // <nl/>
0286 #ifndef Q_OS_WIN
0287     QString deleteEtcPasswd("<html>Do you really want to delete:<br/>\u2018<tt>/etc/passwd</tt>\u2019</html>");
0288 #else
0289     QString deleteEtcPasswd("<html>Do you really want to delete:<br/>\u2018<tt>\\etc\\passwd</tt>\u2019</html>");
0290 #endif
0291     QCOMPARE(xi18nc("@info", "Do you really want to delete:<nl/><filename>%1</filename>", "/etc/passwd"), deleteEtcPasswd);
0292 
0293     // check <nl/> within filename doesn't break (Windows path separators)
0294 #ifndef Q_OS_WIN
0295     QString filenameWithNewline("<html>\u2018<tt>/filename/with<br/>/newline</tt>\u2019</html>");
0296 #else
0297     QString filenameWithNewline("<html>\u2018<tt>\\filename\\with<br/>\\newline</tt>\u2019</html>");
0298 #endif
0299     QCOMPARE(xi18nc("@info", "<filename>/filename/with<nl/>/newline</filename>"), filenameWithNewline);
0300 
0301     // <numid/>
0302     QEXPECT_FAIL("", "what happened to <numid/>? TODO.", Continue);
0303     QCOMPARE(xi18nc("@info:progress", "Connecting to <numid>%1</numid>...", 22), QString("<html>Connecting to <tt>22</tt></html>"));
0304     QCOMPARE(xi18nc("@info", "Replace <placeholder>name</placeholder> with your name."), QString("<html>Replace &lt;<i>name</i>&gt; with your name.</html>"));
0305     QCOMPARE(xi18nc("@item:inlistbox", "<placeholder>All images</placeholder>"), QString("<All images>"));
0306     // <resource/>
0307     QCOMPARE(xi18nc("@info", "Apply color scheme <resource>%1</resource>?", "XXX"), QString("<html>Apply color scheme “XXX”?</html>"));
0308     QCOMPARE(xi18nc("@info:whatsthis", "Cycle through layouts using <shortcut>Alt+Space</shortcut>."),
0309              QString("<html>Cycle through layouts using <b>Alt+Space</b>.</html>"));
0310     // <note/>
0311     QCOMPARE(xi18nc("@info",
0312                     "Probably the best known of all duck species is the Mallard. "
0313                     "It breeds throughout the temperate areas around the world. "
0314                     "<note>Most domestic ducks are derived from Mallard.</note>"),
0315              QString("<html>Probably the best known of all duck species is the Mallard. "
0316                      "It breeds throughout the temperate areas around the world. "
0317                      "<i>Note</i>: Most domestic ducks are derived from Mallard.</html>"));
0318     QCOMPARE(xi18nc("@info", "<note label='Trivia'>Most domestic ducks are derived from Mallard.</note>"),
0319              QString("<html><i>Trivia</i>: Most domestic ducks are derived from Mallard.</html>"));
0320     // <warning/>
0321     QCOMPARE(xi18nc("@info",
0322                     "Really delete this key?"
0323                     "<warning>This cannot be undone.</warning>"),
0324              QString("<html>Really delete this key?"
0325                      "<b>Warning</b>: This cannot be undone.</html>"));
0326     QCOMPARE(xi18nc("@info", "<warning label='Danger'>This cannot be undone.</warning>"), QString("<html><b>Danger</b>: This cannot be undone.</html>"));
0327 }
0328 
0329 void KLocalizedStringTest::setFormatForMarker()
0330 {
0331     KLocalizedString::setLanguages({"en"});
0332 
0333     QCOMPARE(xi18nc("@info:tooltip", "Hello world"), QString("<html>Hello world</html>"));
0334     KuitSetup &setup = Kuit::setupForDomain(KLocalizedString::applicationDomain());
0335     setup.setFormatForMarker("@info:tooltip", Kuit::PlainText);
0336     QCOMPARE(xi18nc("@info:tooltip", "Hello world"), QString("Hello world"));
0337 }
0338 
0339 void KLocalizedStringTest::removeAcceleratorMarker()
0340 {
0341     // No accelerator marker.
0342     QCOMPARE(KLocalizedString::removeAcceleratorMarker(QString()), QString());
0343     QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar"), QString("Foo bar"));
0344 
0345     // Run of the mill.
0346     QCOMPARE(KLocalizedString::removeAcceleratorMarker("&Foo bar"), QString("Foo bar"));
0347     QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo &bar"), QString("Foo bar"));
0348     QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo b&ar"), QString("Foo bar"));
0349     // - presence of escaped ampersands
0350     QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo && Bar"), QString("Foo & Bar"));
0351     QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo && &Bar"), QString("Foo & Bar"));
0352     QCOMPARE(KLocalizedString::removeAcceleratorMarker("&Foo && Bar"), QString("Foo & Bar"));
0353 
0354     // CJK-style markers.
0355     QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar (&F)"), QString("Foo bar"));
0356     QCOMPARE(KLocalizedString::removeAcceleratorMarker("(&F) Foo bar"), QString("Foo bar"));
0357     // - interpunction after/before parenthesis still qualifies CJK marker
0358     QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar (&F):"), QString("Foo bar:"));
0359     QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar (&F)..."), QString("Foo bar..."));
0360     QCOMPARE(KLocalizedString::removeAcceleratorMarker("...(&F) foo bar"), QString("...foo bar"));
0361     // - alphanumerics around parenthesis disqualify CJK marker
0362     QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo (&F) bar"), QString("Foo (F) bar"));
0363     // - something removed raw ampersands, leaving dangling reduced CJK markers.
0364     // Remove reduced markers only if CJK characters are found in the string.
0365     QCOMPARE(KLocalizedString::removeAcceleratorMarker(QString::fromUtf8("Foo bar (F)")), QString::fromUtf8("Foo bar (F)"));
0366     QCOMPARE(KLocalizedString::removeAcceleratorMarker(QString::fromUtf8("印刷(P)...")), QString::fromUtf8("印刷..."));
0367 
0368     // Shady cases, where ampersand is obviously not a marker
0369     // and should have been escaped, but it was not.
0370     QCOMPARE(KLocalizedString::removeAcceleratorMarker("&"), QString("&"));
0371     QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo bar &"), QString("Foo bar &"));
0372     QCOMPARE(KLocalizedString::removeAcceleratorMarker("Foo & Bar"), QString("Foo & Bar"));
0373 }
0374 
0375 void KLocalizedStringTest::miscMethods()
0376 {
0377     KLocalizedString k;
0378     QVERIFY(k.isEmpty());
0379 
0380     if (m_hasFrench) {
0381         QSet<QString> availableLanguages;
0382         availableLanguages.insert("fr");
0383         availableLanguages.insert("en_US");
0384         if (m_hasCatalan) {
0385             availableLanguages.insert("ca");
0386         }
0387         QCOMPARE(KLocalizedString::availableApplicationTranslations(), availableLanguages);
0388     }
0389 }
0390 
0391 // Same as translateToFrench, but using libintl directly (bindtextdomain+dgettext).
0392 // Useful for debugging. This changes global state, though, so it's skipped by default.
0393 void KLocalizedStringTest::translateToFrenchLowlevel()
0394 {
0395     if (!m_hasFrench) {
0396         QSKIP("French test files not usable.");
0397     }
0398     QSKIP("Skipped by default to avoid changing global state.");
0399     // fr_FR locale was set by initTestCase already.
0400     if (QFile::exists("/usr/share/locale/fr/LC_MESSAGES/ki18n-test.mo")) {
0401         bindtextdomain("ki18n-test", "/usr/share/locale");
0402         QCOMPARE(QString::fromUtf8(dgettext("ki18n-test", "Loadable modules")), QString::fromUtf8("Modules chargeables"));
0403     }
0404 }
0405 
0406 void KLocalizedStringTest::translateToFrench()
0407 {
0408     if (!m_hasFrench) {
0409         QSKIP("French test files not usable.");
0410     }
0411     QCOMPARE(i18n("Loadable modules"), QString::fromUtf8("Modules chargeables"));
0412     QCOMPARE(i18n("Job"), QString::fromUtf8("Tâche"));
0413 }
0414 
0415 #if KI18N_ENABLE_DEPRECATED_SINCE(5, 0)
0416 void KLocalizedStringTest::translateQt()
0417 {
0418     KLocalizedString::insertQtDomain("ki18n-test-qt");
0419     QString result = KLocalizedString::translateQt("QPrintPreviewDialog", "Landscape", nullptr, 0);
0420     // When we use the default language, translateQt returns an empty string.
0421     QString expected = m_hasFrench ? QString("Paysage") : QString();
0422     QCOMPARE(result, expected);
0423 #if 0 // KLocalizedString no longer does anything with QTranslator, this needed?
0424     result = QCoreApplication::translate("QPrintPreviewDialog", "Landscape");
0425     QString expected2 = m_hasFrench ? QString("Paysage") : QString("Landscape");
0426     QCOMPARE(result, expected2);
0427 #endif
0428 
0429 #if 0 // translateRaw no longer public, this needed?
0430     // So let's use translateRaw instead for the threaded test
0431     QString lang;
0432     KLocale::global()->translateRaw("Landscape", &lang, &result);
0433     QCOMPARE(lang, m_hasFrench ? QString("fr") : QString("en_US"));
0434     QCOMPARE(result, m_hasFrench ? QString("Paysage") : QString("Landscape"));
0435 #endif
0436     KLocalizedString::removeQtDomain("ki18n-test-qt");
0437 }
0438 #endif
0439 
0440 void KLocalizedStringTest::testLocalizedTranslator()
0441 {
0442     if (!m_hasFrench) {
0443         QSKIP("French test files not usable.");
0444     }
0445     QScopedPointer<KLocalizedTranslator> translator(new KLocalizedTranslator());
0446     QCoreApplication *app = QCoreApplication::instance();
0447     app->installTranslator(translator.data());
0448 
0449     // no translation domain and no context
0450     QCOMPARE(app->translate("foo", "Job"), QStringLiteral("Job"));
0451 
0452     // adding the translation domain still lacks the context
0453     translator->setTranslationDomain(QStringLiteral("ki18n-test"));
0454     QCOMPARE(app->translate("foo", "Job"), QStringLiteral("Job"));
0455 
0456     translator->addContextToMonitor(QStringLiteral("foo"));
0457     // now it should translate
0458     QCOMPARE(app->translate("foo", "Job"), QStringLiteral("Tâche"));
0459     // other context shouldn't translate
0460     QCOMPARE(app->translate("bar", "Job"), QStringLiteral("Job"));
0461     // with a mismatching disambiguation it shouldn't translate
0462     QCOMPARE(app->translate("foo", "Job", "bar"), QStringLiteral("Job"));
0463 }
0464 
0465 void KLocalizedStringTest::addCustomDomainPath()
0466 {
0467     if (!m_hasFrench) {
0468         QSKIP("French test files not usable.");
0469     }
0470     QTemporaryDir dir;
0471     compileCatalogs({QFINDTESTDATA("po/fr/ki18n-test2.po")}, dir.path(), "fr");
0472     KLocalizedString::addDomainLocaleDir("ki18n-test2", dir.path() + "/locale");
0473 
0474     QSet<QString> expectedAvailableTranslations({"en_US", "fr"});
0475     QCOMPARE(KLocalizedString::availableDomainTranslations("ki18n-test2"), expectedAvailableTranslations);
0476     QCOMPARE(i18nd("ki18n-test2", "Cheese"), QString::fromUtf8("Fromage"));
0477 }
0478 
0479 void KLocalizedStringTest::multipleLanguages()
0480 {
0481     if (!m_hasFrench || !m_hasCatalan) {
0482         QSKIP("French or Catalan test files not usable.");
0483     }
0484     KLocalizedString::setLanguages({"ca"});
0485     QCOMPARE(i18n("Job"), QString::fromUtf8("Job")); // This is not the actual catalan translation but who cares
0486     KLocalizedString::setLanguages({"fr"});
0487     QCOMPARE(i18n("Job"), QString::fromUtf8("Tâche"));
0488     KLocalizedString::setLanguages({"ca", "fr"});
0489     QCOMPARE(i18n("Job"), QString::fromUtf8("Job")); // This is not the actual catalan translation but who cares
0490 
0491     KLocalizedString::setLanguages({"ca"});
0492     QCOMPARE(i18n("Loadable modules"), QString::fromUtf8("Loadable modules")); // The po doesn't have a translation so we get the English text
0493     KLocalizedString::setLanguages({"fr"});
0494     QCOMPARE(i18n("Loadable modules"), QString::fromUtf8("Modules chargeables"));
0495     KLocalizedString::setLanguages({"ca", "fr"});
0496     QCOMPARE(i18n("Loadable modules"), QString::fromUtf8("Modules chargeables")); // The Catalan po doesn't have a translation so we get the English text
0497 }
0498 
0499 void KLocalizedStringTest::untranslatedText()
0500 {
0501     if (!m_hasFrench) {
0502         QSKIP("French test files not usable.");
0503     }
0504     KLocalizedString s = ki18n("Job");
0505     KLocalizedString::setLanguages({"fr"});
0506     QCOMPARE(s.untranslatedText(), "Job");
0507     QCOMPARE(s.toString(), QString::fromUtf8("Tâche"));
0508     QCOMPARE(s.untranslatedText(), "Job");
0509 }
0510 
0511 void KLocalizedStringTest::brokenStructTagUsages()
0512 {
0513     QTest::ignoreMessage(QtWarningMsg, QRegularExpression("Structuring tag \\('title'\\) cannot be subtag of phrase tag \\('emphasis'\\) in message {.*}."));
0514     QCOMPARE(xi18nc("@info", "<emphasis><title>History</title></emphasis>"), QString("<html><i>History</i></html>"));
0515 }
0516 
0517 void KLocalizedStringTest::brokenTags()
0518 {
0519     QTest::ignoreMessage(
0520         QtWarningMsg,
0521         QRegularExpression("Markup error in message {.*}: Opening and ending tag mismatch.. Last tag parsed: email. Complete message follows"));
0522     QCOMPARE(xi18nc("@info", "Send bug reports to <email>%1<email>.", "konqi@kde.org"), // notice the missing '/' before "email"
0523              QString("<html>Send bug reports to <email>konqi@kde.org<email>.</html>"));
0524 }
0525 
0526 #include <QFutureSynchronizer>
0527 #include <QThreadPool>
0528 #include <QtConcurrentRun>
0529 
0530 void KLocalizedStringTest::testThreads()
0531 {
0532     QThreadPool::globalInstance()->setMaxThreadCount(10);
0533     QFutureSynchronizer<void> sync;
0534 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0535     sync.addFuture(QtConcurrent::run(&KLocalizedStringTest::correctSubs, this));
0536     sync.addFuture(QtConcurrent::run(&KLocalizedStringTest::correctSubs, this));
0537     sync.addFuture(QtConcurrent::run(&KLocalizedStringTest::correctSubs, this));
0538     sync.addFuture(QtConcurrent::run(&KLocalizedStringTest::translateToFrench, this));
0539 #else
0540     sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::correctSubs));
0541     sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::correctSubs));
0542     sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::correctSubs));
0543     sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::translateToFrench));
0544 #endif
0545 #if KI18N_ENABLE_DEPRECATED_SINCE(5, 0)
0546     sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::translateQt));
0547     sync.addFuture(QtConcurrent::run(this, &KLocalizedStringTest::translateQt));
0548 #endif
0549     sync.waitForFinished();
0550     QThreadPool::globalInstance()->setMaxThreadCount(1); // delete those threads
0551 }
0552 
0553 void KLocalizedStringTest::testLazy()
0554 {
0555     if (!m_hasFrench) {
0556         QSKIP("French test files not usable.");
0557     }
0558     KLocalizedString s = kli18n("Job");
0559     KLocalizedString::setLanguages({"fr"});
0560     QCOMPARE(s.toString(), QString::fromUtf8("Tâche"));
0561 }
0562 
0563 QTEST_MAIN(KLocalizedStringTest)
0564 
0565 #include "moc_klocalizedstringtest.cpp"