File indexing completed on 2022-09-20 12:43:21

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