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 }