KI18n

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

KDE's Doxygen guidelines are available online.