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 <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(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();
54 // In case there are several concatenated locale specifications,
55 // truncate all but first.
56 int f = locale.indexOf(QLatin1Char(':'));
57 if (f >= 0) {
58 locale.truncate(f);
59 }
61 // now decompose into [language[_territory][.codeset][@modifier]]
62 f = locale.indexOf(QLatin1Char('@'));
63 if (f >= 0) {
64 modifier = locale.mid(f + 1);
65 locale.truncate(f);
66 }
68 f = locale.indexOf(QLatin1Char('.'));
69 if (f >= 0) {
70 charset = locale.mid(f + 1);
71 locale.truncate(f);
72 }
73
74 f = locale.indexOf(QLatin1Char('_'));
75 if (f >= 0) {
76 country = locale.mid(f + 1);
77 locale.truncate(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;
88 QString modifier;
89 QString charset;
90 splitLocale(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(envar));
115 if (!qenvar.isEmpty()) {
116 QString value = QFile::decodeName(qenvar);
117 if (isList) {
118 const auto listLanguages = value.split(QLatin1Char(':'), Qt::SkipEmptyParts);
119 for (const QString &v : listLanguages) {
120 appendLocaleString(languages, 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{
143 for (const QString &language : languages) {
144 int pos1 = language.indexOf(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(pos1, 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;
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
246
247class KLocalizedStringPrivateStatics
248{
249public:
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;
266 QList<QStringList> scriptModulesToLoad;
267
268 bool loadTranscriptCalled;
269 KTranscript *ktrs;
270
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(catalogs)) {
318 qDeleteAll(languageCatalogs);
319 }
320 // ktrs is handled by QLibrary.
321 // delete ktrs;
322 qDeleteAll(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(localeLanguages, "LANGUAGE", true);
334 appendLanguagesFromVariable(localeLanguages, "LC_ALL");
335 appendLanguagesFromVariable(localeLanguages, "LC_MESSAGES");
336 appendLanguagesFromVariable(localeLanguages, "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
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
366 : d(new KLocalizedStringPrivate(*rhs.d))
367{
368}
369
371{
372 if (&rhs != this) {
373 *d = *rhs.d;
374 }
375 return *this;
376}
377
379
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(msgid) : QString::fromUtf8(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, 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
453{
454 return d->toString(d->domain, d->languages, d->format);
455}
456
457QString KLocalizedString::toString(const char *domain) const
458{
459 return d->toString(domain, d->languages, d->format);
460}
461
463{
464 return d->toString(d->domain, languages, d->format);
465}
466
468{
469 return d->toString(d->domain, 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(QString::fromUtf8(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(resolvedDomain, resolvedLanguages, context, text, plural, number, language, rawTranslation);
508 QString country = extractCountry(resolvedLanguages);
509
510 // Set ordinary translation and possibly scripted translation.
511 QString translation;
512 QString scriptedTranslation;
513 int fencePos = rawTranslation.indexOf(s->theFence);
514 if (fencePos > 0) {
515 // Script fence has been found, strip the scripted from the
516 // ordinary translation.
517 translation = rawTranslation.left(fencePos);
518
519 // Scripted translation.
520 scriptedTranslation = rawTranslation.mid(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(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(translation) << "without ordinary translation, discarded.";
540 translation = plural.isEmpty() || number == 1 ? QString::fromUtf8(text) : QString::fromUtf8(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(i);
548 if (lsIt != klsArguments.constEnd()) {
549 const KLocalizedString &kls = *lsIt;
550 int fieldWidth = klsArgumentFieldWidths.value(i);
551 QChar fillChar = klsArgumentFillChars.value(i);
552 // Override argument's languages and format, but not domain.
553 bool isArgumentSub = true;
554 QString resdArg = kls.d->toString(kls.d->domain, resolvedLanguages, resolvedFormat, isArgumentSub);
555 resolvedValues.append(resdArg);
556 if (markupAware && !kls.d->markupAware) {
557 resdArg = Kuit::escape(resdArg);
558 }
559 resdArg = QStringLiteral("%1").arg(resdArg, fieldWidth, fillChar);
560 resolvedArguments.append(resdArg);
561 } else {
562 QString resdArg = arguments[i];
563 if (markupAware) {
564 resdArg = Kuit::escape(resdArg);
565 }
566 resolvedArguments.append(resdArg);
567 resolvedValues.append(values[i]);
568 }
569 }
570
571 // Substitute placeholders in ordinary translation.
572 QString finalTranslation = substituteSimple(translation, resolvedArguments);
573 if (markupAware && !isArgument) {
574 // Resolve markup in ordinary translation.
575 finalTranslation = formatMarkup(resolvedDomain, language, QString::fromUtf8(context), finalTranslation, 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, finalTranslation, resolvedArguments, 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(resolvedDomain, language, QString::fromUtf8(context), scriptedTranslation, 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(language);
598 for (const QString &pcall : pcalls) {
599 postTranscript(pcall, language, country, finalTranslation, resolvedArguments, 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(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(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(translation.mid(spos, ctpos - spos));
652 plords.append(plord);
653
654 // Position of next text segment.
655 spos = tpos;
656 }
657
658 tpos = translation.indexOf(plchar, tpos);
659 }
660 // Store last text segment.
661 tsegs.append(translation.mid(spos));
662
663#ifndef NDEBUG
664 // Perhaps enlarge storage for plural-number ordinal.
665 if (!plural.isEmpty() && numberOrdinal >= ords.size()) {
666 ords.resize(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(tsegs.at(i));
681 if (plords.at(i) >= arguments.size()) { // too little arguments
682 // put back the placeholder
683 finalTranslation.append(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(arguments.at(plords.at(i)));
692 }
693 }
694 finalTranslation.append(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(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(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(language);
740 if (formatter == s->formatters.end()) {
741 formatter = s->formatters.insert(language, 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->startInterp);
766 while (tpos >= 0) {
767 // Resolve substitutions in preceding text.
768 QString ptext = substituteSimple(scriptedTranslation.mid(ppos, tpos - ppos), arguments, s->scriptPlchar, true);
769 finalTranslation.append(ptext);
770
771 // Resolve interpolation.
772 QString result;
773 bool fallbackLocal;
774 tpos = resolveInterpolation(scriptedTranslation, tpos, language, country, ordinaryTranslation, arguments, values, result, 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(result);
789
790 // On to next interpolation.
791 ppos = tpos;
792 tpos = scriptedTranslation.indexOf(s->startInterp, tpos);
793 }
794 // Last text segment.
795 finalTranslation.append(substituteSimple(scriptedTranslation.mid(ppos), arguments, s->scriptPlchar, 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(pos, tpos - pos) << "in message" << shortenMessage(scriptedTranslation);
835 return -1;
836 }
837 if (QStringView(scriptedTranslation).mid(tpos, 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(tpos, 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(scriptedTranslation[tpos]);
860 ++tpos;
861 }
862 if (tpos == slen) {
863 qCWarning(KI18N) << "Unclosed quote in interpolation" << scriptedTranslation.mid(pos, tpos - pos) << "in message"
864 << shortenMessage(scriptedTranslation);
865 return -1;
866 }
867
868 // Append to list of segments, resolving placeholders.
869 segs.append(substituteSimple(seg, arguments, s->scriptPlchar, true));
870
871 ++tpos; // skip closing quote
872 } else if (scriptedTranslation.mid(tpos, islen) == s->startInterp) { // sub-interpolation
873 QString resultLocal;
874 bool fallbackLocal;
875 tpos = resolveInterpolation(scriptedTranslation, tpos, language, country, ordinaryTranslation, arguments, values, resultLocal, 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(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(tpos, islen) != s->startInterp //
890 && scriptedTranslation.mid(tpos, ielen) != s->endInterp) {
891 if (scriptedTranslation[tpos] == QLatin1Char('\\')) {
892 ++tpos; // escape next character
893 }
894 seg.append(scriptedTranslation[tpos]);
895 ++tpos;
896 }
897 if (tpos == slen) {
898 qCWarning(KI18N) << "Non-terminated interpolation" << scriptedTranslation.mid(pos, tpos - pos) << "in message"
899 << shortenMessage(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(seg);
908 if (vref.isValid()) {
909 segs.append(seg);
910 } else {
911 segs.append(substituteSimple(seg, arguments, s->scriptPlchar, 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(vref);
922 } else {
923 iargs.append(segs.join(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(context);
935 QString msgid = QString::fromUtf8(text);
936 QString scriptError;
937 bool fallbackLocal;
938 result = s->ktrs->eval(iargs,
939 language,
940 country,
941 msgctxt,
942 dynamicContext,
943 msgid,
944 arguments,
945 values,
946 ordinaryTranslation,
947 s->scriptModulesToLoad,
948 scriptError,
949 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(pos, tpos - pos) << "in" << shortenMessage(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(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(1);
981 int numstrAsInt = QStringView(numstr).left(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) - 1;
989 if (!ok || index >= values.size()) {
990 return QVariant();
991 }
992
993 // Passed all hoops.
994 return values.at(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(pcall);
1015 QString msgctxt = QString::fromUtf8(context);
1016 QString msgid = QString::fromUtf8(text);
1017 QString scriptError;
1018 bool fallback;
1019 s->ktrs->eval(iargs, language, country, msgctxt, dynamicContext, msgid, arguments, values, finalTranslation, s->scriptModulesToLoad, scriptError, fallback);
1020 // s->scriptModulesToLoad will be cleared during the call.
1021
1022 // If the evaluation went wrong.
1023 if (!scriptError.isEmpty()) {
1024 qCWarning(KI18N) << "Post call" << pcall << "for message" << shortenMessage(msgid) << "failed:" << scriptError;
1025 return QString();
1026 }
1027
1028 return finalTranslation;
1029}
1030
1032{
1033 KLocalizedString kls(*this);
1034 kls.d->languages = languages;
1035 return kls;
1036}
1037
1039{
1040 KLocalizedString kls(*this);
1041 kls.d->domain = domain;
1042 return kls;
1043}
1044
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(std::abs(a));
1056 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar));
1057 kls.d->values.append(static_cast<intn>(a));
1058 return kls;
1059}
1060
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(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(std::abs(a));
1074 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar));
1075 kls.d->values.append(static_cast<intn>(a));
1076 return kls;
1077}
1078
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(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(qAbs(a));
1092 kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar));
1093 kls.d->values.append(static_cast<intn>(a));
1094 return kls;
1095}
1096
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, base, fillChar));
1102 kls.d->values.append(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(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(fmtdArg);
1120 kls.d->values.append(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(fmtdArg);
1130 kls.d->values.append(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(QString());
1144 kls.d->values.append(0);
1145 return kls;
1146}
1147
1149{
1150 KLocalizedString kls(*this);
1151 kls.d->dynamicContext[key] = value;
1152 return kls;
1153}
1154
1156{
1157 KLocalizedString kls(*this);
1158 kls.d->relaxedSubs = true;
1159 return kls;
1160}
1161
1163{
1164 KLocalizedString kls(*this);
1165 kls.d->markupAware = false;
1166 return kls;
1167}
1168
1170{
1171 return d->text;
1172}
1173
1175{
1176 KLocalizedStringPrivateStatics *s = staticsKLSP();
1177
1178 QMutexLocker lock(&s->klspMutex);
1179
1180 s->applicationDomain = domain;
1181}
1182
1184{
1185 KLocalizedStringPrivateStatics *s = staticsKLSP();
1186
1187 return s->applicationDomain;
1188}
1189
1191{
1192 KLocalizedStringPrivateStatics *s = staticsKLSP();
1193
1194 return s->languages;
1195}
1196
1198{
1199 KLocalizedStringPrivateStatics *s = staticsKLSP();
1200
1201 QMutexLocker lock(&s->klspMutex);
1202
1203 s->languages = languages;
1204}
1205
1207{
1208 KLocalizedStringPrivateStatics *s = staticsKLSP();
1209
1210 QMutexLocker lock(&s->klspMutex);
1211
1212 s->languages = s->localeLanguages;
1213}
1214
1216{
1217 KLocalizedStringPrivateStatics *s = staticsKLSP();
1218
1219 return language == s->codeLanguage || !KCatalog::catalogLocaleDir(s->applicationDomain, language).isEmpty();
1220}
1221
1226
1228{
1229 QSet<QString> availableLanguages;
1230
1231 if (!domain.isEmpty()) {
1232 availableLanguages = KCatalog::availableCatalogLanguages(domain);
1233 availableLanguages.insert(staticsKLSP()->codeLanguage);
1234 }
1235
1236 return availableLanguages;
1237}
1238
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(domain);
1246 if (languageCatalogs == s->catalogs.end()) {
1247 languageCatalogs = s->catalogs.insert(domain, KCatalogPtrHash());
1248 }
1249 KCatalogPtrHash::iterator catalog = languageCatalogs->find(language);
1250 if (catalog == languageCatalogs->end()) {
1251 catalog = languageCatalogs->insert(language, new KCatalog(domain, language));
1252 locateScriptingModule(domain, language);
1253 }
1254 return **catalog;
1255}
1256
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(QStandardPaths::GenericDataLocation, QLatin1String("locale/%1/%2/%3/%3.js").arg(language, s->scriptDir, QLatin1String{domain}));
1265
1266 // If the module exists and hasn't been already included.
1267 if (!modapath.isEmpty() && !s->scriptModules[language].contains(domain)) {
1268 // Indicate that the module has been considered.
1269 s->scriptModules[language].append(domain);
1270
1271 // Store the absolute path and language of the module,
1272 // to load on next script evaluation.
1273 QStringList module;
1274 module.append(modapath);
1275 module.append(language);
1276 s->scriptModulesToLoad.append(module);
1277 }
1278}
1279
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("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
1317{
1318 KLocalizedStringPrivateStatics *s = staticsKLSP();
1319
1320 // Check if l10n subdirectory is present, stop if not.
1321 QFileInfo fileInfo(filePath);
1322 QString locDirPath = fileInfo.path() + QLatin1Char('/') + QLatin1String("l10n");
1323 QFileInfo locDirInfo(locDirPath);
1324 if (!locDirInfo.isDir()) {
1325 return filePath;
1326 }
1327
1328 // Go through possible localized paths by priority of languages,
1329 // return first that exists.
1330 QString fileName = fileInfo.fileName();
1331 for (const QString &lang : std::as_const(s->languages)) {
1332 QString locFilePath = locDirPath + QLatin1Char('/') + lang + QLatin1Char('/') + fileName;
1333 QFileInfo locFileInfo(locFilePath);
1334 if (locFileInfo.isFile() && locFileInfo.isReadable()) {
1335 return locFilePath;
1336 }
1337 }
1338
1339 return filePath;
1340}
1341
1343{
1344 return ::removeAcceleratorMarker(label);
1345}
1346
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}
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:27
@ UndefinedFormat
Visual format not defined.
Definition kuitsetup.h:35
KEDUVOCDOCUMENT_EXPORT QStringList languages()
bool isEmpty() const const
bool isNull() 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
QHash::const_iterator constEnd() const const
QHash::const_iterator constFind(const Key &key) const const
bool contains(const Key &key) const const
QHash::iterator end()
QHash::iterator find(const Key &key)
QHash::iterator insert(const Key &key, const T &value)
const T value(const Key &key) const const
void append(const T &value)
const T & at(int i) const const
bool isEmpty() const const
T & last()
int size() const const
QLocale system()
QStringList uiLanguages() const const
QSet::iterator insert(const T &value)
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString & append(QChar ch)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
void clear()
QString fromUtf8(const char *str, int size)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(int n) const const
int length() const const
QString mid(int position, int n) const const
QString number(int n, int base)
QString & replace(int position, int n, QChar after)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
void truncate(int position)
QString join(const QString &separator) const const
bool isEmpty() const const
QStringView left(qsizetype length) const const
SkipEmptyParts
bool isValid() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sun Feb 25 2024 18:47:44 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.