KI18n

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

KDE's Doxygen guidelines are available online.