1/* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 2006, 2013 Chusslove Illich <caslav.ilic@gmx.net>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7// We don't want i18n to be expanded to i18nd here
8#undef TRANSLATION_DOMAIN
9
10#include <cstdlib>
11
12#include <QByteArray>
13#include <QCoreApplication>
14#include <QDir>
15#include <QFile>
16#include <QFileInfo>
17#include <QHash>
18#include <QLibrary>
19#include <QList>
20#include <QMutexLocker>
21#include <QPluginLoader>
22#include <QRecursiveMutex>
23#include <QStandardPaths>
24#include <QStringList>
25
26#include <common_helpers_p.h>
27#include <kcatalog_p.h>
28#include <klocalizedstring.h>
29#include <ktranscript_p.h>
30#include <kuitsetup_p.h>
31
32#include "ki18n_logging.h"
33
34// Truncate string, for output of long messages.
35static QString shortenMessage(const QString &str)
36{
37 const int maxlen = 20;
38 if (str.length() <= maxlen) {
39 return str;
40 } else {
41 return QStringView(str).left(n: maxlen) + QLatin1String("...");
42 }
43}
44
45static void splitLocale(const QString &aLocale, QString &language, QString &country, QString &modifier, QString &charset)
46{
47 QString locale = aLocale;
48
49 language.clear();
50 country.clear();
51 modifier.clear();
52 charset.clear();
53
54 // In case there are several concatenated locale specifications,
55 // truncate all but first.
56 int f = locale.indexOf(c: QLatin1Char(':'));
57 if (f >= 0) {
58 locale.truncate(pos: f);
59 }
60
61 // now decompose into [language[_territory][.codeset][@modifier]]
62 f = locale.indexOf(c: QLatin1Char('@'));
63 if (f >= 0) {
64 modifier = locale.mid(position: f + 1);
65 locale.truncate(pos: f);
66 }
67
68 f = locale.indexOf(c: QLatin1Char('.'));
69 if (f >= 0) {
70 charset = locale.mid(position: f + 1);
71 locale.truncate(pos: f);
72 }
73
74 f = locale.indexOf(c: QLatin1Char('_'));
75 if (f >= 0) {
76 country = locale.mid(position: f + 1);
77 locale.truncate(pos: f);
78 }
79
80 language = locale;
81}
82
83static void appendLocaleString(QStringList &languages, const QString &value)
84{
85 // Process the value to create possible combinations.
86 QString language;
87 QString country;
88 QString modifier;
89 QString charset;
90 splitLocale(aLocale: value, language, country, modifier, charset);
91
92 if (language.isEmpty()) {
93 return;
94 }
95
96 if (!country.isEmpty() && !modifier.isEmpty()) {
97 languages += language + QLatin1Char('_') + country + QLatin1Char('@') + modifier;
98 }
99 // NOTE: Priority is unclear in case both the country and
100 // the modifier are present. Should really language@modifier be of
101 // higher priority than language_country?
102 // In at least one case (Serbian language), it is better this way.
103 if (!modifier.isEmpty()) {
104 languages += language + QLatin1Char('@') + modifier;
105 }
106 if (!country.isEmpty()) {
107 languages += language + QLatin1Char('_') + country;
108 }
109 languages += language;
110}
111
112static void appendLanguagesFromVariable(QStringList &languages, const char *envar, bool isList = false)
113{
114 QByteArray qenvar(qgetenv(varName: envar));
115 if (!qenvar.isEmpty()) {
116 QString value = QFile::decodeName(localFileName: qenvar);
117 if (isList) {
118 const auto listLanguages = value.split(sep: QLatin1Char(':'), behavior: Qt::SkipEmptyParts);
119 for (const QString &v : listLanguages) {
120 appendLocaleString(languages, value: v);
121 }
122 } else {
123 appendLocaleString(languages, value);
124 }
125 }
126}
127
128#if !defined(Q_OS_UNIX) || defined(Q_OS_ANDROID)
129static void appendLanguagesFromQLocale(QStringList &languages, const QLocale &locale)
130{
131 const QStringList uiLangs = locale.uiLanguages();
132 for (QString value : uiLangs) { // no const ref because of replace() below
133 appendLocaleString(languages, value.replace(QLatin1Char('-'), QLatin1Char('_')));
134 }
135}
136#endif
137
138// Extract the first country code from a list of language_COUNTRY strings.
139// Country code is converted to all lower case letters.
140static QString extractCountry(const QStringList &languages)
141{
142 QString country;
143 for (const QString &language : languages) {
144 int pos1 = language.indexOf(c: QLatin1Char('_'));
145 if (pos1 >= 0) {
146 ++pos1;
147 int pos2 = pos1;
148 while (pos2 < language.length() && language[pos2].isLetter()) {
149 ++pos2;
150 }
151 country = language.mid(position: pos1, n: pos2 - pos1);
152 break;
153 }
154 }
155 country = country.toLower();
156 return country;
157}
158
159typedef qulonglong pluraln;
160typedef qlonglong intn;
161typedef qulonglong uintn;
162typedef double realn;
163
164class KLocalizedStringPrivate
165{
166 friend class KLocalizedString;
167
168 QByteArray domain;
169 QStringList languages;
170 Kuit::VisualFormat format;
171 QByteArray context;
172 QByteArray text;
173 QByteArray plural;
174 QStringList arguments;
175 QList<QVariant> values;
176 QHash<int, KLocalizedString> klsArguments;
177 QHash<int, int> klsArgumentFieldWidths;
178 QHash<int, QChar> klsArgumentFillChars;
179 bool numberSet;
180 pluraln number;
181 int numberOrdinal;
182 QHash<QString, QString> dynamicContext;
183 bool markupAware;
184 bool relaxedSubs;
185
186 KLocalizedStringPrivate()
187 : format()
188 , numberSet(false)
189 , markupAware(false)
190 , relaxedSubs(false)
191 {
192 }
193
194 static void translateRaw(const QByteArray &domain,
195 const QStringList &languages,
196 const QByteArray &msgctxt,
197 const QByteArray &msgid,
198 const QByteArray &msgid_plural,
199 qulonglong n,
200 QString &language,
201 QString &translation);
202
203 QString toString(const QByteArray &domain, const QStringList &languages, Kuit::VisualFormat format, bool isArgument = false) const;
204 QString substituteSimple(const QString &translation, const QStringList &arguments, QChar plchar = QLatin1Char('%'), bool isPartial = false) const;
205 QString formatMarkup(const QByteArray &domain, const QString &language, const QString &context, const QString &text, Kuit::VisualFormat format) const;
206 QString substituteTranscript(const QString &scriptedTranslation,
207 const QString &language,
208 const QString &country,
209 const QString &ordinaryTranslation,
210 const QStringList &arguments,
211 const QList<QVariant> &values,
212 bool &fallback) const;
213 int resolveInterpolation(const QString &scriptedTranslation,
214 int pos,
215 const QString &language,
216 const QString &country,
217 const QString &ordinaryTranslation,
218 const QStringList &arguments,
219 const QList<QVariant> &values,
220 QString &result,
221 bool &fallback) const;
222 QVariant segmentToValue(const QString &segment) const;
223 QString postTranscript(const QString &pcall,
224 const QString &language,
225 const QString &country,
226 const QString &finalTranslation,
227 const QStringList &arguments,
228 const QList<QVariant> &values) const;
229
230 static const KCatalog &getCatalog(const QByteArray &domain, const QString &language);
231 static void locateScriptingModule(const QByteArray &domain, const QString &language);
232
233 static void loadTranscript();
234
235 void checkNumber(pluraln a)
236 {
237 if (!plural.isEmpty() && !numberSet) {
238 number = a;
239 numberSet = true;
240 numberOrdinal = arguments.size();
241 }
242 }
243};
244
245typedef QHash<QString, KCatalog *> KCatalogPtrHash;
246
247class KLocalizedStringPrivateStatics
248{
249public:
250 QHash<QByteArray, KCatalogPtrHash> catalogs;
251 QStringList languages;
252
253 QByteArray ourDomain;
254 QByteArray applicationDomain;
255 const QString codeLanguage;
256 QStringList localeLanguages;
257
258 const QString theFence;
259 const QString startInterp;
260 const QString endInterp;
261 const QChar scriptPlchar;
262 const QChar scriptVachar;
263
264 const QString scriptDir;
265 QHash<QString, QList<QByteArray>> scriptModules;
266 QList<QStringList> scriptModulesToLoad;
267
268 bool loadTranscriptCalled;
269 KTranscript *ktrs;
270
271 QHash<QString, KuitFormatter *> formatters;
272
273 QList<QByteArray> qtDomains;
274 QList<int> qtDomainInsertCount;
275
276 QRecursiveMutex klspMutex;
277
278 KLocalizedStringPrivateStatics();
279 ~KLocalizedStringPrivateStatics();
280
281 void initializeLocaleLanguages();
282};
283
284KLocalizedStringPrivateStatics::KLocalizedStringPrivateStatics()
285 : catalogs()
286 , languages()
287
288 , ourDomain(QByteArrayLiteral("ki18n6"))
289 , applicationDomain()
290 , codeLanguage(QStringLiteral("en_US"))
291 , localeLanguages()
292
293 , theFence(QStringLiteral("|/|"))
294 , startInterp(QStringLiteral("$["))
295 , endInterp(QStringLiteral("]"))
296 , scriptPlchar(QLatin1Char('%'))
297 , scriptVachar(QLatin1Char('^'))
298
299 , scriptDir(QStringLiteral("LC_SCRIPTS"))
300 , scriptModules()
301 , scriptModulesToLoad()
302
303 , loadTranscriptCalled(false)
304 , ktrs(nullptr)
305
306 , formatters()
307
308 , qtDomains()
309 , qtDomainInsertCount()
310{
311 initializeLocaleLanguages();
312 languages = localeLanguages;
313}
314
315KLocalizedStringPrivateStatics::~KLocalizedStringPrivateStatics()
316{
317 for (const KCatalogPtrHash &languageCatalogs : std::as_const(t&: catalogs)) {
318 qDeleteAll(c: languageCatalogs);
319 }
320 // ktrs is handled by QLibrary.
321 // delete ktrs;
322 qDeleteAll(c: formatters);
323}
324
325Q_GLOBAL_STATIC(KLocalizedStringPrivateStatics, staticsKLSP)
326
327void KLocalizedStringPrivateStatics::initializeLocaleLanguages()
328{
329 QMutexLocker lock(&klspMutex);
330
331 // Collect languages by same order of priority as for gettext(3).
332 // LANGUAGE contains list of language codes, not locale string.
333 appendLanguagesFromVariable(languages&: localeLanguages, envar: "LANGUAGE", isList: true);
334 appendLanguagesFromVariable(languages&: localeLanguages, envar: "LC_ALL");
335 appendLanguagesFromVariable(languages&: localeLanguages, envar: "LC_MESSAGES");
336 appendLanguagesFromVariable(languages&: localeLanguages, envar: "LANG");
337#if !defined(Q_OS_UNIX) || defined(Q_OS_ANDROID)
338 // For non UNIX platforms the environment variables might not
339 // suffice so we add system locale UI languages, too.
340 appendLanguagesFromQLocale(localeLanguages, QLocale::system());
341#endif
342}
343
344KLocalizedString::KLocalizedString()
345 : d(new KLocalizedStringPrivate)
346{
347}
348
349KLocalizedString::KLocalizedString(const char *domain, const char *context, const char *text, const char *plural, bool markupAware)
350 : d(new KLocalizedStringPrivate)
351{
352 d->domain = domain;
353 d->languages.clear();
354 d->format = Kuit::UndefinedFormat;
355 d->context = context;
356 d->text = text;
357 d->plural = plural;
358 d->numberSet = false;
359 d->number = 0;
360 d->numberOrdinal = 0;
361 d->markupAware = markupAware;
362 d->relaxedSubs = false;
363}
364
365KLocalizedString::KLocalizedString(const KLocalizedString &rhs)
366 : d(new KLocalizedStringPrivate(*rhs.d))
367{
368}
369
370KLocalizedString &KLocalizedString::operator=(const KLocalizedString &rhs)
371{
372 if (&rhs != this) {
373 *d = *rhs.d;
374 }
375 return *this;
376}
377
378KLocalizedString::~KLocalizedString() = default;
379
380bool KLocalizedString::isEmpty() const
381{
382 return d->text.isEmpty();
383}
384
385void KLocalizedStringPrivate::translateRaw(const QByteArray &domain,
386 const QStringList &languages,
387 const QByteArray &msgctxt,
388 const QByteArray &msgid,
389 const QByteArray &msgid_plural,
390 qulonglong n,
391 QString &language,
392 QString &msgstr)
393{
394 KLocalizedStringPrivateStatics *s = staticsKLSP();
395
396 // Empty msgid would result in returning the catalog header,
397 // which is never intended, so warn and return empty translation.
398 if (msgid.isNull() || msgid.isEmpty()) {
399 qCWarning(KI18N) << "KLocalizedString: "
400 "Trying to look up translation of \"\", fix the code.";
401 language.clear();
402 msgstr.clear();
403 return;
404 }
405 // Gettext semantics allows empty context, but it is pointless, so warn.
406 if (!msgctxt.isNull() && msgctxt.isEmpty()) {
407 qCWarning(KI18N) << "KLocalizedString: "
408 "Using \"\" as context, fix the code.";
409 }
410 // Gettext semantics allows empty plural, but it is pointless, so warn.
411 if (!msgid_plural.isNull() && msgid_plural.isEmpty()) {
412 qCWarning(KI18N) << "KLocalizedString: "
413 "Using \"\" as plural text, fix the code.";
414 }
415
416 // Set translation to text in code language, in case no translation found.
417 msgstr = msgid_plural.isNull() || n == 1 ? QString::fromUtf8(ba: msgid) : QString::fromUtf8(ba: msgid_plural);
418 language = s->codeLanguage;
419
420 if (domain.isEmpty()) {
421 qCWarning(KI18N) << "KLocalizedString: Domain is not set for this string, translation will not work. Please see https://api.kde.org/frameworks/ki18n/html/prg_guide.html msgid:" << msgid << "msgid_plural:" << msgid_plural
422 << "msgctxt:" << msgctxt;
423 return;
424 }
425
426 // Languages are ordered from highest to lowest priority.
427 for (const QString &testLanguage : languages) {
428 // If code language reached, no catalog lookup is needed.
429 if (testLanguage == s->codeLanguage) {
430 return;
431 }
432 const KCatalog &catalog = getCatalog(domain, language: testLanguage);
433 QString testMsgstr;
434 if (!msgctxt.isNull() && !msgid_plural.isNull()) {
435 testMsgstr = catalog.translate(msgctxt, msgid, msgid_plural, n);
436 } else if (!msgid_plural.isNull()) {
437 testMsgstr = catalog.translate(msgid, msgid_plural, n);
438 } else if (!msgctxt.isNull()) {
439 testMsgstr = catalog.translate(msgctxt, msgid);
440 } else {
441 testMsgstr = catalog.translate(msgid);
442 }
443 if (!testMsgstr.isEmpty()) {
444 // Translation found.
445 language = testLanguage;
446 msgstr = testMsgstr;
447 return;
448 }
449 }
450}
451
452QString KLocalizedString::toString() const
453{
454 return d->toString(domain: d->domain, languages: d->languages, format: d->format);
455}
456
457QString KLocalizedString::toString(const char *domain) const
458{
459 return d->toString(domain, languages: d->languages, format: d->format);
460}
461
462QString KLocalizedString::toString(const QStringList &languages) const
463{
464 return d->toString(domain: d->domain, languages, format: d->format);
465}
466
467QString KLocalizedString::toString(Kuit::VisualFormat format) const
468{
469 return d->toString(domain: d->domain, languages: d->languages, format);
470}
471
472QString KLocalizedStringPrivate::toString(const QByteArray &domain, const QStringList &languages, Kuit::VisualFormat format, bool isArgument) const
473{
474 KLocalizedStringPrivateStatics *s = staticsKLSP();
475
476 QMutexLocker lock(&s->klspMutex);
477
478 // Assure the message has been supplied.
479 if (text.isEmpty()) {
480 qCWarning(KI18N) << "Trying to convert empty KLocalizedString to QString.";
481#ifndef NDEBUG
482 return QStringLiteral("(I18N_EMPTY_MESSAGE)");
483#else
484 return QString();
485#endif
486 }
487
488 // Check whether plural argument has been supplied, if message has plural.
489 if (!plural.isEmpty() && !numberSet) {
490 qCWarning(KI18N) << "Plural argument to message" << shortenMessage(str: QString::fromUtf8(ba: text)) << "not supplied before conversion.";
491 }
492
493 // Resolve inputs.
494 QByteArray resolvedDomain = domain;
495 if (resolvedDomain.isEmpty()) {
496 resolvedDomain = s->applicationDomain;
497 }
498 QStringList resolvedLanguages = languages;
499 if (resolvedLanguages.isEmpty()) {
500 resolvedLanguages = s->languages;
501 }
502 Kuit::VisualFormat resolvedFormat = format;
503
504 // Get raw translation.
505 QString language;
506 QString rawTranslation;
507 translateRaw(domain: resolvedDomain, languages: resolvedLanguages, msgctxt: context, msgid: text, msgid_plural: plural, n: number, language, msgstr&: rawTranslation);
508 QString country = extractCountry(languages: resolvedLanguages);
509
510 // Set ordinary translation and possibly scripted translation.
511 QString translation;
512 QString scriptedTranslation;
513 int fencePos = rawTranslation.indexOf(s: s->theFence);
514 if (fencePos > 0) {
515 // Script fence has been found, strip the scripted from the
516 // ordinary translation.
517 translation = rawTranslation.left(n: fencePos);
518
519 // Scripted translation.
520 scriptedTranslation = rawTranslation.mid(position: fencePos + s->theFence.length());
521
522 // Try to initialize Transcript if not initialized and script not empty.
523 // FIXME: And also if Transcript not disabled: where to configure this?
524 if (!s->loadTranscriptCalled && !scriptedTranslation.isEmpty()) {
525 loadTranscript();
526
527 // Definitions from this library's scripting module
528 // must be available to all other modules.
529 // So force creation of this library's catalog here,
530 // to make sure the scripting module is loaded.
531 getCatalog(domain: s->ourDomain, language);
532 }
533 } else if (fencePos < 0) {
534 // No script fence, use translation as is.
535 translation = rawTranslation;
536 } else { // fencePos == 0
537 // The msgstr starts with the script fence, no ordinary translation.
538 // This is not allowed, consider message not translated.
539 qCWarning(KI18N) << "Scripted message" << shortenMessage(str: translation) << "without ordinary translation, discarded.";
540 translation = plural.isEmpty() || number == 1 ? QString::fromUtf8(ba: text) : QString::fromUtf8(ba: plural);
541 }
542
543 // Resolve substituted KLocalizedString arguments.
544 QStringList resolvedArguments;
545 QList<QVariant> resolvedValues;
546 for (int i = 0; i < arguments.size(); i++) {
547 auto lsIt = klsArguments.constFind(key: i);
548 if (lsIt != klsArguments.constEnd()) {
549 const KLocalizedString &kls = *lsIt;
550 int fieldWidth = klsArgumentFieldWidths.value(key: i);
551 QChar fillChar = klsArgumentFillChars.value(key: i);
552 // Override argument's languages and format, but not domain.
553 bool isArgumentSub = true;
554 QString resdArg = kls.d->toString(domain: kls.d->domain, languages: resolvedLanguages, format: resolvedFormat, isArgument: isArgumentSub);
555 resolvedValues.append(t: resdArg);
556 if (markupAware && !kls.d->markupAware) {
557 resdArg = Kuit::escape(text: resdArg);
558 }
559 resdArg = QStringLiteral("%1").arg(a: resdArg, fieldWidth, fillChar);
560 resolvedArguments.append(t: resdArg);
561 } else {
562 QString resdArg = arguments[i];
563 if (markupAware) {
564 resdArg = Kuit::escape(text: resdArg);
565 }
566 resolvedArguments.append(t: resdArg);
567 resolvedValues.append(t: values[i]);
568 }
569 }
570
571 // Substitute placeholders in ordinary translation.
572 QString finalTranslation = substituteSimple(translation, arguments: resolvedArguments);
573 if (markupAware && !isArgument) {
574 // Resolve markup in ordinary translation.
575 finalTranslation = formatMarkup(domain: resolvedDomain, language, context: QString::fromUtf8(ba: context), text: finalTranslation, format: resolvedFormat);
576 }
577
578 // If there is also a scripted translation.
579 if (!scriptedTranslation.isEmpty()) {
580 // Evaluate scripted translation.
581 bool fallback = false;
582 scriptedTranslation = substituteTranscript(scriptedTranslation, language, country, ordinaryTranslation: finalTranslation, arguments: resolvedArguments, values: resolvedValues, fallback);
583
584 // If any translation produced and no fallback requested.
585 if (!scriptedTranslation.isEmpty() && !fallback) {
586 if (markupAware && !isArgument) {
587 // Resolve markup in scripted translation.
588 scriptedTranslation = formatMarkup(domain: resolvedDomain, language, context: QString::fromUtf8(ba: context), text: scriptedTranslation, format: resolvedFormat);
589 }
590 finalTranslation = scriptedTranslation;
591 }
592 }
593
594 // Execute any scripted post calls; they cannot modify the final result,
595 // but are used to set states.
596 if (s->ktrs != nullptr) {
597 const QStringList pcalls = s->ktrs->postCalls(lang: language);
598 for (const QString &pcall : pcalls) {
599 postTranscript(pcall, language, country, finalTranslation, arguments: resolvedArguments, values: resolvedValues);
600 }
601 }
602
603 return finalTranslation;
604}
605
606QString KLocalizedStringPrivate::substituteSimple(const QString &translation, const QStringList &arguments, QChar plchar, bool isPartial) const
607{
608#ifdef NDEBUG
609 Q_UNUSED(isPartial);
610#endif
611
612 QStringList tsegs; // text segments per placeholder occurrence
613 QList<int> plords; // ordinal numbers per placeholder occurrence
614#ifndef NDEBUG
615 QList<int> ords; // indicates which placeholders are present
616#endif
617 int slen = translation.length();
618 int spos = 0;
619 int tpos = translation.indexOf(c: plchar);
620 while (tpos >= 0) {
621 int ctpos = tpos;
622
623 ++tpos;
624 if (tpos == slen) {
625 break;
626 }
627
628 if (translation[tpos].digitValue() > 0) {
629 // NOTE: %0 is not considered a placeholder.
630 // Get the placeholder ordinal.
631 int plord = 0;
632 while (tpos < slen && translation[tpos].digitValue() >= 0) {
633 plord = 10 * plord + translation[tpos].digitValue();
634 ++tpos;
635 }
636 --plord; // ordinals are zero based
637
638#ifndef NDEBUG
639 // Perhaps enlarge storage for indicators.
640 // Note that QList<int> will initialize new elements to 0,
641 // as they are supposed to be.
642 if (plord >= ords.size()) {
643 ords.resize(size: plord + 1);
644 }
645
646 // Indicate that placeholder with computed ordinal is present.
647 ords[plord] = 1;
648#endif
649
650 // Store text segment prior to placeholder and placeholder number.
651 tsegs.append(t: translation.mid(position: spos, n: ctpos - spos));
652 plords.append(t: plord);
653
654 // Position of next text segment.
655 spos = tpos;
656 }
657
658 tpos = translation.indexOf(c: plchar, from: tpos);
659 }
660 // Store last text segment.
661 tsegs.append(t: translation.mid(position: spos));
662
663#ifndef NDEBUG
664 // Perhaps enlarge storage for plural-number ordinal.
665 if (!plural.isEmpty() && numberOrdinal >= ords.size()) {
666 ords.resize(size: numberOrdinal + 1);
667 }
668
669 // Message might have plural but without plural placeholder, which is an
670 // allowed state. To ease further logic, indicate that plural placeholder
671 // is present anyway if message has plural.
672 if (!plural.isEmpty()) {
673 ords[numberOrdinal] = 1;
674 }
675#endif
676
677 // Assemble the final string from text segments and arguments.
678 QString finalTranslation;
679 for (int i = 0; i < plords.size(); i++) {
680 finalTranslation.append(s: tsegs.at(i));
681 if (plords.at(i) >= arguments.size()) { // too little arguments
682 // put back the placeholder
683 finalTranslation.append(s: QLatin1Char('%') + QString::number(plords.at(i) + 1));
684#ifndef NDEBUG
685 if (!isPartial) {
686 // spoof the message
687 finalTranslation.append(QStringLiteral("(I18N_ARGUMENT_MISSING)"));
688 }
689#endif
690 } else { // just fine
691 finalTranslation.append(s: arguments.at(i: plords.at(i)));
692 }
693 }
694 finalTranslation.append(s: tsegs.last());
695
696#ifndef NDEBUG
697 if (!isPartial && !relaxedSubs) {
698 // Check that there are no gaps in numbering sequence of placeholders.
699 bool gaps = false;
700 for (int i = 0; i < ords.size(); i++) {
701 if (!ords.at(i)) {
702 gaps = true;
703 qCWarning(KI18N).nospace() << "Placeholder %" << QString::number(i + 1) << " skipped in message " << shortenMessage(str: translation);
704 }
705 }
706 // If no gaps, check for mismatch between the number of
707 // unique placeholders and actually supplied arguments.
708 if (!gaps && ords.size() != arguments.size()) {
709 qCWarning(KI18N) << arguments.size() << "instead of" << ords.size() << "arguments to message" << shortenMessage(str: translation)
710 << "supplied before conversion";
711 }
712
713 // Some spoofs.
714 if (gaps) {
715 finalTranslation.append(QStringLiteral("(I18N_GAPS_IN_PLACEHOLDER_SEQUENCE)"));
716 }
717 if (ords.size() < arguments.size()) {
718 finalTranslation.append(QStringLiteral("(I18N_EXCESS_ARGUMENTS_SUPPLIED)"));
719 }
720 }
721 if (!isPartial) {
722 if (!plural.isEmpty() && !numberSet) {
723 finalTranslation.append(QStringLiteral("(I18N_PLURAL_ARGUMENT_MISSING)"));
724 }
725 }
726#endif
727
728 return finalTranslation;
729}
730
731QString KLocalizedStringPrivate::formatMarkup(const QByteArray &domain,
732 const QString &language,
733 const QString &context,
734 const QString &text,
735 Kuit::VisualFormat format) const
736{
737 KLocalizedStringPrivateStatics *s = staticsKLSP();
738
739 QHash<QString, KuitFormatter *>::iterator formatter = s->formatters.find(key: language);
740 if (formatter == s->formatters.end()) {
741 formatter = s->formatters.insert(key: language, value: new KuitFormatter(language));
742 }
743 return (*formatter)->format(domain, context, text, format);
744}
745
746QString KLocalizedStringPrivate::substituteTranscript(const QString &scriptedTranslation,
747 const QString &language,
748 const QString &country,
749 const QString &ordinaryTranslation,
750 const QStringList &arguments,
751 const QList<QVariant> &values,
752 bool &fallback) const
753{
754 KLocalizedStringPrivateStatics *s = staticsKLSP();
755
756 if (s->ktrs == nullptr) {
757 // Scripting engine not available.
758 return QString();
759 }
760
761 // Iterate by interpolations.
762 QString finalTranslation;
763 fallback = false;
764 int ppos = 0;
765 int tpos = scriptedTranslation.indexOf(s: s->startInterp);
766 while (tpos >= 0) {
767 // Resolve substitutions in preceding text.
768 QString ptext = substituteSimple(translation: scriptedTranslation.mid(position: ppos, n: tpos - ppos), arguments, plchar: s->scriptPlchar, isPartial: true);
769 finalTranslation.append(s: ptext);
770
771 // Resolve interpolation.
772 QString result;
773 bool fallbackLocal;
774 tpos = resolveInterpolation(scriptedTranslation, pos: tpos, language, country, ordinaryTranslation, arguments, values, result, fallback&: fallbackLocal);
775
776 // If there was a problem in parsing the interpolation, cannot proceed
777 // (debug info already reported while parsing).
778 if (tpos < 0) {
779 return QString();
780 }
781 // If fallback has been explicitly requested, indicate global fallback
782 // but proceed with evaluations (other interpolations may set states).
783 if (fallbackLocal) {
784 fallback = true;
785 }
786
787 // Add evaluated interpolation to the text.
788 finalTranslation.append(s: result);
789
790 // On to next interpolation.
791 ppos = tpos;
792 tpos = scriptedTranslation.indexOf(s: s->startInterp, from: tpos);
793 }
794 // Last text segment.
795 finalTranslation.append(s: substituteSimple(translation: scriptedTranslation.mid(position: ppos), arguments, plchar: s->scriptPlchar, isPartial: true));
796
797 // Return empty string if fallback was requested.
798 return fallback ? QString() : finalTranslation;
799}
800
801int KLocalizedStringPrivate::resolveInterpolation(const QString &scriptedTranslation,
802 int pos,
803 const QString &language,
804 const QString &country,
805 const QString &ordinaryTranslation,
806 const QStringList &arguments,
807 const QList<QVariant> &values,
808 QString &result,
809 bool &fallback) const
810{
811 // pos is the position of opening character sequence.
812 // Returns the position of first character after closing sequence,
813 // or -1 in case of parsing error.
814 // result is set to result of Transcript evaluation.
815 // fallback is set to true if Transcript evaluation requested so.
816
817 KLocalizedStringPrivateStatics *s = staticsKLSP();
818
819 result.clear();
820 fallback = false;
821
822 // Split interpolation into arguments.
823 QList<QVariant> iargs;
824 const int slen = scriptedTranslation.length();
825 const int islen = s->startInterp.length();
826 const int ielen = s->endInterp.length();
827 int tpos = pos + s->startInterp.length();
828 while (1) {
829 // Skip whitespace.
830 while (tpos < slen && scriptedTranslation[tpos].isSpace()) {
831 ++tpos;
832 }
833 if (tpos == slen) {
834 qCWarning(KI18N) << "Unclosed interpolation" << scriptedTranslation.mid(position: pos, n: tpos - pos) << "in message" << shortenMessage(str: scriptedTranslation);
835 return -1;
836 }
837 if (QStringView(scriptedTranslation).mid(pos: tpos, n: ielen) == s->endInterp) {
838 break; // no more arguments
839 }
840
841 // Parse argument: may be concatenated from free and quoted text,
842 // and sub-interpolations.
843 // Free and quoted segments may contain placeholders, substitute them;
844 // recurse into sub-interpolations.
845 // Free segments may be value references, parse and record for
846 // consideration at the end.
847 // Mind backslash escapes throughout.
848 QStringList segs;
849 QVariant vref;
850 while (!scriptedTranslation[tpos].isSpace() && scriptedTranslation.mid(position: tpos, n: ielen) != s->endInterp) {
851 if (scriptedTranslation[tpos] == QLatin1Char('\'')) { // quoted segment
852 QString seg;
853 ++tpos; // skip opening quote
854 // Find closing quote.
855 while (tpos < slen && scriptedTranslation[tpos] != QLatin1Char('\'')) {
856 if (scriptedTranslation[tpos] == QLatin1Char('\\')) {
857 ++tpos; // escape next character
858 }
859 seg.append(c: scriptedTranslation[tpos]);
860 ++tpos;
861 }
862 if (tpos == slen) {
863 qCWarning(KI18N) << "Unclosed quote in interpolation" << scriptedTranslation.mid(position: pos, n: tpos - pos) << "in message"
864 << shortenMessage(str: scriptedTranslation);
865 return -1;
866 }
867
868 // Append to list of segments, resolving placeholders.
869 segs.append(t: substituteSimple(translation: seg, arguments, plchar: s->scriptPlchar, isPartial: true));
870
871 ++tpos; // skip closing quote
872 } else if (scriptedTranslation.mid(position: tpos, n: islen) == s->startInterp) { // sub-interpolation
873 QString resultLocal;
874 bool fallbackLocal;
875 tpos = resolveInterpolation(scriptedTranslation, pos: tpos, language, country, ordinaryTranslation, arguments, values, result&: resultLocal, fallback&: fallbackLocal);
876 if (tpos < 0) { // unrecoverable problem in sub-interpolation
877 // Error reported in the subcall.
878 return tpos;
879 }
880 if (fallbackLocal) { // sub-interpolation requested fallback
881 fallback = true;
882 }
883 segs.append(t: resultLocal);
884 } else { // free segment
885 QString seg;
886 // Find whitespace, quote, opening or closing sequence.
887 while (tpos < slen && !scriptedTranslation[tpos].isSpace() //
888 && scriptedTranslation[tpos] != QLatin1Char('\'') //
889 && scriptedTranslation.mid(position: tpos, n: islen) != s->startInterp //
890 && scriptedTranslation.mid(position: tpos, n: ielen) != s->endInterp) {
891 if (scriptedTranslation[tpos] == QLatin1Char('\\')) {
892 ++tpos; // escape next character
893 }
894 seg.append(c: scriptedTranslation[tpos]);
895 ++tpos;
896 }
897 if (tpos == slen) {
898 qCWarning(KI18N) << "Non-terminated interpolation" << scriptedTranslation.mid(position: pos, n: tpos - pos) << "in message"
899 << shortenMessage(str: scriptedTranslation);
900 return -1;
901 }
902
903 // The free segment may look like a value reference;
904 // in that case, record which value it would reference,
905 // and add verbatim to the segment list.
906 // Otherwise, do a normal substitution on the segment.
907 vref = segmentToValue(segment: seg);
908 if (vref.isValid()) {
909 segs.append(t: seg);
910 } else {
911 segs.append(t: substituteSimple(translation: seg, arguments, plchar: s->scriptPlchar, isPartial: true));
912 }
913 }
914 }
915
916 // Append this argument to rest of the arguments.
917 // If the there was a single text segment and it was a proper value
918 // reference, add it instead of the joined segments.
919 // Otherwise, add the joined segments.
920 if (segs.size() == 1 && vref.isValid()) {
921 iargs.append(t: vref);
922 } else {
923 iargs.append(t: segs.join(sep: QString()));
924 }
925 }
926 tpos += ielen; // skip to first character after closing sequence
927
928 // NOTE: Why not substitute placeholders (via substituteSimple) in one
929 // global pass, then handle interpolations in second pass? Because then
930 // there is the danger of substituted text or sub-interpolations producing
931 // quotes and escapes themselves, which would mess up the parsing.
932
933 // Evaluate interpolation.
934 QString msgctxt = QString::fromUtf8(ba: context);
935 QString msgid = QString::fromUtf8(ba: text);
936 QString scriptError;
937 bool fallbackLocal;
938 result = s->ktrs->eval(argv: iargs,
939 lang: language,
940 ctry: country,
941 msgctxt,
942 dynctxt: dynamicContext,
943 msgid,
944 subs: arguments,
945 vals: values,
946 ftrans: ordinaryTranslation,
947 mods&: s->scriptModulesToLoad,
948 error&: scriptError,
949 fallback&: fallbackLocal);
950 // s->scriptModulesToLoad will be cleared during the call.
951
952 if (fallbackLocal) { // evaluation requested fallback
953 fallback = true;
954 }
955 if (!scriptError.isEmpty()) { // problem with evaluation
956 fallback = true; // also signal fallback
957 if (!scriptError.isEmpty()) {
958 qCWarning(KI18N) << "Interpolation" << scriptedTranslation.mid(position: pos, n: tpos - pos) << "in" << shortenMessage(str: scriptedTranslation)
959 << "failed:" << scriptError;
960 }
961 }
962
963 return tpos;
964}
965
966QVariant KLocalizedStringPrivate::segmentToValue(const QString &segment) const
967{
968 KLocalizedStringPrivateStatics *s = staticsKLSP();
969
970 // Return invalid variant if segment is either not a proper
971 // value reference, or the reference is out of bounds.
972
973 // Value reference must start with a special character.
974 if (!segment.startsWith(c: s->scriptVachar)) {
975 return QVariant();
976 }
977
978 // Reference number must start with 1-9.
979 // (If numstr is empty, toInt() will return 0.)
980 QString numstr = segment.mid(position: 1);
981 int numstrAsInt = QStringView(numstr).left(n: 1).toInt();
982 if (numstrAsInt < 1) {
983 return QVariant();
984 }
985
986 // Number must be valid and in bounds.
987 bool ok;
988 int index = numstr.toInt(ok: &ok) - 1;
989 if (!ok || index >= values.size()) {
990 return QVariant();
991 }
992
993 // Passed all hoops.
994 return values.at(i: index);
995}
996
997QString KLocalizedStringPrivate::postTranscript(const QString &pcall,
998 const QString &language,
999 const QString &country,
1000 const QString &finalTranslation,
1001 const QStringList &arguments,
1002 const QList<QVariant> &values) const
1003{
1004 KLocalizedStringPrivateStatics *s = staticsKLSP();
1005
1006 if (s->ktrs == nullptr) {
1007 // Scripting engine not available.
1008 // (Though this cannot happen, we wouldn't be here then.)
1009 return QString();
1010 }
1011
1012 // Resolve the post call.
1013 QList<QVariant> iargs;
1014 iargs.append(t: pcall);
1015 QString msgctxt = QString::fromUtf8(ba: context);
1016 QString msgid = QString::fromUtf8(ba: text);
1017 QString scriptError;
1018 bool fallback;
1019 s->ktrs->eval(argv: iargs, lang: language, ctry: country, msgctxt, dynctxt: dynamicContext, msgid, subs: arguments, vals: values, ftrans: finalTranslation, mods&: s->scriptModulesToLoad, error&: scriptError, fallback);
1020 // s->scriptModulesToLoad will be cleared during the call.
1021
1022 // If the evaluation went wrong.
1023 if (!scriptError.isEmpty()) {
1024 qCWarning(KI18N) << "Post call" << pcall << "for message" << shortenMessage(str: msgid) << "failed:" << scriptError;
1025 return QString();
1026 }
1027
1028 return finalTranslation;
1029}
1030
1031KLocalizedString KLocalizedString::withLanguages(const QStringList &languages) const
1032{
1033 KLocalizedString kls(*this);
1034 kls.d->languages = languages;
1035 return kls;
1036}
1037
1038KLocalizedString KLocalizedString::withDomain(const char *domain) const
1039{
1040 KLocalizedString kls(*this);
1041 kls.d->domain = domain;
1042 return kls;
1043}
1044
1045KLocalizedString KLocalizedString::withFormat(Kuit::VisualFormat format) const
1046{
1047 KLocalizedString kls(*this);
1048 kls.d->format = format;
1049 return kls;
1050}
1051
1052KLocalizedString KLocalizedString::subs(int a, int fieldWidth, int base, QChar fillChar) const
1053{
1054 KLocalizedString kls(*this);
1055 kls.d->checkNumber(a: std::abs(x: a));
1056 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar));
1057 kls.d->values.append(t: static_cast<intn>(a));
1058 return kls;
1059}
1060
1061KLocalizedString KLocalizedString::subs(uint a, int fieldWidth, int base, QChar fillChar) const
1062{
1063 KLocalizedString kls(*this);
1064 kls.d->checkNumber(a);
1065 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar));
1066 kls.d->values.append(t: static_cast<uintn>(a));
1067 return kls;
1068}
1069
1070KLocalizedString KLocalizedString::subs(long a, int fieldWidth, int base, QChar fillChar) const
1071{
1072 KLocalizedString kls(*this);
1073 kls.d->checkNumber(a: std::abs(i: a));
1074 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar));
1075 kls.d->values.append(t: static_cast<intn>(a));
1076 return kls;
1077}
1078
1079KLocalizedString KLocalizedString::subs(ulong a, int fieldWidth, int base, QChar fillChar) const
1080{
1081 KLocalizedString kls(*this);
1082 kls.d->checkNumber(a);
1083 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar));
1084 kls.d->values.append(t: static_cast<uintn>(a));
1085 return kls;
1086}
1087
1088KLocalizedString KLocalizedString::subs(qlonglong a, int fieldWidth, int base, QChar fillChar) const
1089{
1090 KLocalizedString kls(*this);
1091 kls.d->checkNumber(a: qAbs(t: a));
1092 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldwidth: fieldWidth, base, fillChar));
1093 kls.d->values.append(t: static_cast<intn>(a));
1094 return kls;
1095}
1096
1097KLocalizedString KLocalizedString::subs(qulonglong a, int fieldWidth, int base, QChar fillChar) const
1098{
1099 KLocalizedString kls(*this);
1100 kls.d->checkNumber(a);
1101 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldwidth: fieldWidth, base, fillChar));
1102 kls.d->values.append(t: static_cast<uintn>(a));
1103 return kls;
1104}
1105
1106KLocalizedString KLocalizedString::subs(double a, int fieldWidth, char format, int precision, QChar fillChar) const
1107{
1108 KLocalizedString kls(*this);
1109 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, format, precision, fillChar));
1110 kls.d->values.append(t: static_cast<realn>(a));
1111 return kls;
1112}
1113
1114KLocalizedString KLocalizedString::subs(QChar a, int fieldWidth, QChar fillChar) const
1115{
1116 KLocalizedString kls(*this);
1117 QString baseArg = QString(a);
1118 QString fmtdArg = QStringLiteral("%1").arg(a, fieldWidth, fillChar);
1119 kls.d->arguments.append(t: fmtdArg);
1120 kls.d->values.append(t: baseArg);
1121 return kls;
1122}
1123
1124KLocalizedString KLocalizedString::subs(const QString &a, int fieldWidth, QChar fillChar) const
1125{
1126 KLocalizedString kls(*this);
1127 QString baseArg = a;
1128 QString fmtdArg = QStringLiteral("%1").arg(a, fieldWidth, fillChar);
1129 kls.d->arguments.append(t: fmtdArg);
1130 kls.d->values.append(t: baseArg);
1131 return kls;
1132}
1133
1134KLocalizedString KLocalizedString::subs(const KLocalizedString &a, int fieldWidth, QChar fillChar) const
1135{
1136 KLocalizedString kls(*this);
1137 // KLocalizedString arguments must be resolved inside toString
1138 // when the domain, language, visual format, etc. become known.
1139 int i = kls.d->arguments.size();
1140 kls.d->klsArguments[i] = a;
1141 kls.d->klsArgumentFieldWidths[i] = fieldWidth;
1142 kls.d->klsArgumentFillChars[i] = fillChar;
1143 kls.d->arguments.append(t: QString());
1144 kls.d->values.append(t: 0);
1145 return kls;
1146}
1147
1148KLocalizedString KLocalizedString::inContext(const QString &key, const QString &value) const
1149{
1150 KLocalizedString kls(*this);
1151 kls.d->dynamicContext[key] = value;
1152 return kls;
1153}
1154
1155KLocalizedString KLocalizedString::relaxSubs() const
1156{
1157 KLocalizedString kls(*this);
1158 kls.d->relaxedSubs = true;
1159 return kls;
1160}
1161
1162KLocalizedString KLocalizedString::ignoreMarkup() const
1163{
1164 KLocalizedString kls(*this);
1165 kls.d->markupAware = false;
1166 return kls;
1167}
1168
1169QByteArray KLocalizedString::untranslatedText() const
1170{
1171 return d->text;
1172}
1173
1174void KLocalizedString::setApplicationDomain(const QByteArray &domain)
1175{
1176 KLocalizedStringPrivateStatics *s = staticsKLSP();
1177
1178 QMutexLocker lock(&s->klspMutex);
1179
1180 s->applicationDomain = domain;
1181}
1182
1183QByteArray KLocalizedString::applicationDomain()
1184{
1185 KLocalizedStringPrivateStatics *s = staticsKLSP();
1186
1187 return s->applicationDomain;
1188}
1189
1190QStringList KLocalizedString::languages()
1191{
1192 KLocalizedStringPrivateStatics *s = staticsKLSP();
1193
1194 return s->languages;
1195}
1196
1197void KLocalizedString::setLanguages(const QStringList &languages)
1198{
1199 KLocalizedStringPrivateStatics *s = staticsKLSP();
1200
1201 QMutexLocker lock(&s->klspMutex);
1202
1203 s->languages = languages;
1204}
1205
1206void KLocalizedString::clearLanguages()
1207{
1208 KLocalizedStringPrivateStatics *s = staticsKLSP();
1209
1210 QMutexLocker lock(&s->klspMutex);
1211
1212 s->languages = s->localeLanguages;
1213}
1214
1215bool KLocalizedString::isApplicationTranslatedInto(const QString &language)
1216{
1217 KLocalizedStringPrivateStatics *s = staticsKLSP();
1218
1219 return language == s->codeLanguage || !KCatalog::catalogLocaleDir(domain: s->applicationDomain, language).isEmpty();
1220}
1221
1222QSet<QString> KLocalizedString::availableApplicationTranslations()
1223{
1224 return availableDomainTranslations(domain: staticsKLSP()->applicationDomain);
1225}
1226
1227QSet<QString> KLocalizedString::availableDomainTranslations(const QByteArray &domain)
1228{
1229 QSet<QString> availableLanguages;
1230
1231 if (!domain.isEmpty()) {
1232 availableLanguages = KCatalog::availableCatalogLanguages(domain);
1233 availableLanguages.insert(value: staticsKLSP()->codeLanguage);
1234 }
1235
1236 return availableLanguages;
1237}
1238
1239const KCatalog &KLocalizedStringPrivate::getCatalog(const QByteArray &domain, const QString &language)
1240{
1241 KLocalizedStringPrivateStatics *s = staticsKLSP();
1242
1243 QMutexLocker lock(&s->klspMutex);
1244
1245 QHash<QByteArray, KCatalogPtrHash>::iterator languageCatalogs = s->catalogs.find(key: domain);
1246 if (languageCatalogs == s->catalogs.end()) {
1247 languageCatalogs = s->catalogs.insert(key: domain, value: KCatalogPtrHash());
1248 }
1249 KCatalogPtrHash::iterator catalog = languageCatalogs->find(key: language);
1250 if (catalog == languageCatalogs->end()) {
1251 catalog = languageCatalogs->insert(key: language, value: new KCatalog(domain, language));
1252 locateScriptingModule(domain, language);
1253 }
1254 return **catalog;
1255}
1256
1257void KLocalizedStringPrivate::locateScriptingModule(const QByteArray &domain, const QString &language)
1258{
1259 KLocalizedStringPrivateStatics *s = staticsKLSP();
1260
1261 QMutexLocker lock(&s->klspMutex);
1262
1263 QString modapath =
1264 QStandardPaths::locate(type: QStandardPaths::GenericDataLocation, fileName: QLatin1String("locale/%1/%2/%3/%3.js").arg(args: language, args: s->scriptDir, args: QLatin1String{domain}));
1265
1266 // If the module exists and hasn't been already included.
1267 if (!modapath.isEmpty() && !s->scriptModules[language].contains(t: domain)) {
1268 // Indicate that the module has been considered.
1269 s->scriptModules[language].append(t: domain);
1270
1271 // Store the absolute path and language of the module,
1272 // to load on next script evaluation.
1273 QStringList module;
1274 module.append(t: modapath);
1275 module.append(t: language);
1276 s->scriptModulesToLoad.append(t: module);
1277 }
1278}
1279
1280extern "C" {
1281typedef KTranscript *(*InitFunc)();
1282}
1283
1284void KLocalizedStringPrivate::loadTranscript()
1285{
1286 KLocalizedStringPrivateStatics *s = staticsKLSP();
1287
1288 QMutexLocker lock(&s->klspMutex);
1289
1290 s->loadTranscriptCalled = true;
1291 s->ktrs = nullptr; // null indicates that Transcript is not available
1292
1293 // QPluginLoader is just used to find the plugin
1294 QPluginLoader loader(QStringLiteral("kf6/ktranscript"));
1295 if (loader.fileName().isEmpty()) {
1296 qCWarning(KI18N) << "Cannot find Transcript plugin.";
1297 return;
1298 }
1299
1300 QLibrary lib(loader.fileName());
1301 if (!lib.load()) {
1302 qCWarning(KI18N) << "Cannot load Transcript plugin:" << lib.errorString();
1303 return;
1304 }
1305
1306 InitFunc initf = (InitFunc)lib.resolve(symbol: "load_transcript");
1307 if (!initf) {
1308 lib.unload();
1309 qCWarning(KI18N) << "Cannot find function load_transcript in Transcript plugin.";
1310 return;
1311 }
1312
1313 s->ktrs = initf();
1314}
1315
1316QString KLocalizedString::localizedFilePath(const QString &filePath)
1317{
1318 KLocalizedStringPrivateStatics *s = staticsKLSP();
1319
1320 // Check if l10n subdirectory is present, stop if not.
1321 QFileInfo fileInfo(filePath);
1322 QString locDirPath = fileInfo.path() + QLatin1Char('/') + QLatin1String("l10n");
1323 QFileInfo locDirInfo(locDirPath);
1324 if (!locDirInfo.isDir()) {
1325 return filePath;
1326 }
1327
1328 // Go through possible localized paths by priority of languages,
1329 // return first that exists.
1330 QString fileName = fileInfo.fileName();
1331 for (const QString &lang : std::as_const(t&: s->languages)) {
1332 QString locFilePath = locDirPath + QLatin1Char('/') + lang + QLatin1Char('/') + fileName;
1333 QFileInfo locFileInfo(locFilePath);
1334 if (locFileInfo.isFile() && locFileInfo.isReadable()) {
1335 return locFilePath;
1336 }
1337 }
1338
1339 return filePath;
1340}
1341
1342QString KLocalizedString::removeAcceleratorMarker(const QString &label)
1343{
1344 return ::removeAcceleratorMarker(label);
1345}
1346
1347void KLocalizedString::addDomainLocaleDir(const QByteArray &domain, const QString &path)
1348{
1349 KCatalog::addDomainLocaleDir(domain, path);
1350}
1351
1352KLocalizedString ki18n(const char *text)
1353{
1354 return KLocalizedString(nullptr, nullptr, text, nullptr, false);
1355}
1356
1357KLocalizedString ki18nc(const char *context, const char *text)
1358{
1359 return KLocalizedString(nullptr, context, text, nullptr, false);
1360}
1361
1362KLocalizedString ki18np(const char *singular, const char *plural)
1363{
1364 return KLocalizedString(nullptr, nullptr, singular, plural, false);
1365}
1366
1367KLocalizedString ki18ncp(const char *context, const char *singular, const char *plural)
1368{
1369 return KLocalizedString(nullptr, context, singular, plural, false);
1370}
1371
1372KLocalizedString ki18nd(const char *domain, const char *text)
1373{
1374 return KLocalizedString(domain, nullptr, text, nullptr, false);
1375}
1376
1377KLocalizedString ki18ndc(const char *domain, const char *context, const char *text)
1378{
1379 return KLocalizedString(domain, context, text, nullptr, false);
1380}
1381
1382KLocalizedString ki18ndp(const char *domain, const char *singular, const char *plural)
1383{
1384 return KLocalizedString(domain, nullptr, singular, plural, false);
1385}
1386
1387KLocalizedString ki18ndcp(const char *domain, const char *context, const char *singular, const char *plural)
1388{
1389 return KLocalizedString(domain, context, singular, plural, false);
1390}
1391
1392KLocalizedString kxi18n(const char *text)
1393{
1394 return KLocalizedString(nullptr, nullptr, text, nullptr, true);
1395}
1396
1397KLocalizedString kxi18nc(const char *context, const char *text)
1398{
1399 return KLocalizedString(nullptr, context, text, nullptr, true);
1400}
1401
1402KLocalizedString kxi18np(const char *singular, const char *plural)
1403{
1404 return KLocalizedString(nullptr, nullptr, singular, plural, true);
1405}
1406
1407KLocalizedString kxi18ncp(const char *context, const char *singular, const char *plural)
1408{
1409 return KLocalizedString(nullptr, context, singular, plural, true);
1410}
1411
1412KLocalizedString kxi18nd(const char *domain, const char *text)
1413{
1414 return KLocalizedString(domain, nullptr, text, nullptr, true);
1415}
1416
1417KLocalizedString kxi18ndc(const char *domain, const char *context, const char *text)
1418{
1419 return KLocalizedString(domain, context, text, nullptr, true);
1420}
1421
1422KLocalizedString kxi18ndp(const char *domain, const char *singular, const char *plural)
1423{
1424 return KLocalizedString(domain, nullptr, singular, plural, true);
1425}
1426
1427KLocalizedString kxi18ndcp(const char *domain, const char *context, const char *singular, const char *plural)
1428{
1429 return KLocalizedString(domain, context, singular, plural, true);
1430}
1431

source code of ki18n/src/i18n/klocalizedstring.cpp