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 < mc^2"), QString("E < mc^2")); 0178 QCOMPARE(xi18n("E ? <emphasis>mc^2</emphasis>"), QString("E ? *mc^2*")); 0179 QCOMPARE(xi18n("E < <emphasis>mc^2</emphasis>"), QString("E < *mc^2*")); 0180 QCOMPARE(xi18nc("@label", "E < <emphasis>mc^2</emphasis>"), QString("E < *mc^2*")); 0181 QCOMPARE(xi18nc("@info", "E < <emphasis>mc^2</emphasis>"), QString("<html>E < <i>mc^2</i></html>")); 0182 QCOMPARE(xi18nc("@info:status", "E < <emphasis>mc^2</emphasis>"), QString("E < *mc^2*")); 0183 QCOMPARE(xi18nc("@info:progress", "E < <emphasis>mc^2</emphasis>"), QString("E < *mc^2*")); 0184 QCOMPARE(xi18nc("@info:tooltip", "E < <emphasis>mc^2</emphasis>"), QString("<html>E < <i>mc^2</i></html>")); 0185 QCOMPARE(xi18nc("@info:shell", "E < <emphasis>mc^2</emphasis>"), QString("E < *mc^2*")); 0186 QCOMPARE(xi18n("E = mc^2"), QString("E = mc^2")); 0187 QCOMPARE(xi18n("E = mc^2"), QString("E = mc^2")); 0188 0189 // with additional whitespace 0190 QCOMPARE(xi18nc(" @info:progress ", "E < <emphasis>mc^2</emphasis>"), QString("E < *mc^2*")); 0191 QCOMPARE(xi18nc(" @info:tooltip ", "E < <emphasis>mc^2</emphasis>"), QString("<html>E < <i>mc^2</i></html>")); 0192 QCOMPARE(xi18nc(" @info: progress ", "E < <emphasis>mc^2</emphasis>"), // not parsed as a cue 0193 QString("<html>E < <i>mc^2</i></html>")); 0194 QCOMPARE(xi18nc(" @info: tooltip ", "E < <emphasis>mc^2</emphasis>"), // not parsed as a cue 0195 QString("<html>E < <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 < <emphasis>mc^2</emphasis>"), QString("<html>E < <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 <<a href=\"mailto:konqi@kde.org\">konqi@kde.org</a>>.</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->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 <<i>name</i>> 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"