File indexing completed on 2024-04-21 03:54:22
0001 /* This file is part of the KDE libraries 0002 SPDX-FileCopyrightText: 2006, 2013 Chusslove Illich <caslav.ilic@gmx.net> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 // We don't want i18n to be expanded to i18nd here 0008 #undef TRANSLATION_DOMAIN 0009 0010 #include <cstdlib> 0011 0012 #include <QByteArray> 0013 #include <QCoreApplication> 0014 #include <QDir> 0015 #include <QFile> 0016 #include <QFileInfo> 0017 #include <QHash> 0018 #include <QLibrary> 0019 #include <QList> 0020 #include <QMutexLocker> 0021 #include <QPluginLoader> 0022 #include <QRecursiveMutex> 0023 #include <QStandardPaths> 0024 #include <QStringList> 0025 0026 #include <common_helpers_p.h> 0027 #include <kcatalog_p.h> 0028 #include <klocalizedstring.h> 0029 #include <ktranscript_p.h> 0030 #include <kuitsetup_p.h> 0031 0032 #include "ki18n_logging.h" 0033 0034 // Truncate string, for output of long messages. 0035 static QString shortenMessage(const QString &str) 0036 { 0037 const int maxlen = 20; 0038 if (str.length() <= maxlen) { 0039 return str; 0040 } else { 0041 return QStringView(str).left(maxlen) + QLatin1String("..."); 0042 } 0043 } 0044 0045 static void splitLocale(const QString &aLocale, QString &language, QString &country, QString &modifier, QString &charset) 0046 { 0047 QString locale = aLocale; 0048 0049 language.clear(); 0050 country.clear(); 0051 modifier.clear(); 0052 charset.clear(); 0053 0054 // In case there are several concatenated locale specifications, 0055 // truncate all but first. 0056 int f = locale.indexOf(QLatin1Char(':')); 0057 if (f >= 0) { 0058 locale.truncate(f); 0059 } 0060 0061 // now decompose into [language[_territory][.codeset][@modifier]] 0062 f = locale.indexOf(QLatin1Char('@')); 0063 if (f >= 0) { 0064 modifier = locale.mid(f + 1); 0065 locale.truncate(f); 0066 } 0067 0068 f = locale.indexOf(QLatin1Char('.')); 0069 if (f >= 0) { 0070 charset = locale.mid(f + 1); 0071 locale.truncate(f); 0072 } 0073 0074 f = locale.indexOf(QLatin1Char('_')); 0075 if (f >= 0) { 0076 country = locale.mid(f + 1); 0077 locale.truncate(f); 0078 } 0079 0080 language = locale; 0081 } 0082 0083 static void appendLocaleString(QStringList &languages, const QString &value) 0084 { 0085 // Process the value to create possible combinations. 0086 QString language; 0087 QString country; 0088 QString modifier; 0089 QString charset; 0090 splitLocale(value, language, country, modifier, charset); 0091 0092 if (language.isEmpty()) { 0093 return; 0094 } 0095 0096 if (!country.isEmpty() && !modifier.isEmpty()) { 0097 languages += language + QLatin1Char('_') + country + QLatin1Char('@') + modifier; 0098 } 0099 // NOTE: Priority is unclear in case both the country and 0100 // the modifier are present. Should really language@modifier be of 0101 // higher priority than language_country? 0102 // In at least one case (Serbian language), it is better this way. 0103 if (!modifier.isEmpty()) { 0104 languages += language + QLatin1Char('@') + modifier; 0105 } 0106 if (!country.isEmpty()) { 0107 languages += language + QLatin1Char('_') + country; 0108 } 0109 languages += language; 0110 } 0111 0112 static void appendLanguagesFromVariable(QStringList &languages, const char *envar, bool isList = false) 0113 { 0114 QByteArray qenvar(qgetenv(envar)); 0115 if (!qenvar.isEmpty()) { 0116 QString value = QFile::decodeName(qenvar); 0117 if (isList) { 0118 const auto listLanguages = value.split(QLatin1Char(':'), Qt::SkipEmptyParts); 0119 for (const QString &v : listLanguages) { 0120 appendLocaleString(languages, v); 0121 } 0122 } else { 0123 appendLocaleString(languages, value); 0124 } 0125 } 0126 } 0127 0128 #if !defined(Q_OS_UNIX) || defined(Q_OS_ANDROID) 0129 static void appendLanguagesFromQLocale(QStringList &languages, const QLocale &locale) 0130 { 0131 const QStringList uiLangs = locale.uiLanguages(); 0132 for (QString value : uiLangs) { // no const ref because of replace() below 0133 appendLocaleString(languages, value.replace(QLatin1Char('-'), QLatin1Char('_'))); 0134 } 0135 } 0136 #endif 0137 0138 // Extract the first country code from a list of language_COUNTRY strings. 0139 // Country code is converted to all lower case letters. 0140 static QString extractCountry(const QStringList &languages) 0141 { 0142 QString country; 0143 for (const QString &language : languages) { 0144 int pos1 = language.indexOf(QLatin1Char('_')); 0145 if (pos1 >= 0) { 0146 ++pos1; 0147 int pos2 = pos1; 0148 while (pos2 < language.length() && language[pos2].isLetter()) { 0149 ++pos2; 0150 } 0151 country = language.mid(pos1, pos2 - pos1); 0152 break; 0153 } 0154 } 0155 country = country.toLower(); 0156 return country; 0157 } 0158 0159 typedef qulonglong pluraln; 0160 typedef qlonglong intn; 0161 typedef qulonglong uintn; 0162 typedef double realn; 0163 0164 class KLocalizedStringPrivate 0165 { 0166 friend class KLocalizedString; 0167 0168 QByteArray domain; 0169 QStringList languages; 0170 Kuit::VisualFormat format; 0171 QByteArray context; 0172 QByteArray text; 0173 QByteArray plural; 0174 QStringList arguments; 0175 QList<QVariant> values; 0176 QHash<int, KLocalizedString> klsArguments; 0177 QHash<int, int> klsArgumentFieldWidths; 0178 QHash<int, QChar> klsArgumentFillChars; 0179 bool numberSet; 0180 pluraln number; 0181 int numberOrdinal; 0182 QHash<QString, QString> dynamicContext; 0183 bool markupAware; 0184 bool relaxedSubs; 0185 0186 KLocalizedStringPrivate() 0187 : format() 0188 , numberSet(false) 0189 , markupAware(false) 0190 , relaxedSubs(false) 0191 { 0192 } 0193 0194 static void translateRaw(const QByteArray &domain, 0195 const QStringList &languages, 0196 const QByteArray &msgctxt, 0197 const QByteArray &msgid, 0198 const QByteArray &msgid_plural, 0199 qulonglong n, 0200 QString &language, 0201 QString &translation); 0202 0203 QString toString(const QByteArray &domain, const QStringList &languages, Kuit::VisualFormat format, bool isArgument = false) const; 0204 QString substituteSimple(const QString &translation, const QStringList &arguments, QChar plchar = QLatin1Char('%'), bool isPartial = false) const; 0205 QString formatMarkup(const QByteArray &domain, const QString &language, const QString &context, const QString &text, Kuit::VisualFormat format) const; 0206 QString substituteTranscript(const QString &scriptedTranslation, 0207 const QString &language, 0208 const QString &country, 0209 const QString &ordinaryTranslation, 0210 const QStringList &arguments, 0211 const QList<QVariant> &values, 0212 bool &fallback) const; 0213 int resolveInterpolation(const QString &scriptedTranslation, 0214 int pos, 0215 const QString &language, 0216 const QString &country, 0217 const QString &ordinaryTranslation, 0218 const QStringList &arguments, 0219 const QList<QVariant> &values, 0220 QString &result, 0221 bool &fallback) const; 0222 QVariant segmentToValue(const QString &segment) const; 0223 QString postTranscript(const QString &pcall, 0224 const QString &language, 0225 const QString &country, 0226 const QString &finalTranslation, 0227 const QStringList &arguments, 0228 const QList<QVariant> &values) const; 0229 0230 static const KCatalog &getCatalog(const QByteArray &domain, const QString &language); 0231 static void locateScriptingModule(const QByteArray &domain, const QString &language); 0232 0233 static void loadTranscript(); 0234 0235 void checkNumber(pluraln a) 0236 { 0237 if (!plural.isEmpty() && !numberSet) { 0238 number = a; 0239 numberSet = true; 0240 numberOrdinal = arguments.size(); 0241 } 0242 } 0243 }; 0244 0245 typedef QHash<QString, KCatalog *> KCatalogPtrHash; 0246 0247 class KLocalizedStringPrivateStatics 0248 { 0249 public: 0250 QHash<QByteArray, KCatalogPtrHash> catalogs; 0251 QStringList languages; 0252 0253 QByteArray ourDomain; 0254 QByteArray applicationDomain; 0255 const QString codeLanguage; 0256 QStringList localeLanguages; 0257 0258 const QString theFence; 0259 const QString startInterp; 0260 const QString endInterp; 0261 const QChar scriptPlchar; 0262 const QChar scriptVachar; 0263 0264 const QString scriptDir; 0265 QHash<QString, QList<QByteArray>> scriptModules; 0266 QList<QStringList> scriptModulesToLoad; 0267 0268 bool loadTranscriptCalled; 0269 KTranscript *ktrs; 0270 0271 QHash<QString, KuitFormatter *> formatters; 0272 0273 QList<QByteArray> qtDomains; 0274 QList<int> qtDomainInsertCount; 0275 0276 QRecursiveMutex klspMutex; 0277 0278 KLocalizedStringPrivateStatics(); 0279 ~KLocalizedStringPrivateStatics(); 0280 0281 void initializeLocaleLanguages(); 0282 }; 0283 0284 KLocalizedStringPrivateStatics::KLocalizedStringPrivateStatics() 0285 : catalogs() 0286 , languages() 0287 0288 , ourDomain(QByteArrayLiteral("ki18n6")) 0289 , applicationDomain() 0290 , codeLanguage(QStringLiteral("en_US")) 0291 , localeLanguages() 0292 0293 , theFence(QStringLiteral("|/|")) 0294 , startInterp(QStringLiteral("$[")) 0295 , endInterp(QStringLiteral("]")) 0296 , scriptPlchar(QLatin1Char('%')) 0297 , scriptVachar(QLatin1Char('^')) 0298 0299 , scriptDir(QStringLiteral("LC_SCRIPTS")) 0300 , scriptModules() 0301 , scriptModulesToLoad() 0302 0303 , loadTranscriptCalled(false) 0304 , ktrs(nullptr) 0305 0306 , formatters() 0307 0308 , qtDomains() 0309 , qtDomainInsertCount() 0310 { 0311 initializeLocaleLanguages(); 0312 languages = localeLanguages; 0313 } 0314 0315 KLocalizedStringPrivateStatics::~KLocalizedStringPrivateStatics() 0316 { 0317 for (const KCatalogPtrHash &languageCatalogs : std::as_const(catalogs)) { 0318 qDeleteAll(languageCatalogs); 0319 } 0320 // ktrs is handled by QLibrary. 0321 // delete ktrs; 0322 qDeleteAll(formatters); 0323 } 0324 0325 Q_GLOBAL_STATIC(KLocalizedStringPrivateStatics, staticsKLSP) 0326 0327 void KLocalizedStringPrivateStatics::initializeLocaleLanguages() 0328 { 0329 QMutexLocker lock(&klspMutex); 0330 0331 // Collect languages by same order of priority as for gettext(3). 0332 // LANGUAGE contains list of language codes, not locale string. 0333 appendLanguagesFromVariable(localeLanguages, "LANGUAGE", true); 0334 appendLanguagesFromVariable(localeLanguages, "LC_ALL"); 0335 appendLanguagesFromVariable(localeLanguages, "LC_MESSAGES"); 0336 appendLanguagesFromVariable(localeLanguages, "LANG"); 0337 #if !defined(Q_OS_UNIX) || defined(Q_OS_ANDROID) 0338 // For non UNIX platforms the environment variables might not 0339 // suffice so we add system locale UI languages, too. 0340 appendLanguagesFromQLocale(localeLanguages, QLocale::system()); 0341 #endif 0342 } 0343 0344 KLocalizedString::KLocalizedString() 0345 : d(new KLocalizedStringPrivate) 0346 { 0347 } 0348 0349 KLocalizedString::KLocalizedString(const char *domain, const char *context, const char *text, const char *plural, bool markupAware) 0350 : d(new KLocalizedStringPrivate) 0351 { 0352 d->domain = domain; 0353 d->languages.clear(); 0354 d->format = Kuit::UndefinedFormat; 0355 d->context = context; 0356 d->text = text; 0357 d->plural = plural; 0358 d->numberSet = false; 0359 d->number = 0; 0360 d->numberOrdinal = 0; 0361 d->markupAware = markupAware; 0362 d->relaxedSubs = false; 0363 } 0364 0365 KLocalizedString::KLocalizedString(const KLocalizedString &rhs) 0366 : d(new KLocalizedStringPrivate(*rhs.d)) 0367 { 0368 } 0369 0370 KLocalizedString &KLocalizedString::operator=(const KLocalizedString &rhs) 0371 { 0372 if (&rhs != this) { 0373 *d = *rhs.d; 0374 } 0375 return *this; 0376 } 0377 0378 KLocalizedString::~KLocalizedString() = default; 0379 0380 bool KLocalizedString::isEmpty() const 0381 { 0382 return d->text.isEmpty(); 0383 } 0384 0385 void KLocalizedStringPrivate::translateRaw(const QByteArray &domain, 0386 const QStringList &languages, 0387 const QByteArray &msgctxt, 0388 const QByteArray &msgid, 0389 const QByteArray &msgid_plural, 0390 qulonglong n, 0391 QString &language, 0392 QString &msgstr) 0393 { 0394 KLocalizedStringPrivateStatics *s = staticsKLSP(); 0395 0396 // Empty msgid would result in returning the catalog header, 0397 // which is never intended, so warn and return empty translation. 0398 if (msgid.isNull() || msgid.isEmpty()) { 0399 qCWarning(KI18N) << "KLocalizedString: " 0400 "Trying to look up translation of \"\", fix the code."; 0401 language.clear(); 0402 msgstr.clear(); 0403 return; 0404 } 0405 // Gettext semantics allows empty context, but it is pointless, so warn. 0406 if (!msgctxt.isNull() && msgctxt.isEmpty()) { 0407 qCWarning(KI18N) << "KLocalizedString: " 0408 "Using \"\" as context, fix the code."; 0409 } 0410 // Gettext semantics allows empty plural, but it is pointless, so warn. 0411 if (!msgid_plural.isNull() && msgid_plural.isEmpty()) { 0412 qCWarning(KI18N) << "KLocalizedString: " 0413 "Using \"\" as plural text, fix the code."; 0414 } 0415 0416 // Set translation to text in code language, in case no translation found. 0417 msgstr = msgid_plural.isNull() || n == 1 ? QString::fromUtf8(msgid) : QString::fromUtf8(msgid_plural); 0418 language = s->codeLanguage; 0419 0420 if (domain.isEmpty()) { 0421 qCWarning(KI18N) << "KLocalizedString: Domain is not set for this string, translation will not work. Please see https://api.kde.org/frameworks/ki18n/html/prg_guide.html msgid:" << msgid << "msgid_plural:" << msgid_plural 0422 << "msgctxt:" << msgctxt; 0423 return; 0424 } 0425 0426 // Languages are ordered from highest to lowest priority. 0427 for (const QString &testLanguage : languages) { 0428 // If code language reached, no catalog lookup is needed. 0429 if (testLanguage == s->codeLanguage) { 0430 return; 0431 } 0432 const KCatalog &catalog = getCatalog(domain, testLanguage); 0433 QString testMsgstr; 0434 if (!msgctxt.isNull() && !msgid_plural.isNull()) { 0435 testMsgstr = catalog.translate(msgctxt, msgid, msgid_plural, n); 0436 } else if (!msgid_plural.isNull()) { 0437 testMsgstr = catalog.translate(msgid, msgid_plural, n); 0438 } else if (!msgctxt.isNull()) { 0439 testMsgstr = catalog.translate(msgctxt, msgid); 0440 } else { 0441 testMsgstr = catalog.translate(msgid); 0442 } 0443 if (!testMsgstr.isEmpty()) { 0444 // Translation found. 0445 language = testLanguage; 0446 msgstr = testMsgstr; 0447 return; 0448 } 0449 } 0450 } 0451 0452 QString KLocalizedString::toString() const 0453 { 0454 return d->toString(d->domain, d->languages, d->format); 0455 } 0456 0457 QString KLocalizedString::toString(const char *domain) const 0458 { 0459 return d->toString(domain, d->languages, d->format); 0460 } 0461 0462 QString KLocalizedString::toString(const QStringList &languages) const 0463 { 0464 return d->toString(d->domain, languages, d->format); 0465 } 0466 0467 QString KLocalizedString::toString(Kuit::VisualFormat format) const 0468 { 0469 return d->toString(d->domain, d->languages, format); 0470 } 0471 0472 QString KLocalizedStringPrivate::toString(const QByteArray &domain, const QStringList &languages, Kuit::VisualFormat format, bool isArgument) const 0473 { 0474 KLocalizedStringPrivateStatics *s = staticsKLSP(); 0475 0476 QMutexLocker lock(&s->klspMutex); 0477 0478 // Assure the message has been supplied. 0479 if (text.isEmpty()) { 0480 qCWarning(KI18N) << "Trying to convert empty KLocalizedString to QString."; 0481 #ifndef NDEBUG 0482 return QStringLiteral("(I18N_EMPTY_MESSAGE)"); 0483 #else 0484 return QString(); 0485 #endif 0486 } 0487 0488 // Check whether plural argument has been supplied, if message has plural. 0489 if (!plural.isEmpty() && !numberSet) { 0490 qCWarning(KI18N) << "Plural argument to message" << shortenMessage(QString::fromUtf8(text)) << "not supplied before conversion."; 0491 } 0492 0493 // Resolve inputs. 0494 QByteArray resolvedDomain = domain; 0495 if (resolvedDomain.isEmpty()) { 0496 resolvedDomain = s->applicationDomain; 0497 } 0498 QStringList resolvedLanguages = languages; 0499 if (resolvedLanguages.isEmpty()) { 0500 resolvedLanguages = s->languages; 0501 } 0502 Kuit::VisualFormat resolvedFormat = format; 0503 0504 // Get raw translation. 0505 QString language; 0506 QString rawTranslation; 0507 translateRaw(resolvedDomain, resolvedLanguages, context, text, plural, number, language, rawTranslation); 0508 QString country = extractCountry(resolvedLanguages); 0509 0510 // Set ordinary translation and possibly scripted translation. 0511 QString translation; 0512 QString scriptedTranslation; 0513 int fencePos = rawTranslation.indexOf(s->theFence); 0514 if (fencePos > 0) { 0515 // Script fence has been found, strip the scripted from the 0516 // ordinary translation. 0517 translation = rawTranslation.left(fencePos); 0518 0519 // Scripted translation. 0520 scriptedTranslation = rawTranslation.mid(fencePos + s->theFence.length()); 0521 0522 // Try to initialize Transcript if not initialized and script not empty. 0523 // FIXME: And also if Transcript not disabled: where to configure this? 0524 if (!s->loadTranscriptCalled && !scriptedTranslation.isEmpty()) { 0525 loadTranscript(); 0526 0527 // Definitions from this library's scripting module 0528 // must be available to all other modules. 0529 // So force creation of this library's catalog here, 0530 // to make sure the scripting module is loaded. 0531 getCatalog(s->ourDomain, language); 0532 } 0533 } else if (fencePos < 0) { 0534 // No script fence, use translation as is. 0535 translation = rawTranslation; 0536 } else { // fencePos == 0 0537 // The msgstr starts with the script fence, no ordinary translation. 0538 // This is not allowed, consider message not translated. 0539 qCWarning(KI18N) << "Scripted message" << shortenMessage(translation) << "without ordinary translation, discarded."; 0540 translation = plural.isEmpty() || number == 1 ? QString::fromUtf8(text) : QString::fromUtf8(plural); 0541 } 0542 0543 // Resolve substituted KLocalizedString arguments. 0544 QStringList resolvedArguments; 0545 QList<QVariant> resolvedValues; 0546 for (int i = 0; i < arguments.size(); i++) { 0547 auto lsIt = klsArguments.constFind(i); 0548 if (lsIt != klsArguments.constEnd()) { 0549 const KLocalizedString &kls = *lsIt; 0550 int fieldWidth = klsArgumentFieldWidths.value(i); 0551 QChar fillChar = klsArgumentFillChars.value(i); 0552 // Override argument's languages and format, but not domain. 0553 bool isArgumentSub = true; 0554 QString resdArg = kls.d->toString(kls.d->domain, resolvedLanguages, resolvedFormat, isArgumentSub); 0555 resolvedValues.append(resdArg); 0556 if (markupAware && !kls.d->markupAware) { 0557 resdArg = Kuit::escape(resdArg); 0558 } 0559 resdArg = QStringLiteral("%1").arg(resdArg, fieldWidth, fillChar); 0560 resolvedArguments.append(resdArg); 0561 } else { 0562 QString resdArg = arguments[i]; 0563 if (markupAware) { 0564 resdArg = Kuit::escape(resdArg); 0565 } 0566 resolvedArguments.append(resdArg); 0567 resolvedValues.append(values[i]); 0568 } 0569 } 0570 0571 // Substitute placeholders in ordinary translation. 0572 QString finalTranslation = substituteSimple(translation, resolvedArguments); 0573 if (markupAware && !isArgument) { 0574 // Resolve markup in ordinary translation. 0575 finalTranslation = formatMarkup(resolvedDomain, language, QString::fromUtf8(context), finalTranslation, resolvedFormat); 0576 } 0577 0578 // If there is also a scripted translation. 0579 if (!scriptedTranslation.isEmpty()) { 0580 // Evaluate scripted translation. 0581 bool fallback = false; 0582 scriptedTranslation = substituteTranscript(scriptedTranslation, language, country, finalTranslation, resolvedArguments, resolvedValues, fallback); 0583 0584 // If any translation produced and no fallback requested. 0585 if (!scriptedTranslation.isEmpty() && !fallback) { 0586 if (markupAware && !isArgument) { 0587 // Resolve markup in scripted translation. 0588 scriptedTranslation = formatMarkup(resolvedDomain, language, QString::fromUtf8(context), scriptedTranslation, resolvedFormat); 0589 } 0590 finalTranslation = scriptedTranslation; 0591 } 0592 } 0593 0594 // Execute any scripted post calls; they cannot modify the final result, 0595 // but are used to set states. 0596 if (s->ktrs != nullptr) { 0597 const QStringList pcalls = s->ktrs->postCalls(language); 0598 for (const QString &pcall : pcalls) { 0599 postTranscript(pcall, language, country, finalTranslation, resolvedArguments, resolvedValues); 0600 } 0601 } 0602 0603 return finalTranslation; 0604 } 0605 0606 QString KLocalizedStringPrivate::substituteSimple(const QString &translation, const QStringList &arguments, QChar plchar, bool isPartial) const 0607 { 0608 #ifdef NDEBUG 0609 Q_UNUSED(isPartial); 0610 #endif 0611 0612 QStringList tsegs; // text segments per placeholder occurrence 0613 QList<int> plords; // ordinal numbers per placeholder occurrence 0614 #ifndef NDEBUG 0615 QList<int> ords; // indicates which placeholders are present 0616 #endif 0617 int slen = translation.length(); 0618 int spos = 0; 0619 int tpos = translation.indexOf(plchar); 0620 while (tpos >= 0) { 0621 int ctpos = tpos; 0622 0623 ++tpos; 0624 if (tpos == slen) { 0625 break; 0626 } 0627 0628 if (translation[tpos].digitValue() > 0) { 0629 // NOTE: %0 is not considered a placeholder. 0630 // Get the placeholder ordinal. 0631 int plord = 0; 0632 while (tpos < slen && translation[tpos].digitValue() >= 0) { 0633 plord = 10 * plord + translation[tpos].digitValue(); 0634 ++tpos; 0635 } 0636 --plord; // ordinals are zero based 0637 0638 #ifndef NDEBUG 0639 // Perhaps enlarge storage for indicators. 0640 // Note that QList<int> will initialize new elements to 0, 0641 // as they are supposed to be. 0642 if (plord >= ords.size()) { 0643 ords.resize(plord + 1); 0644 } 0645 0646 // Indicate that placeholder with computed ordinal is present. 0647 ords[plord] = 1; 0648 #endif 0649 0650 // Store text segment prior to placeholder and placeholder number. 0651 tsegs.append(translation.mid(spos, ctpos - spos)); 0652 plords.append(plord); 0653 0654 // Position of next text segment. 0655 spos = tpos; 0656 } 0657 0658 tpos = translation.indexOf(plchar, tpos); 0659 } 0660 // Store last text segment. 0661 tsegs.append(translation.mid(spos)); 0662 0663 #ifndef NDEBUG 0664 // Perhaps enlarge storage for plural-number ordinal. 0665 if (!plural.isEmpty() && numberOrdinal >= ords.size()) { 0666 ords.resize(numberOrdinal + 1); 0667 } 0668 0669 // Message might have plural but without plural placeholder, which is an 0670 // allowed state. To ease further logic, indicate that plural placeholder 0671 // is present anyway if message has plural. 0672 if (!plural.isEmpty()) { 0673 ords[numberOrdinal] = 1; 0674 } 0675 #endif 0676 0677 // Assemble the final string from text segments and arguments. 0678 QString finalTranslation; 0679 for (int i = 0; i < plords.size(); i++) { 0680 finalTranslation.append(tsegs.at(i)); 0681 if (plords.at(i) >= arguments.size()) { // too little arguments 0682 // put back the placeholder 0683 finalTranslation.append(QLatin1Char('%') + QString::number(plords.at(i) + 1)); 0684 #ifndef NDEBUG 0685 if (!isPartial) { 0686 // spoof the message 0687 finalTranslation.append(QStringLiteral("(I18N_ARGUMENT_MISSING)")); 0688 } 0689 #endif 0690 } else { // just fine 0691 finalTranslation.append(arguments.at(plords.at(i))); 0692 } 0693 } 0694 finalTranslation.append(tsegs.last()); 0695 0696 #ifndef NDEBUG 0697 if (!isPartial && !relaxedSubs) { 0698 // Check that there are no gaps in numbering sequence of placeholders. 0699 bool gaps = false; 0700 for (int i = 0; i < ords.size(); i++) { 0701 if (!ords.at(i)) { 0702 gaps = true; 0703 qCWarning(KI18N).nospace() << "Placeholder %" << QString::number(i + 1) << " skipped in message " << shortenMessage(translation); 0704 } 0705 } 0706 // If no gaps, check for mismatch between the number of 0707 // unique placeholders and actually supplied arguments. 0708 if (!gaps && ords.size() != arguments.size()) { 0709 qCWarning(KI18N) << arguments.size() << "instead of" << ords.size() << "arguments to message" << shortenMessage(translation) 0710 << "supplied before conversion"; 0711 } 0712 0713 // Some spoofs. 0714 if (gaps) { 0715 finalTranslation.append(QStringLiteral("(I18N_GAPS_IN_PLACEHOLDER_SEQUENCE)")); 0716 } 0717 if (ords.size() < arguments.size()) { 0718 finalTranslation.append(QStringLiteral("(I18N_EXCESS_ARGUMENTS_SUPPLIED)")); 0719 } 0720 } 0721 if (!isPartial) { 0722 if (!plural.isEmpty() && !numberSet) { 0723 finalTranslation.append(QStringLiteral("(I18N_PLURAL_ARGUMENT_MISSING)")); 0724 } 0725 } 0726 #endif 0727 0728 return finalTranslation; 0729 } 0730 0731 QString KLocalizedStringPrivate::formatMarkup(const QByteArray &domain, 0732 const QString &language, 0733 const QString &context, 0734 const QString &text, 0735 Kuit::VisualFormat format) const 0736 { 0737 KLocalizedStringPrivateStatics *s = staticsKLSP(); 0738 0739 QHash<QString, KuitFormatter *>::iterator formatter = s->formatters.find(language); 0740 if (formatter == s->formatters.end()) { 0741 formatter = s->formatters.insert(language, new KuitFormatter(language)); 0742 } 0743 return (*formatter)->format(domain, context, text, format); 0744 } 0745 0746 QString KLocalizedStringPrivate::substituteTranscript(const QString &scriptedTranslation, 0747 const QString &language, 0748 const QString &country, 0749 const QString &ordinaryTranslation, 0750 const QStringList &arguments, 0751 const QList<QVariant> &values, 0752 bool &fallback) const 0753 { 0754 KLocalizedStringPrivateStatics *s = staticsKLSP(); 0755 0756 if (s->ktrs == nullptr) { 0757 // Scripting engine not available. 0758 return QString(); 0759 } 0760 0761 // Iterate by interpolations. 0762 QString finalTranslation; 0763 fallback = false; 0764 int ppos = 0; 0765 int tpos = scriptedTranslation.indexOf(s->startInterp); 0766 while (tpos >= 0) { 0767 // Resolve substitutions in preceding text. 0768 QString ptext = substituteSimple(scriptedTranslation.mid(ppos, tpos - ppos), arguments, s->scriptPlchar, true); 0769 finalTranslation.append(ptext); 0770 0771 // Resolve interpolation. 0772 QString result; 0773 bool fallbackLocal; 0774 tpos = resolveInterpolation(scriptedTranslation, tpos, language, country, ordinaryTranslation, arguments, values, result, fallbackLocal); 0775 0776 // If there was a problem in parsing the interpolation, cannot proceed 0777 // (debug info already reported while parsing). 0778 if (tpos < 0) { 0779 return QString(); 0780 } 0781 // If fallback has been explicitly requested, indicate global fallback 0782 // but proceed with evaluations (other interpolations may set states). 0783 if (fallbackLocal) { 0784 fallback = true; 0785 } 0786 0787 // Add evaluated interpolation to the text. 0788 finalTranslation.append(result); 0789 0790 // On to next interpolation. 0791 ppos = tpos; 0792 tpos = scriptedTranslation.indexOf(s->startInterp, tpos); 0793 } 0794 // Last text segment. 0795 finalTranslation.append(substituteSimple(scriptedTranslation.mid(ppos), arguments, s->scriptPlchar, true)); 0796 0797 // Return empty string if fallback was requested. 0798 return fallback ? QString() : finalTranslation; 0799 } 0800 0801 int KLocalizedStringPrivate::resolveInterpolation(const QString &scriptedTranslation, 0802 int pos, 0803 const QString &language, 0804 const QString &country, 0805 const QString &ordinaryTranslation, 0806 const QStringList &arguments, 0807 const QList<QVariant> &values, 0808 QString &result, 0809 bool &fallback) const 0810 { 0811 // pos is the position of opening character sequence. 0812 // Returns the position of first character after closing sequence, 0813 // or -1 in case of parsing error. 0814 // result is set to result of Transcript evaluation. 0815 // fallback is set to true if Transcript evaluation requested so. 0816 0817 KLocalizedStringPrivateStatics *s = staticsKLSP(); 0818 0819 result.clear(); 0820 fallback = false; 0821 0822 // Split interpolation into arguments. 0823 QList<QVariant> iargs; 0824 const int slen = scriptedTranslation.length(); 0825 const int islen = s->startInterp.length(); 0826 const int ielen = s->endInterp.length(); 0827 int tpos = pos + s->startInterp.length(); 0828 while (1) { 0829 // Skip whitespace. 0830 while (tpos < slen && scriptedTranslation[tpos].isSpace()) { 0831 ++tpos; 0832 } 0833 if (tpos == slen) { 0834 qCWarning(KI18N) << "Unclosed interpolation" << scriptedTranslation.mid(pos, tpos - pos) << "in message" << shortenMessage(scriptedTranslation); 0835 return -1; 0836 } 0837 if (QStringView(scriptedTranslation).mid(tpos, ielen) == s->endInterp) { 0838 break; // no more arguments 0839 } 0840 0841 // Parse argument: may be concatenated from free and quoted text, 0842 // and sub-interpolations. 0843 // Free and quoted segments may contain placeholders, substitute them; 0844 // recurse into sub-interpolations. 0845 // Free segments may be value references, parse and record for 0846 // consideration at the end. 0847 // Mind backslash escapes throughout. 0848 QStringList segs; 0849 QVariant vref; 0850 while (!scriptedTranslation[tpos].isSpace() && scriptedTranslation.mid(tpos, ielen) != s->endInterp) { 0851 if (scriptedTranslation[tpos] == QLatin1Char('\'')) { // quoted segment 0852 QString seg; 0853 ++tpos; // skip opening quote 0854 // Find closing quote. 0855 while (tpos < slen && scriptedTranslation[tpos] != QLatin1Char('\'')) { 0856 if (scriptedTranslation[tpos] == QLatin1Char('\\')) { 0857 ++tpos; // escape next character 0858 } 0859 seg.append(scriptedTranslation[tpos]); 0860 ++tpos; 0861 } 0862 if (tpos == slen) { 0863 qCWarning(KI18N) << "Unclosed quote in interpolation" << scriptedTranslation.mid(pos, tpos - pos) << "in message" 0864 << shortenMessage(scriptedTranslation); 0865 return -1; 0866 } 0867 0868 // Append to list of segments, resolving placeholders. 0869 segs.append(substituteSimple(seg, arguments, s->scriptPlchar, true)); 0870 0871 ++tpos; // skip closing quote 0872 } else if (scriptedTranslation.mid(tpos, islen) == s->startInterp) { // sub-interpolation 0873 QString resultLocal; 0874 bool fallbackLocal; 0875 tpos = resolveInterpolation(scriptedTranslation, tpos, language, country, ordinaryTranslation, arguments, values, resultLocal, fallbackLocal); 0876 if (tpos < 0) { // unrecoverable problem in sub-interpolation 0877 // Error reported in the subcall. 0878 return tpos; 0879 } 0880 if (fallbackLocal) { // sub-interpolation requested fallback 0881 fallback = true; 0882 } 0883 segs.append(resultLocal); 0884 } else { // free segment 0885 QString seg; 0886 // Find whitespace, quote, opening or closing sequence. 0887 while (tpos < slen && !scriptedTranslation[tpos].isSpace() // 0888 && scriptedTranslation[tpos] != QLatin1Char('\'') // 0889 && scriptedTranslation.mid(tpos, islen) != s->startInterp // 0890 && scriptedTranslation.mid(tpos, ielen) != s->endInterp) { 0891 if (scriptedTranslation[tpos] == QLatin1Char('\\')) { 0892 ++tpos; // escape next character 0893 } 0894 seg.append(scriptedTranslation[tpos]); 0895 ++tpos; 0896 } 0897 if (tpos == slen) { 0898 qCWarning(KI18N) << "Non-terminated interpolation" << scriptedTranslation.mid(pos, tpos - pos) << "in message" 0899 << shortenMessage(scriptedTranslation); 0900 return -1; 0901 } 0902 0903 // The free segment may look like a value reference; 0904 // in that case, record which value it would reference, 0905 // and add verbatim to the segment list. 0906 // Otherwise, do a normal substitution on the segment. 0907 vref = segmentToValue(seg); 0908 if (vref.isValid()) { 0909 segs.append(seg); 0910 } else { 0911 segs.append(substituteSimple(seg, arguments, s->scriptPlchar, true)); 0912 } 0913 } 0914 } 0915 0916 // Append this argument to rest of the arguments. 0917 // If the there was a single text segment and it was a proper value 0918 // reference, add it instead of the joined segments. 0919 // Otherwise, add the joined segments. 0920 if (segs.size() == 1 && vref.isValid()) { 0921 iargs.append(vref); 0922 } else { 0923 iargs.append(segs.join(QString())); 0924 } 0925 } 0926 tpos += ielen; // skip to first character after closing sequence 0927 0928 // NOTE: Why not substitute placeholders (via substituteSimple) in one 0929 // global pass, then handle interpolations in second pass? Because then 0930 // there is the danger of substituted text or sub-interpolations producing 0931 // quotes and escapes themselves, which would mess up the parsing. 0932 0933 // Evaluate interpolation. 0934 QString msgctxt = QString::fromUtf8(context); 0935 QString msgid = QString::fromUtf8(text); 0936 QString scriptError; 0937 bool fallbackLocal; 0938 result = s->ktrs->eval(iargs, 0939 language, 0940 country, 0941 msgctxt, 0942 dynamicContext, 0943 msgid, 0944 arguments, 0945 values, 0946 ordinaryTranslation, 0947 s->scriptModulesToLoad, 0948 scriptError, 0949 fallbackLocal); 0950 // s->scriptModulesToLoad will be cleared during the call. 0951 0952 if (fallbackLocal) { // evaluation requested fallback 0953 fallback = true; 0954 } 0955 if (!scriptError.isEmpty()) { // problem with evaluation 0956 fallback = true; // also signal fallback 0957 if (!scriptError.isEmpty()) { 0958 qCWarning(KI18N) << "Interpolation" << scriptedTranslation.mid(pos, tpos - pos) << "in" << shortenMessage(scriptedTranslation) 0959 << "failed:" << scriptError; 0960 } 0961 } 0962 0963 return tpos; 0964 } 0965 0966 QVariant KLocalizedStringPrivate::segmentToValue(const QString &segment) const 0967 { 0968 KLocalizedStringPrivateStatics *s = staticsKLSP(); 0969 0970 // Return invalid variant if segment is either not a proper 0971 // value reference, or the reference is out of bounds. 0972 0973 // Value reference must start with a special character. 0974 if (!segment.startsWith(s->scriptVachar)) { 0975 return QVariant(); 0976 } 0977 0978 // Reference number must start with 1-9. 0979 // (If numstr is empty, toInt() will return 0.) 0980 QString numstr = segment.mid(1); 0981 int numstrAsInt = QStringView(numstr).left(1).toInt(); 0982 if (numstrAsInt < 1) { 0983 return QVariant(); 0984 } 0985 0986 // Number must be valid and in bounds. 0987 bool ok; 0988 int index = numstr.toInt(&ok) - 1; 0989 if (!ok || index >= values.size()) { 0990 return QVariant(); 0991 } 0992 0993 // Passed all hoops. 0994 return values.at(index); 0995 } 0996 0997 QString KLocalizedStringPrivate::postTranscript(const QString &pcall, 0998 const QString &language, 0999 const QString &country, 1000 const QString &finalTranslation, 1001 const QStringList &arguments, 1002 const QList<QVariant> &values) const 1003 { 1004 KLocalizedStringPrivateStatics *s = staticsKLSP(); 1005 1006 if (s->ktrs == nullptr) { 1007 // Scripting engine not available. 1008 // (Though this cannot happen, we wouldn't be here then.) 1009 return QString(); 1010 } 1011 1012 // Resolve the post call. 1013 QList<QVariant> iargs; 1014 iargs.append(pcall); 1015 QString msgctxt = QString::fromUtf8(context); 1016 QString msgid = QString::fromUtf8(text); 1017 QString scriptError; 1018 bool fallback; 1019 s->ktrs->eval(iargs, language, country, msgctxt, dynamicContext, msgid, arguments, values, finalTranslation, s->scriptModulesToLoad, scriptError, fallback); 1020 // s->scriptModulesToLoad will be cleared during the call. 1021 1022 // If the evaluation went wrong. 1023 if (!scriptError.isEmpty()) { 1024 qCWarning(KI18N) << "Post call" << pcall << "for message" << shortenMessage(msgid) << "failed:" << scriptError; 1025 return QString(); 1026 } 1027 1028 return finalTranslation; 1029 } 1030 1031 KLocalizedString KLocalizedString::withLanguages(const QStringList &languages) const 1032 { 1033 KLocalizedString kls(*this); 1034 kls.d->languages = languages; 1035 return kls; 1036 } 1037 1038 KLocalizedString KLocalizedString::withDomain(const char *domain) const 1039 { 1040 KLocalizedString kls(*this); 1041 kls.d->domain = domain; 1042 return kls; 1043 } 1044 1045 KLocalizedString KLocalizedString::withFormat(Kuit::VisualFormat format) const 1046 { 1047 KLocalizedString kls(*this); 1048 kls.d->format = format; 1049 return kls; 1050 } 1051 1052 KLocalizedString KLocalizedString::subs(int a, int fieldWidth, int base, QChar fillChar) const 1053 { 1054 KLocalizedString kls(*this); 1055 kls.d->checkNumber(std::abs(a)); 1056 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar)); 1057 kls.d->values.append(static_cast<intn>(a)); 1058 return kls; 1059 } 1060 1061 KLocalizedString KLocalizedString::subs(uint a, int fieldWidth, int base, QChar fillChar) const 1062 { 1063 KLocalizedString kls(*this); 1064 kls.d->checkNumber(a); 1065 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar)); 1066 kls.d->values.append(static_cast<uintn>(a)); 1067 return kls; 1068 } 1069 1070 KLocalizedString KLocalizedString::subs(long a, int fieldWidth, int base, QChar fillChar) const 1071 { 1072 KLocalizedString kls(*this); 1073 kls.d->checkNumber(std::abs(a)); 1074 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar)); 1075 kls.d->values.append(static_cast<intn>(a)); 1076 return kls; 1077 } 1078 1079 KLocalizedString KLocalizedString::subs(ulong a, int fieldWidth, int base, QChar fillChar) const 1080 { 1081 KLocalizedString kls(*this); 1082 kls.d->checkNumber(a); 1083 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar)); 1084 kls.d->values.append(static_cast<uintn>(a)); 1085 return kls; 1086 } 1087 1088 KLocalizedString KLocalizedString::subs(qlonglong a, int fieldWidth, int base, QChar fillChar) const 1089 { 1090 KLocalizedString kls(*this); 1091 kls.d->checkNumber(qAbs(a)); 1092 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar)); 1093 kls.d->values.append(static_cast<intn>(a)); 1094 return kls; 1095 } 1096 1097 KLocalizedString KLocalizedString::subs(qulonglong a, int fieldWidth, int base, QChar fillChar) const 1098 { 1099 KLocalizedString kls(*this); 1100 kls.d->checkNumber(a); 1101 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar)); 1102 kls.d->values.append(static_cast<uintn>(a)); 1103 return kls; 1104 } 1105 1106 KLocalizedString KLocalizedString::subs(double a, int fieldWidth, char format, int precision, QChar fillChar) const 1107 { 1108 KLocalizedString kls(*this); 1109 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, format, precision, fillChar)); 1110 kls.d->values.append(static_cast<realn>(a)); 1111 return kls; 1112 } 1113 1114 KLocalizedString KLocalizedString::subs(QChar a, int fieldWidth, QChar fillChar) const 1115 { 1116 KLocalizedString kls(*this); 1117 QString baseArg = QString(a); 1118 QString fmtdArg = QStringLiteral("%1").arg(a, fieldWidth, fillChar); 1119 kls.d->arguments.append(fmtdArg); 1120 kls.d->values.append(baseArg); 1121 return kls; 1122 } 1123 1124 KLocalizedString KLocalizedString::subs(const QString &a, int fieldWidth, QChar fillChar) const 1125 { 1126 KLocalizedString kls(*this); 1127 QString baseArg = a; 1128 QString fmtdArg = QStringLiteral("%1").arg(a, fieldWidth, fillChar); 1129 kls.d->arguments.append(fmtdArg); 1130 kls.d->values.append(baseArg); 1131 return kls; 1132 } 1133 1134 KLocalizedString KLocalizedString::subs(const KLocalizedString &a, int fieldWidth, QChar fillChar) const 1135 { 1136 KLocalizedString kls(*this); 1137 // KLocalizedString arguments must be resolved inside toString 1138 // when the domain, language, visual format, etc. become known. 1139 int i = kls.d->arguments.size(); 1140 kls.d->klsArguments[i] = a; 1141 kls.d->klsArgumentFieldWidths[i] = fieldWidth; 1142 kls.d->klsArgumentFillChars[i] = fillChar; 1143 kls.d->arguments.append(QString()); 1144 kls.d->values.append(0); 1145 return kls; 1146 } 1147 1148 KLocalizedString KLocalizedString::inContext(const QString &key, const QString &value) const 1149 { 1150 KLocalizedString kls(*this); 1151 kls.d->dynamicContext[key] = value; 1152 return kls; 1153 } 1154 1155 KLocalizedString KLocalizedString::relaxSubs() const 1156 { 1157 KLocalizedString kls(*this); 1158 kls.d->relaxedSubs = true; 1159 return kls; 1160 } 1161 1162 KLocalizedString KLocalizedString::ignoreMarkup() const 1163 { 1164 KLocalizedString kls(*this); 1165 kls.d->markupAware = false; 1166 return kls; 1167 } 1168 1169 QByteArray KLocalizedString::untranslatedText() const 1170 { 1171 return d->text; 1172 } 1173 1174 void KLocalizedString::setApplicationDomain(const QByteArray &domain) 1175 { 1176 KLocalizedStringPrivateStatics *s = staticsKLSP(); 1177 1178 QMutexLocker lock(&s->klspMutex); 1179 1180 s->applicationDomain = domain; 1181 } 1182 1183 QByteArray KLocalizedString::applicationDomain() 1184 { 1185 KLocalizedStringPrivateStatics *s = staticsKLSP(); 1186 1187 return s->applicationDomain; 1188 } 1189 1190 QStringList KLocalizedString::languages() 1191 { 1192 KLocalizedStringPrivateStatics *s = staticsKLSP(); 1193 1194 return s->languages; 1195 } 1196 1197 void KLocalizedString::setLanguages(const QStringList &languages) 1198 { 1199 KLocalizedStringPrivateStatics *s = staticsKLSP(); 1200 1201 QMutexLocker lock(&s->klspMutex); 1202 1203 s->languages = languages; 1204 } 1205 1206 void KLocalizedString::clearLanguages() 1207 { 1208 KLocalizedStringPrivateStatics *s = staticsKLSP(); 1209 1210 QMutexLocker lock(&s->klspMutex); 1211 1212 s->languages = s->localeLanguages; 1213 } 1214 1215 bool KLocalizedString::isApplicationTranslatedInto(const QString &language) 1216 { 1217 KLocalizedStringPrivateStatics *s = staticsKLSP(); 1218 1219 return language == s->codeLanguage || !KCatalog::catalogLocaleDir(s->applicationDomain, language).isEmpty(); 1220 } 1221 1222 QSet<QString> KLocalizedString::availableApplicationTranslations() 1223 { 1224 return availableDomainTranslations(staticsKLSP()->applicationDomain); 1225 } 1226 1227 QSet<QString> KLocalizedString::availableDomainTranslations(const QByteArray &domain) 1228 { 1229 QSet<QString> availableLanguages; 1230 1231 if (!domain.isEmpty()) { 1232 availableLanguages = KCatalog::availableCatalogLanguages(domain); 1233 availableLanguages.insert(staticsKLSP()->codeLanguage); 1234 } 1235 1236 return availableLanguages; 1237 } 1238 1239 const KCatalog &KLocalizedStringPrivate::getCatalog(const QByteArray &domain, const QString &language) 1240 { 1241 KLocalizedStringPrivateStatics *s = staticsKLSP(); 1242 1243 QMutexLocker lock(&s->klspMutex); 1244 1245 QHash<QByteArray, KCatalogPtrHash>::iterator languageCatalogs = s->catalogs.find(domain); 1246 if (languageCatalogs == s->catalogs.end()) { 1247 languageCatalogs = s->catalogs.insert(domain, KCatalogPtrHash()); 1248 } 1249 KCatalogPtrHash::iterator catalog = languageCatalogs->find(language); 1250 if (catalog == languageCatalogs->end()) { 1251 catalog = languageCatalogs->insert(language, new KCatalog(domain, language)); 1252 locateScriptingModule(domain, language); 1253 } 1254 return **catalog; 1255 } 1256 1257 void KLocalizedStringPrivate::locateScriptingModule(const QByteArray &domain, const QString &language) 1258 { 1259 KLocalizedStringPrivateStatics *s = staticsKLSP(); 1260 1261 QMutexLocker lock(&s->klspMutex); 1262 1263 QString modapath = 1264 QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("locale/%1/%2/%3/%3.js").arg(language, s->scriptDir, QLatin1String{domain})); 1265 1266 // If the module exists and hasn't been already included. 1267 if (!modapath.isEmpty() && !s->scriptModules[language].contains(domain)) { 1268 // Indicate that the module has been considered. 1269 s->scriptModules[language].append(domain); 1270 1271 // Store the absolute path and language of the module, 1272 // to load on next script evaluation. 1273 QStringList module; 1274 module.append(modapath); 1275 module.append(language); 1276 s->scriptModulesToLoad.append(module); 1277 } 1278 } 1279 1280 extern "C" { 1281 typedef KTranscript *(*InitFunc)(); 1282 } 1283 1284 void KLocalizedStringPrivate::loadTranscript() 1285 { 1286 KLocalizedStringPrivateStatics *s = staticsKLSP(); 1287 1288 QMutexLocker lock(&s->klspMutex); 1289 1290 s->loadTranscriptCalled = true; 1291 s->ktrs = nullptr; // null indicates that Transcript is not available 1292 1293 // QPluginLoader is just used to find the plugin 1294 QPluginLoader loader(QStringLiteral("kf6/ktranscript")); 1295 if (loader.fileName().isEmpty()) { 1296 qCWarning(KI18N) << "Cannot find Transcript plugin."; 1297 return; 1298 } 1299 1300 QLibrary lib(loader.fileName()); 1301 if (!lib.load()) { 1302 qCWarning(KI18N) << "Cannot load Transcript plugin:" << lib.errorString(); 1303 return; 1304 } 1305 1306 InitFunc initf = (InitFunc)lib.resolve("load_transcript"); 1307 if (!initf) { 1308 lib.unload(); 1309 qCWarning(KI18N) << "Cannot find function load_transcript in Transcript plugin."; 1310 return; 1311 } 1312 1313 s->ktrs = initf(); 1314 } 1315 1316 QString KLocalizedString::localizedFilePath(const QString &filePath) 1317 { 1318 KLocalizedStringPrivateStatics *s = staticsKLSP(); 1319 1320 // Check if l10n subdirectory is present, stop if not. 1321 QFileInfo fileInfo(filePath); 1322 QString locDirPath = fileInfo.path() + QLatin1Char('/') + QLatin1String("l10n"); 1323 QFileInfo locDirInfo(locDirPath); 1324 if (!locDirInfo.isDir()) { 1325 return filePath; 1326 } 1327 1328 // Go through possible localized paths by priority of languages, 1329 // return first that exists. 1330 QString fileName = fileInfo.fileName(); 1331 for (const QString &lang : std::as_const(s->languages)) { 1332 QString locFilePath = locDirPath + QLatin1Char('/') + lang + QLatin1Char('/') + fileName; 1333 QFileInfo locFileInfo(locFilePath); 1334 if (locFileInfo.isFile() && locFileInfo.isReadable()) { 1335 return locFilePath; 1336 } 1337 } 1338 1339 return filePath; 1340 } 1341 1342 QString KLocalizedString::removeAcceleratorMarker(const QString &label) 1343 { 1344 return ::removeAcceleratorMarker(label); 1345 } 1346 1347 void KLocalizedString::addDomainLocaleDir(const QByteArray &domain, const QString &path) 1348 { 1349 KCatalog::addDomainLocaleDir(domain, path); 1350 } 1351 1352 KLocalizedString ki18n(const char *text) 1353 { 1354 return KLocalizedString(nullptr, nullptr, text, nullptr, false); 1355 } 1356 1357 KLocalizedString ki18nc(const char *context, const char *text) 1358 { 1359 return KLocalizedString(nullptr, context, text, nullptr, false); 1360 } 1361 1362 KLocalizedString ki18np(const char *singular, const char *plural) 1363 { 1364 return KLocalizedString(nullptr, nullptr, singular, plural, false); 1365 } 1366 1367 KLocalizedString ki18ncp(const char *context, const char *singular, const char *plural) 1368 { 1369 return KLocalizedString(nullptr, context, singular, plural, false); 1370 } 1371 1372 KLocalizedString ki18nd(const char *domain, const char *text) 1373 { 1374 return KLocalizedString(domain, nullptr, text, nullptr, false); 1375 } 1376 1377 KLocalizedString ki18ndc(const char *domain, const char *context, const char *text) 1378 { 1379 return KLocalizedString(domain, context, text, nullptr, false); 1380 } 1381 1382 KLocalizedString ki18ndp(const char *domain, const char *singular, const char *plural) 1383 { 1384 return KLocalizedString(domain, nullptr, singular, plural, false); 1385 } 1386 1387 KLocalizedString ki18ndcp(const char *domain, const char *context, const char *singular, const char *plural) 1388 { 1389 return KLocalizedString(domain, context, singular, plural, false); 1390 } 1391 1392 KLocalizedString kxi18n(const char *text) 1393 { 1394 return KLocalizedString(nullptr, nullptr, text, nullptr, true); 1395 } 1396 1397 KLocalizedString kxi18nc(const char *context, const char *text) 1398 { 1399 return KLocalizedString(nullptr, context, text, nullptr, true); 1400 } 1401 1402 KLocalizedString kxi18np(const char *singular, const char *plural) 1403 { 1404 return KLocalizedString(nullptr, nullptr, singular, plural, true); 1405 } 1406 1407 KLocalizedString kxi18ncp(const char *context, const char *singular, const char *plural) 1408 { 1409 return KLocalizedString(nullptr, context, singular, plural, true); 1410 } 1411 1412 KLocalizedString kxi18nd(const char *domain, const char *text) 1413 { 1414 return KLocalizedString(domain, nullptr, text, nullptr, true); 1415 } 1416 1417 KLocalizedString kxi18ndc(const char *domain, const char *context, const char *text) 1418 { 1419 return KLocalizedString(domain, context, text, nullptr, true); 1420 } 1421 1422 KLocalizedString kxi18ndp(const char *domain, const char *singular, const char *plural) 1423 { 1424 return KLocalizedString(domain, nullptr, singular, plural, true); 1425 } 1426 1427 KLocalizedString kxi18ndcp(const char *domain, const char *context, const char *singular, const char *plural) 1428 { 1429 return KLocalizedString(domain, context, singular, plural, true); 1430 }