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 <kuitmarkup_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 [email protected] 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  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  return;
423  }
424 
425  // Languages are ordered from highest to lowest priority.
426  for (const QString &testLanguage : languages) {
427  // If code language reached, no catalog lookup is needed.
428  if (testLanguage == s->codeLanguage) {
429  return;
430  }
431  const KCatalog &catalog = getCatalog(domain, testLanguage);
432  QString testMsgstr;
433  if (!msgctxt.isNull() && !msgid_plural.isNull()) {
434  testMsgstr = catalog.translate(msgctxt, msgid, msgid_plural, n);
435  } else if (!msgid_plural.isNull()) {
436  testMsgstr = catalog.translate(msgid, msgid_plural, n);
437  } else if (!msgctxt.isNull()) {
438  testMsgstr = catalog.translate(msgctxt, msgid);
439  } else {
440  testMsgstr = catalog.translate(msgid);
441  }
442  if (!testMsgstr.isEmpty()) {
443  // Translation found.
444  language = testLanguage;
445  msgstr = testMsgstr;
446  return;
447  }
448  }
449 }
450 
452 {
453  return d->toString(d->domain, d->languages, d->format);
454 }
455 
456 QString KLocalizedString::toString(const char *domain) const
457 {
458  return d->toString(domain, d->languages, d->format);
459 }
460 
462 {
463  return d->toString(d->domain, languages, d->format);
464 }
465 
467 {
468  return d->toString(d->domain, d->languages, format);
469 }
470 
471 QString KLocalizedStringPrivate::toString(const QByteArray &domain, const QStringList &languages, Kuit::VisualFormat format, bool isArgument) const
472 {
473  KLocalizedStringPrivateStatics *s = staticsKLSP();
474 
475  QMutexLocker lock(&s->klspMutex);
476 
477  // Assure the message has been supplied.
478  if (text.isEmpty()) {
479  qCWarning(KI18N) << "Trying to convert empty KLocalizedString to QString.";
480 #ifndef NDEBUG
481  return QStringLiteral("(I18N_EMPTY_MESSAGE)");
482 #else
483  return QString();
484 #endif
485  }
486 
487  // Check whether plural argument has been supplied, if message has plural.
488  if (!plural.isEmpty() && !numberSet) {
489  qCWarning(KI18N) << "Plural argument to message" << shortenMessage(QString::fromUtf8(text)) << "not supplied before conversion.";
490  }
491 
492  // Resolve inputs.
493  QByteArray resolvedDomain = domain;
494  if (resolvedDomain.isEmpty()) {
495  resolvedDomain = s->applicationDomain;
496  }
497  QStringList resolvedLanguages = languages;
498  if (resolvedLanguages.isEmpty()) {
499  resolvedLanguages = s->languages;
500  }
501  Kuit::VisualFormat resolvedFormat = format;
502 
503  // Get raw translation.
504  QString language;
505  QString rawTranslation;
506  translateRaw(resolvedDomain, resolvedLanguages, context, text, plural, number, language, rawTranslation);
507  QString country = extractCountry(resolvedLanguages);
508 
509  // Set ordinary translation and possibly scripted translation.
510  QString translation;
511  QString scriptedTranslation;
512  int fencePos = rawTranslation.indexOf(s->theFence);
513  if (fencePos > 0) {
514  // Script fence has been found, strip the scripted from the
515  // ordinary translation.
516  translation = rawTranslation.left(fencePos);
517 
518  // Scripted translation.
519  scriptedTranslation = rawTranslation.mid(fencePos + s->theFence.length());
520 
521  // Try to initialize Transcript if not initialized and script not empty.
522  // FIXME: And also if Transcript not disabled: where to configure this?
523  if (!s->loadTranscriptCalled && !scriptedTranslation.isEmpty()) {
524  loadTranscript();
525 
526  // Definitions from this library's scripting module
527  // must be available to all other modules.
528  // So force creation of this library's catalog here,
529  // to make sure the scripting module is loaded.
530  getCatalog(s->ourDomain, language);
531  }
532  } else if (fencePos < 0) {
533  // No script fence, use translation as is.
534  translation = rawTranslation;
535  } else { // fencePos == 0
536  // The msgstr starts with the script fence, no ordinary translation.
537  // This is not allowed, consider message not translated.
538  qCWarning(KI18N) << "Scripted message" << shortenMessage(translation) << "without ordinary translation, discarded.";
539  translation = plural.isEmpty() || number == 1 ? QString::fromUtf8(text) : QString::fromUtf8(plural);
540  }
541 
542  // Resolve substituted KLocalizedString arguments.
543  QStringList resolvedArguments;
544  QList<QVariant> resolvedValues;
545  for (int i = 0; i < arguments.size(); i++) {
546  auto lsIt = klsArguments.constFind(i);
547  if (lsIt != klsArguments.constEnd()) {
548  const KLocalizedString &kls = *lsIt;
549  int fieldWidth = klsArgumentFieldWidths.value(i);
550  QChar fillChar = klsArgumentFillChars.value(i);
551  // Override argument's languages and format, but not domain.
552  bool isArgumentSub = true;
553  QString resdArg = kls.d->toString(kls.d->domain, resolvedLanguages, resolvedFormat, isArgumentSub);
554  resolvedValues.append(resdArg);
555  if (markupAware && !kls.d->markupAware) {
556  resdArg = Kuit::escape(resdArg);
557  }
558  resdArg = QStringLiteral("%1").arg(resdArg, fieldWidth, fillChar);
559  resolvedArguments.append(resdArg);
560  } else {
561  QString resdArg = arguments[i];
562  if (markupAware) {
563  resdArg = Kuit::escape(resdArg);
564  }
565  resolvedArguments.append(resdArg);
566  resolvedValues.append(values[i]);
567  }
568  }
569 
570  // Substitute placeholders in ordinary translation.
571  QString finalTranslation = substituteSimple(translation, resolvedArguments);
572  if (markupAware && !isArgument) {
573  // Resolve markup in ordinary translation.
574  finalTranslation = formatMarkup(resolvedDomain, language, QString::fromUtf8(context), finalTranslation, resolvedFormat);
575  }
576 
577  // If there is also a scripted translation.
578  if (!scriptedTranslation.isEmpty()) {
579  // Evaluate scripted translation.
580  bool fallback = false;
581  scriptedTranslation = substituteTranscript(scriptedTranslation, language, country, finalTranslation, resolvedArguments, resolvedValues, fallback);
582 
583  // If any translation produced and no fallback requested.
584  if (!scriptedTranslation.isEmpty() && !fallback) {
585  if (markupAware && !isArgument) {
586  // Resolve markup in scripted translation.
587  scriptedTranslation = formatMarkup(resolvedDomain, language, QString::fromUtf8(context), scriptedTranslation, resolvedFormat);
588  }
589  finalTranslation = scriptedTranslation;
590  }
591  }
592 
593  // Execute any scripted post calls; they cannot modify the final result,
594  // but are used to set states.
595  if (s->ktrs != nullptr) {
596  const QStringList pcalls = s->ktrs->postCalls(language);
597  for (const QString &pcall : pcalls) {
598  postTranscript(pcall, language, country, finalTranslation, resolvedArguments, resolvedValues);
599  }
600  }
601 
602  return finalTranslation;
603 }
604 
605 QString KLocalizedStringPrivate::substituteSimple(const QString &translation, const QStringList &arguments, QChar plchar, bool isPartial) const
606 {
607 #ifdef NDEBUG
608  Q_UNUSED(isPartial);
609 #endif
610 
611  QStringList tsegs; // text segments per placeholder occurrence
612  QList<int> plords; // ordinal numbers per placeholder occurrence
613 #ifndef NDEBUG
614  QVector<int> ords; // indicates which placeholders are present
615 #endif
616  int slen = translation.length();
617  int spos = 0;
618  int tpos = translation.indexOf(plchar);
619  while (tpos >= 0) {
620  int ctpos = tpos;
621 
622  ++tpos;
623  if (tpos == slen) {
624  break;
625  }
626 
627  if (translation[tpos].digitValue() > 0) {
628  // NOTE: %0 is not considered a placeholder.
629  // Get the placeholder ordinal.
630  int plord = 0;
631  while (tpos < slen && translation[tpos].digitValue() >= 0) {
632  plord = 10 * plord + translation[tpos].digitValue();
633  ++tpos;
634  }
635  --plord; // ordinals are zero based
636 
637 #ifndef NDEBUG
638  // Perhaps enlarge storage for indicators.
639  // Note that QVector<int> will initialize new elements to 0,
640  // as they are supposed to be.
641  if (plord >= ords.size()) {
642  ords.resize(plord + 1);
643  }
644 
645  // Indicate that placeholder with computed ordinal is present.
646  ords[plord] = 1;
647 #endif
648 
649  // Store text segment prior to placeholder and placeholder number.
650  tsegs.append(translation.mid(spos, ctpos - spos));
651  plords.append(plord);
652 
653  // Position of next text segment.
654  spos = tpos;
655  }
656 
657  tpos = translation.indexOf(plchar, tpos);
658  }
659  // Store last text segment.
660  tsegs.append(translation.mid(spos));
661 
662 #ifndef NDEBUG
663  // Perhaps enlarge storage for plural-number ordinal.
664  if (!plural.isEmpty() && numberOrdinal >= ords.size()) {
665  ords.resize(numberOrdinal + 1);
666  }
667 
668  // Message might have plural but without plural placeholder, which is an
669  // allowed state. To ease further logic, indicate that plural placeholder
670  // is present anyway if message has plural.
671  if (!plural.isEmpty()) {
672  ords[numberOrdinal] = 1;
673  }
674 #endif
675 
676  // Assemble the final string from text segments and arguments.
677  QString finalTranslation;
678  for (int i = 0; i < plords.size(); i++) {
679  finalTranslation.append(tsegs.at(i));
680  if (plords.at(i) >= arguments.size()) { // too little arguments
681  // put back the placeholder
682  finalTranslation.append(QLatin1Char('%') + QString::number(plords.at(i) + 1));
683 #ifndef NDEBUG
684  if (!isPartial) {
685  // spoof the message
686  finalTranslation.append(QStringLiteral("(I18N_ARGUMENT_MISSING)"));
687  }
688 #endif
689  } else { // just fine
690  finalTranslation.append(arguments.at(plords.at(i)));
691  }
692  }
693  finalTranslation.append(tsegs.last());
694 
695 #ifndef NDEBUG
696  if (!isPartial && !relaxedSubs) {
697  // Check that there are no gaps in numbering sequence of placeholders.
698  bool gaps = false;
699  for (int i = 0; i < ords.size(); i++) {
700  if (!ords.at(i)) {
701  gaps = true;
702  qCWarning(KI18N).nospace() << "Placeholder %" << QString::number(i + 1) << " skipped in message " << shortenMessage(translation);
703  }
704  }
705  // If no gaps, check for mismatch between the number of
706  // unique placeholders and actually supplied arguments.
707  if (!gaps && ords.size() != arguments.size()) {
708  qCWarning(KI18N) << arguments.size() << "instead of" << ords.size() << "arguments to message" << shortenMessage(translation)
709  << "supplied before conversion";
710  }
711 
712  // Some spoofs.
713  if (gaps) {
714  finalTranslation.append(QStringLiteral("(I18N_GAPS_IN_PLACEHOLDER_SEQUENCE)"));
715  }
716  if (ords.size() < arguments.size()) {
717  finalTranslation.append(QStringLiteral("(I18N_EXCESS_ARGUMENTS_SUPPLIED)"));
718  }
719  }
720  if (!isPartial) {
721  if (!plural.isEmpty() && !numberSet) {
722  finalTranslation.append(QStringLiteral("(I18N_PLURAL_ARGUMENT_MISSING)"));
723  }
724  }
725 #endif
726 
727  return finalTranslation;
728 }
729 
730 QString KLocalizedStringPrivate::formatMarkup(const QByteArray &domain,
731  const QString &language,
732  const QString &context,
733  const QString &text,
734  Kuit::VisualFormat format) const
735 {
736  KLocalizedStringPrivateStatics *s = staticsKLSP();
737 
738  QHash<QString, KuitFormatter *>::iterator formatter = s->formatters.find(language);
739  if (formatter == s->formatters.end()) {
740  formatter = s->formatters.insert(language, new KuitFormatter(language));
741  }
742  return (*formatter)->format(domain, context, text, format);
743 }
744 
745 QString KLocalizedStringPrivate::substituteTranscript(const QString &scriptedTranslation,
746  const QString &language,
747  const QString &country,
748  const QString &ordinaryTranslation,
749  const QStringList &arguments,
750  const QList<QVariant> &values,
751  bool &fallback) const
752 {
753  KLocalizedStringPrivateStatics *s = staticsKLSP();
754 
755  if (s->ktrs == nullptr) {
756  // Scripting engine not available.
757  return QString();
758  }
759 
760  // Iterate by interpolations.
761  QString finalTranslation;
762  fallback = false;
763  int ppos = 0;
764  int tpos = scriptedTranslation.indexOf(s->startInterp);
765  while (tpos >= 0) {
766  // Resolve substitutions in preceding text.
767  QString ptext = substituteSimple(scriptedTranslation.mid(ppos, tpos - ppos), arguments, s->scriptPlchar, true);
768  finalTranslation.append(ptext);
769 
770  // Resolve interpolation.
771  QString result;
772  bool fallbackLocal;
773  tpos = resolveInterpolation(scriptedTranslation, tpos, language, country, ordinaryTranslation, arguments, values, result, fallbackLocal);
774 
775  // If there was a problem in parsing the interpolation, cannot proceed
776  // (debug info already reported while parsing).
777  if (tpos < 0) {
778  return QString();
779  }
780  // If fallback has been explicitly requested, indicate global fallback
781  // but proceed with evaluations (other interpolations may set states).
782  if (fallbackLocal) {
783  fallback = true;
784  }
785 
786  // Add evaluated interpolation to the text.
787  finalTranslation.append(result);
788 
789  // On to next interpolation.
790  ppos = tpos;
791  tpos = scriptedTranslation.indexOf(s->startInterp, tpos);
792  }
793  // Last text segment.
794  finalTranslation.append(substituteSimple(scriptedTranslation.mid(ppos), arguments, s->scriptPlchar, true));
795 
796  // Return empty string if fallback was requested.
797  return fallback ? QString() : finalTranslation;
798 }
799 
800 int KLocalizedStringPrivate::resolveInterpolation(const QString &scriptedTranslation,
801  int pos,
802  const QString &language,
803  const QString &country,
804  const QString &ordinaryTranslation,
805  const QStringList &arguments,
806  const QList<QVariant> &values,
807  QString &result,
808  bool &fallback) const
809 {
810  // pos is the position of opening character sequence.
811  // Returns the position of first character after closing sequence,
812  // or -1 in case of parsing error.
813  // result is set to result of Transcript evaluation.
814  // fallback is set to true if Transcript evaluation requested so.
815 
816  KLocalizedStringPrivateStatics *s = staticsKLSP();
817 
818  result.clear();
819  fallback = false;
820 
821  // Split interpolation into arguments.
822  QList<QVariant> iargs;
823  const int slen = scriptedTranslation.length();
824  const int islen = s->startInterp.length();
825  const int ielen = s->endInterp.length();
826  int tpos = pos + s->startInterp.length();
827  while (1) {
828  // Skip whitespace.
829  while (tpos < slen && scriptedTranslation[tpos].isSpace()) {
830  ++tpos;
831  }
832  if (tpos == slen) {
833  qCWarning(KI18N) << "Unclosed interpolation" << scriptedTranslation.mid(pos, tpos - pos) << "in message" << shortenMessage(scriptedTranslation);
834  return -1;
835  }
836  if (QStringView(scriptedTranslation).mid(tpos, ielen) == s->endInterp) {
837  break; // no more arguments
838  }
839 
840  // Parse argument: may be concatenated from free and quoted text,
841  // and sub-interpolations.
842  // Free and quoted segments may contain placeholders, substitute them;
843  // recurse into sub-interpolations.
844  // Free segments may be value references, parse and record for
845  // consideration at the end.
846  // Mind backslash escapes throughout.
847  QStringList segs;
848  QVariant vref;
849  while (!scriptedTranslation[tpos].isSpace() && scriptedTranslation.mid(tpos, ielen) != s->endInterp) {
850  if (scriptedTranslation[tpos] == QLatin1Char('\'')) { // quoted segment
851  QString seg;
852  ++tpos; // skip opening quote
853  // Find closing quote.
854  while (tpos < slen && scriptedTranslation[tpos] != QLatin1Char('\'')) {
855  if (scriptedTranslation[tpos] == QLatin1Char('\\')) {
856  ++tpos; // escape next character
857  }
858  seg.append(scriptedTranslation[tpos]);
859  ++tpos;
860  }
861  if (tpos == slen) {
862  qCWarning(KI18N) << "Unclosed quote in interpolation" << scriptedTranslation.mid(pos, tpos - pos) << "in message"
863  << shortenMessage(scriptedTranslation);
864  return -1;
865  }
866 
867  // Append to list of segments, resolving placeholders.
868  segs.append(substituteSimple(seg, arguments, s->scriptPlchar, true));
869 
870  ++tpos; // skip closing quote
871  } else if (scriptedTranslation.mid(tpos, islen) == s->startInterp) { // sub-interpolation
872  QString resultLocal;
873  bool fallbackLocal;
874  tpos = resolveInterpolation(scriptedTranslation, tpos, language, country, ordinaryTranslation, arguments, values, resultLocal, fallbackLocal);
875  if (tpos < 0) { // unrecoverable problem in sub-interpolation
876  // Error reported in the subcall.
877  return tpos;
878  }
879  if (fallbackLocal) { // sub-interpolation requested fallback
880  fallback = true;
881  }
882  segs.append(resultLocal);
883  } else { // free segment
884  QString seg;
885  // Find whitespace, quote, opening or closing sequence.
886  while (tpos < slen && !scriptedTranslation[tpos].isSpace() //
887  && scriptedTranslation[tpos] != QLatin1Char('\'') //
888  && scriptedTranslation.mid(tpos, islen) != s->startInterp //
889  && scriptedTranslation.mid(tpos, ielen) != s->endInterp) {
890  if (scriptedTranslation[tpos] == QLatin1Char('\\')) {
891  ++tpos; // escape next character
892  }
893  seg.append(scriptedTranslation[tpos]);
894  ++tpos;
895  }
896  if (tpos == slen) {
897  qCWarning(KI18N) << "Non-terminated interpolation" << scriptedTranslation.mid(pos, tpos - pos) << "in message"
898  << shortenMessage(scriptedTranslation);
899  return -1;
900  }
901 
902  // The free segment may look like a value reference;
903  // in that case, record which value it would reference,
904  // and add verbatim to the segment list.
905  // Otherwise, do a normal substitution on the segment.
906  vref = segmentToValue(seg);
907  if (vref.isValid()) {
908  segs.append(seg);
909  } else {
910  segs.append(substituteSimple(seg, arguments, s->scriptPlchar, true));
911  }
912  }
913  }
914 
915  // Append this argument to rest of the arguments.
916  // If the there was a single text segment and it was a proper value
917  // reference, add it instead of the joined segments.
918  // Otherwise, add the joined segments.
919  if (segs.size() == 1 && vref.isValid()) {
920  iargs.append(vref);
921  } else {
922  iargs.append(segs.join(QString()));
923  }
924  }
925  tpos += ielen; // skip to first character after closing sequence
926 
927  // NOTE: Why not substitute placeholders (via substituteSimple) in one
928  // global pass, then handle interpolations in second pass? Because then
929  // there is the danger of substituted text or sub-interpolations producing
930  // quotes and escapes themselves, which would mess up the parsing.
931 
932  // Evaluate interpolation.
933  QString msgctxt = QString::fromUtf8(context);
934  QString msgid = QString::fromUtf8(text);
935  QString scriptError;
936  bool fallbackLocal;
937  result = s->ktrs->eval(iargs,
938  language,
939  country,
940  msgctxt,
941  dynamicContext,
942  msgid,
943  arguments,
944  values,
945  ordinaryTranslation,
946  s->scriptModulesToLoad,
947  scriptError,
948  fallbackLocal);
949  // s->scriptModulesToLoad will be cleared during the call.
950 
951  if (fallbackLocal) { // evaluation requested fallback
952  fallback = true;
953  }
954  if (!scriptError.isEmpty()) { // problem with evaluation
955  fallback = true; // also signal fallback
956  if (!scriptError.isEmpty()) {
957  qCWarning(KI18N) << "Interpolation" << scriptedTranslation.mid(pos, tpos - pos) << "in" << shortenMessage(scriptedTranslation)
958  << "failed:" << scriptError;
959  }
960  }
961 
962  return tpos;
963 }
964 
965 QVariant KLocalizedStringPrivate::segmentToValue(const QString &segment) const
966 {
967  KLocalizedStringPrivateStatics *s = staticsKLSP();
968 
969  // Return invalid variant if segment is either not a proper
970  // value reference, or the reference is out of bounds.
971 
972  // Value reference must start with a special character.
973  if (!segment.startsWith(s->scriptVachar)) {
974  return QVariant();
975  }
976 
977  // Reference number must start with 1-9.
978  // (If numstr is empty, toInt() will return 0.)
979  QString numstr = segment.mid(1);
980 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
981  int numstrAsInt = QStringView(numstr).left(1).toInt();
982 #else
983  int numstrAsInt = numstr.leftRef(1).toInt();
984 #endif
985  if (numstrAsInt < 1) {
986  return QVariant();
987  }
988 
989  // Number must be valid and in bounds.
990  bool ok;
991  int index = numstr.toInt(&ok) - 1;
992  if (!ok || index >= values.size()) {
993  return QVariant();
994  }
995 
996  // Passed all hoops.
997  return values.at(index);
998 }
999 
1000 QString KLocalizedStringPrivate::postTranscript(const QString &pcall,
1001  const QString &language,
1002  const QString &country,
1003  const QString &finalTranslation,
1004  const QStringList &arguments,
1005  const QList<QVariant> &values) const
1006 {
1007  KLocalizedStringPrivateStatics *s = staticsKLSP();
1008 
1009  if (s->ktrs == nullptr) {
1010  // Scripting engine not available.
1011  // (Though this cannot happen, we wouldn't be here then.)
1012  return QString();
1013  }
1014 
1015  // Resolve the post call.
1016  QList<QVariant> iargs;
1017  iargs.append(pcall);
1018  QString msgctxt = QString::fromUtf8(context);
1019  QString msgid = QString::fromUtf8(text);
1020  QString scriptError;
1021  bool fallback;
1022  QString dummy = s->ktrs->eval(iargs,
1023  language,
1024  country,
1025  msgctxt,
1026  dynamicContext,
1027  msgid,
1028  arguments,
1029  values,
1030  finalTranslation,
1031  s->scriptModulesToLoad,
1032  scriptError,
1033  fallback);
1034  // s->scriptModulesToLoad will be cleared during the call.
1035 
1036  // If the evaluation went wrong.
1037  if (!scriptError.isEmpty()) {
1038  qCWarning(KI18N) << "Post call" << pcall << "for message" << shortenMessage(msgid) << "failed:" << scriptError;
1039  return QString();
1040  }
1041 
1042  return finalTranslation;
1043 }
1044 
1046 {
1047  KLocalizedString kls(*this);
1048  kls.d->languages = languages;
1049  return kls;
1050 }
1051 
1053 {
1054  KLocalizedString kls(*this);
1055  kls.d->domain = domain;
1056  return kls;
1057 }
1058 
1060 {
1061  KLocalizedString kls(*this);
1062  kls.d->format = format;
1063  return kls;
1064 }
1065 
1066 KLocalizedString KLocalizedString::subs(int a, int fieldWidth, int base, QChar fillChar) const
1067 {
1068  KLocalizedString kls(*this);
1069  kls.d->checkNumber(std::abs(a));
1070  kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar));
1071  kls.d->values.append(static_cast<intn>(a));
1072  return kls;
1073 }
1074 
1075 KLocalizedString KLocalizedString::subs(uint a, int fieldWidth, int base, QChar fillChar) const
1076 {
1077  KLocalizedString kls(*this);
1078  kls.d->checkNumber(a);
1079  kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar));
1080  kls.d->values.append(static_cast<uintn>(a));
1081  return kls;
1082 }
1083 
1084 KLocalizedString KLocalizedString::subs(long a, int fieldWidth, int base, QChar fillChar) const
1085 {
1086  KLocalizedString kls(*this);
1087  kls.d->checkNumber(std::abs(a));
1088  kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar));
1089  kls.d->values.append(static_cast<intn>(a));
1090  return kls;
1091 }
1092 
1093 KLocalizedString KLocalizedString::subs(ulong a, int fieldWidth, int base, QChar fillChar) const
1094 {
1095  KLocalizedString kls(*this);
1096  kls.d->checkNumber(a);
1097  kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar));
1098  kls.d->values.append(static_cast<uintn>(a));
1099  return kls;
1100 }
1101 
1102 KLocalizedString KLocalizedString::subs(qlonglong a, int fieldWidth, int base, QChar fillChar) const
1103 {
1104  KLocalizedString kls(*this);
1105  kls.d->checkNumber(qAbs(a));
1106  kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar));
1107  kls.d->values.append(static_cast<intn>(a));
1108  return kls;
1109 }
1110 
1111 KLocalizedString KLocalizedString::subs(qulonglong a, int fieldWidth, int base, QChar fillChar) const
1112 {
1113  KLocalizedString kls(*this);
1114  kls.d->checkNumber(a);
1115  kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, base, fillChar));
1116  kls.d->values.append(static_cast<uintn>(a));
1117  return kls;
1118 }
1119 
1120 KLocalizedString KLocalizedString::subs(double a, int fieldWidth, char format, int precision, QChar fillChar) const
1121 {
1122  KLocalizedString kls(*this);
1123  kls.d->arguments.append(QStringLiteral("%L1").arg(a, fieldWidth, format, precision, fillChar));
1124  kls.d->values.append(static_cast<realn>(a));
1125  return kls;
1126 }
1127 
1128 KLocalizedString KLocalizedString::subs(QChar a, int fieldWidth, QChar fillChar) const
1129 {
1130  KLocalizedString kls(*this);
1131  QString baseArg = QString(a);
1132  QString fmtdArg = QStringLiteral("%1").arg(a, fieldWidth, fillChar);
1133  kls.d->arguments.append(fmtdArg);
1134  kls.d->values.append(baseArg);
1135  return kls;
1136 }
1137 
1138 KLocalizedString KLocalizedString::subs(const QString &a, int fieldWidth, QChar fillChar) const
1139 {
1140  KLocalizedString kls(*this);
1141  QString baseArg = a;
1142  QString fmtdArg = QStringLiteral("%1").arg(a, fieldWidth, fillChar);
1143  kls.d->arguments.append(fmtdArg);
1144  kls.d->values.append(baseArg);
1145  return kls;
1146 }
1147 
1148 KLocalizedString KLocalizedString::subs(const KLocalizedString &a, int fieldWidth, QChar fillChar) const
1149 {
1150  KLocalizedString kls(*this);
1151  // KLocalizedString arguments must be resolved inside toString
1152  // when the domain, language, visual format, etc. become known.
1153  int i = kls.d->arguments.size();
1154  kls.d->klsArguments[i] = a;
1155  kls.d->klsArgumentFieldWidths[i] = fieldWidth;
1156  kls.d->klsArgumentFillChars[i] = fillChar;
1157  kls.d->arguments.append(QString());
1158  kls.d->values.append(0);
1159  return kls;
1160 }
1161 
1163 {
1164  KLocalizedString kls(*this);
1165  kls.d->dynamicContext[key] = value;
1166  return kls;
1167 }
1168 
1170 {
1171  KLocalizedString kls(*this);
1172  kls.d->relaxedSubs = true;
1173  return kls;
1174 }
1175 
1177 {
1178  KLocalizedString kls(*this);
1179  kls.d->markupAware = false;
1180  return kls;
1181 }
1182 
1184 {
1185  return d->text;
1186 }
1187 
1189 {
1190  KLocalizedStringPrivateStatics *s = staticsKLSP();
1191 
1192  QMutexLocker lock(&s->klspMutex);
1193 
1194  s->applicationDomain = domain;
1195 }
1196 
1198 {
1199  KLocalizedStringPrivateStatics *s = staticsKLSP();
1200 
1201  return s->applicationDomain;
1202 }
1203 
1205 {
1206  KLocalizedStringPrivateStatics *s = staticsKLSP();
1207 
1208  return s->languages;
1209 }
1210 
1212 {
1213  KLocalizedStringPrivateStatics *s = staticsKLSP();
1214 
1215  QMutexLocker lock(&s->klspMutex);
1216 
1217  s->languages = languages;
1218 }
1219 
1221 {
1222  KLocalizedStringPrivateStatics *s = staticsKLSP();
1223 
1224  QMutexLocker lock(&s->klspMutex);
1225 
1226  s->languages = s->localeLanguages;
1227 }
1228 
1230 {
1231  KLocalizedStringPrivateStatics *s = staticsKLSP();
1232 
1233  return language == s->codeLanguage || !KCatalog::catalogLocaleDir(s->applicationDomain, language).isEmpty();
1234 }
1235 
1237 {
1238  return availableDomainTranslations(staticsKLSP()->applicationDomain);
1239 }
1240 
1242 {
1243  QSet<QString> availableLanguages;
1244 
1245  if (!domain.isEmpty()) {
1246  availableLanguages = KCatalog::availableCatalogLanguages(domain);
1247  availableLanguages.insert(staticsKLSP()->codeLanguage);
1248  }
1249 
1250  return availableLanguages;
1251 }
1252 
1253 const KCatalog &KLocalizedStringPrivate::getCatalog(const QByteArray &domain, const QString &language)
1254 {
1255  KLocalizedStringPrivateStatics *s = staticsKLSP();
1256 
1257  QMutexLocker lock(&s->klspMutex);
1258 
1259  QHash<QByteArray, KCatalogPtrHash>::iterator languageCatalogs = s->catalogs.find(domain);
1260  if (languageCatalogs == s->catalogs.end()) {
1261  languageCatalogs = s->catalogs.insert(domain, KCatalogPtrHash());
1262  }
1263  KCatalogPtrHash::iterator catalog = languageCatalogs->find(language);
1264  if (catalog == languageCatalogs->end()) {
1265  catalog = languageCatalogs->insert(language, new KCatalog(domain, language));
1266  locateScriptingModule(domain, language);
1267  }
1268  return **catalog;
1269 }
1270 
1271 void KLocalizedStringPrivate::locateScriptingModule(const QByteArray &domain, const QString &language)
1272 {
1273  KLocalizedStringPrivateStatics *s = staticsKLSP();
1274 
1275  QMutexLocker lock(&s->klspMutex);
1276 
1277  QString modapath =
1278  QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("locale/%1/%2/%3/%3.js").arg(language, s->scriptDir, QLatin1String{domain}));
1279 
1280  // If the module exists and hasn't been already included.
1281  if (!modapath.isEmpty() && !s->scriptModules[language].contains(domain)) {
1282  // Indicate that the module has been considered.
1283  s->scriptModules[language].append(domain);
1284 
1285  // Store the absolute path and language of the module,
1286  // to load on next script evaluation.
1287  QStringList module;
1288  module.append(modapath);
1289  module.append(language);
1290  s->scriptModulesToLoad.append(module);
1291  }
1292 }
1293 
1294 extern "C" {
1295 typedef KTranscript *(*InitFunc)();
1296 }
1297 
1298 void KLocalizedStringPrivate::loadTranscript()
1299 {
1300  KLocalizedStringPrivateStatics *s = staticsKLSP();
1301 
1302  QMutexLocker lock(&s->klspMutex);
1303 
1304  s->loadTranscriptCalled = true;
1305  s->ktrs = nullptr; // null indicates that Transcript is not available
1306 
1307  // QPluginLoader is just used to find the plugin
1308  QPluginLoader loader(QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/ktranscript"));
1309  if (loader.fileName().isEmpty()) {
1310  qCWarning(KI18N) << "Cannot find Transcript plugin.";
1311  return;
1312  }
1313 
1314  QLibrary lib(loader.fileName());
1315  if (!lib.load()) {
1316  qCWarning(KI18N) << "Cannot load Transcript plugin:" << lib.errorString();
1317  return;
1318  }
1319 
1320  InitFunc initf = (InitFunc)lib.resolve("load_transcript");
1321  if (!initf) {
1322  lib.unload();
1323  qCWarning(KI18N) << "Cannot find function load_transcript in Transcript plugin.";
1324  return;
1325  }
1326 
1327  s->ktrs = initf();
1328 }
1329 
1331 {
1332  KLocalizedStringPrivateStatics *s = staticsKLSP();
1333 
1334  // Check if l10n subdirectory is present, stop if not.
1335  QFileInfo fileInfo(filePath);
1336  QString locDirPath = fileInfo.path() + QLatin1Char('/') + QLatin1String("l10n");
1337  QFileInfo locDirInfo(locDirPath);
1338  if (!locDirInfo.isDir()) {
1339  return filePath;
1340  }
1341 
1342  // Go through possible localized paths by priority of languages,
1343  // return first that exists.
1344  QString fileName = fileInfo.fileName();
1345  for (const QString &lang : std::as_const(s->languages)) {
1346  QString locFilePath = locDirPath + QLatin1Char('/') + lang + QLatin1Char('/') + fileName;
1347  QFileInfo locFileInfo(locFilePath);
1348  if (locFileInfo.isFile() && locFileInfo.isReadable()) {
1349  return locFilePath;
1350  }
1351  }
1352 
1353  return filePath;
1354 }
1355 
1357 {
1358  return ::removeAcceleratorMarker(label);
1359 }
1360 
1361 #if KI18N_BUILD_DEPRECATED_SINCE(5, 0)
1362 QString KLocalizedString::translateQt(const char *context, const char *sourceText, const char *comment, int n)
1363 {
1364  // NOTE: Qt message semantics.
1365  //
1366  // Qt's context is normally the name of the class of the method which makes
1367  // the tr(sourceText) call. However, it can also be manually supplied via
1368  // translate(context, sourceText) call.
1369  //
1370  // Qt's sourceText is the actual message displayed to the user.
1371  //
1372  // Qt's comment is an optional argument of tr() and translate(), like
1373  // tr(sourceText, comment) and translate(context, sourceText, comment).
1374  //
1375  // We handle this in the following way:
1376  //
1377  // If the comment is given, then it is considered gettext's msgctxt, so a
1378  // context call is made.
1379  //
1380  // If the comment is not given, but context is given, then we treat it as
1381  // msgctxt only if it was manually supplied (the one in translate()) -- but
1382  // we don't know this, so we first try a context call, and if translation
1383  // is not found, we fallback to ordinary call.
1384  //
1385  // If neither comment nor context are given, it's just an ordinary call
1386  // on sourceText.
1387 
1388  Q_UNUSED(n);
1389 
1390  KLocalizedStringPrivateStatics *s = staticsKLSP();
1391 
1392  QMutexLocker lock(&s->klspMutex);
1393 
1394  if (!sourceText || !sourceText[0]) {
1395  qCWarning(KI18N) << "KLocalizedString::translateQt: "
1396  "Trying to look up translation of \"\", fix the code.";
1397  return QString();
1398  }
1399 
1400  // NOTE: Condition (language != s->codeLanguage) means that translation
1401  // was found, otherwise the original text was returned as translation.
1402  QString translation;
1403  QString language;
1404  for (const QByteArray &domain : std::as_const(s->qtDomains)) {
1405  if (comment && comment[0]) {
1406  // Comment given, go for context call.
1407  KLocalizedStringPrivate::translateRaw(domain, s->languages, comment, sourceText, nullptr, 0, language, translation);
1408  } else {
1409  // Comment not given, go for try-fallback with context.
1410  if (context && context[0]) {
1411  KLocalizedStringPrivate::translateRaw(domain, s->languages, context, sourceText, nullptr, 0, language, translation);
1412  }
1413  if (language.isEmpty() || language == s->codeLanguage) {
1414  KLocalizedStringPrivate::translateRaw(domain, s->languages, nullptr, sourceText, nullptr, 0, language, translation);
1415  }
1416  }
1417  if (language != s->codeLanguage) {
1418  return translation;
1419  }
1420  }
1421  // No proper translation found, return empty according to Qt semantics.
1422  return QString();
1423 }
1424 #endif
1425 
1426 #if KI18N_BUILD_DEPRECATED_SINCE(5, 0)
1427 void KLocalizedString::insertQtDomain(const char *domain)
1428 {
1429  KLocalizedStringPrivateStatics *s = staticsKLSP();
1430 
1431  QMutexLocker lock(&s->klspMutex);
1432 
1433  int pos = s->qtDomains.indexOf(domain);
1434  if (pos < 0) {
1435  // Domain priority is undefined, but to minimize damage
1436  // due to message conflicts, put later inserted catalogs at front.
1437  s->qtDomains.prepend(domain);
1438  s->qtDomainInsertCount.prepend(1);
1439  } else {
1440  ++s->qtDomainInsertCount[pos];
1441  }
1442 }
1443 #endif
1444 
1445 #if KI18N_BUILD_DEPRECATED_SINCE(5, 0)
1446 void KLocalizedString::removeQtDomain(const char *domain)
1447 {
1448  KLocalizedStringPrivateStatics *s = staticsKLSP();
1449 
1450  QMutexLocker lock(&s->klspMutex);
1451 
1452  int pos = s->qtDomains.indexOf(domain);
1453  if (pos >= 0 && --s->qtDomainInsertCount[pos] == 0) {
1454  s->qtDomains.removeAt(pos);
1455  s->qtDomainInsertCount.removeAt(pos);
1456  }
1457 }
1458 #endif
1459 
1461 {
1462  KCatalog::addDomainLocaleDir(domain, path);
1463 }
1464 
1465 KLocalizedString ki18n(const char *text)
1466 {
1467  return KLocalizedString(nullptr, nullptr, text, nullptr, false);
1468 }
1469 
1470 KLocalizedString ki18nc(const char *context, const char *text)
1471 {
1472  return KLocalizedString(nullptr, context, text, nullptr, false);
1473 }
1474 
1475 KLocalizedString ki18np(const char *singular, const char *plural)
1476 {
1477  return KLocalizedString(nullptr, nullptr, singular, plural, false);
1478 }
1479 
1480 KLocalizedString ki18ncp(const char *context, const char *singular, const char *plural)
1481 {
1482  return KLocalizedString(nullptr, context, singular, plural, false);
1483 }
1484 
1485 KLocalizedString ki18nd(const char *domain, const char *text)
1486 {
1487  return KLocalizedString(domain, nullptr, text, nullptr, false);
1488 }
1489 
1490 KLocalizedString ki18ndc(const char *domain, const char *context, const char *text)
1491 {
1492  return KLocalizedString(domain, context, text, nullptr, false);
1493 }
1494 
1495 KLocalizedString ki18ndp(const char *domain, const char *singular, const char *plural)
1496 {
1497  return KLocalizedString(domain, nullptr, singular, plural, false);
1498 }
1499 
1500 KLocalizedString ki18ndcp(const char *domain, const char *context, const char *singular, const char *plural)
1501 {
1502  return KLocalizedString(domain, context, singular, plural, false);
1503 }
1504 
1505 KLocalizedString kxi18n(const char *text)
1506 {
1507  return KLocalizedString(nullptr, nullptr, text, nullptr, true);
1508 }
1509 
1510 KLocalizedString kxi18nc(const char *context, const char *text)
1511 {
1512  return KLocalizedString(nullptr, context, text, nullptr, true);
1513 }
1514 
1515 KLocalizedString kxi18np(const char *singular, const char *plural)
1516 {
1517  return KLocalizedString(nullptr, nullptr, singular, plural, true);
1518 }
1519 
1520 KLocalizedString kxi18ncp(const char *context, const char *singular, const char *plural)
1521 {
1522  return KLocalizedString(nullptr, context, singular, plural, true);
1523 }
1524 
1525 KLocalizedString kxi18nd(const char *domain, const char *text)
1526 {
1527  return KLocalizedString(domain, nullptr, text, nullptr, true);
1528 }
1529 
1530 KLocalizedString kxi18ndc(const char *domain, const char *context, const char *text)
1531 {
1532  return KLocalizedString(domain, context, text, nullptr, true);
1533 }
1534 
1535 KLocalizedString kxi18ndp(const char *domain, const char *singular, const char *plural)
1536 {
1537  return KLocalizedString(domain, nullptr, singular, plural, true);
1538 }
1539 
1540 KLocalizedString kxi18ndcp(const char *domain, const char *context, const char *singular, const char *plural)
1541 {
1542  return KLocalizedString(domain, context, singular, plural, true);
1543 }
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: kuitmarkup.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: kuitmarkup.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-2022 The KDE developers.
Generated on Tue Aug 16 2022 04:07:13 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.