File indexing completed on 2024-04-28 15:25:18

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