1/*
2 SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#ifndef KLAZYLOCALIZEDSTRING_H
7#define KLAZYLOCALIZEDSTRING_H
8
9#include "klocalizedstring.h"
10
11#include <cstddef>
12
13/*!
14 * \class KLazyLocalizedString
15 * \inmodule KI18n
16 *
17 * \brief Lazy-initialized variant of KLocalizedString.
18 *
19 * This is a safer replacement for the I18N_NOOP set of macros and allows
20 * marking strings for extraction without runtime-initializing KLocalizedString instances,
21 * for storing in static data tables.
22 *
23 * Instances of KLazyLocalizedString are not created directly, unless they should be empty.
24 * Instead they have to be obtained via the kli18n* functions (similar to KLocalizedString
25 * and the ki18n* functions).
26 *
27 * Example usage in a static message table:
28 * \code
29 * struct {
30 * MyClass::VehicleType type;
31 * KLazyLocalizedString msg;
32 * } static constexpr const vehicle_msg_table[] = {
33 * { MyClass::Train, kli18np("%1 train", "%1 trains") },
34 * { MyClass::Bus, kli18ncp("the vehicle, not the USB one", "%1 bus", "%1 buses") },
35 * ...
36 * };
37 *
38 * ...
39 *
40 * const auto it = std::find_if(std::begin(vehicle_msg_table), std::end(vehicle_msg_table), [vehicleType](const auto &m) { return m.type == vehicleType; });
41 * QString translatedMessage = (*it).msg.subs(vehicleCount).toString();
42 * \endcode
43 *
44 * \note KLazyLocalizedString is primarily meant for storage in e.g. message tables,
45 * not for use in API, especially not across translation domains. This is due to
46 * it not carrying the translation domain in which it was created, but using the
47 * active translation domain at the point of converting to a KLocalizedString.
48 *
49 * \since 5.89
50 */
51class KLazyLocalizedString
52{
53public:
54 /*!
55 * Construct an empty message.
56 *
57 * Direct construction is used when e.g. a KLazyLocalizedString field in
58 * a data table needs to be set, but there is not message to be used.
59 * Directly constructed instances are not valid for
60 * finalization by toString methods.
61 *
62 * \sa isEmpty
63 */
64 constexpr inline KLazyLocalizedString() = default;
65
66 /*! Convert to a KLocalizedString to actually perform the translation and obtain a concrete
67 * localized string.
68 *
69 * The translation domain active at this point will be used. This means this has to be
70 * called in the same translation domain the corresponding kli18n call is in.
71 */
72 Q_REQUIRED_RESULT inline operator KLocalizedString() const
73 {
74 if (!m_text) {
75 return KLocalizedString();
76 }
77#ifdef TRANSLATION_DOMAIN
78 return KLocalizedString(TRANSLATION_DOMAIN, m_context, m_text, m_plural, m_markupAware);
79#else
80 return KLocalizedString(nullptr, m_context, m_text, m_plural, m_markupAware);
81#endif
82 }
83
84 /*!
85 * Check whether the message is empty.
86 *
87 * The message is considered empty if the object was constructed
88 * via the default constructor.
89 *
90 * Empty messages are not valid for finalization.
91 * The behavior of calling toString() on them is undefined.
92 * In debug mode, an error mark may appear in the returned string.
93 *
94 * Returns \c true if the message is empty, \c false otherwise
95 */
96 Q_REQUIRED_RESULT constexpr inline bool isEmpty() const
97 {
98 return (m_text == nullptr) || (m_text[0] == '\0');
99 }
100
101 /*! Returns the raw untranslated text as passed to \a kli18n*. */
102 Q_REQUIRED_RESULT constexpr inline const char *untranslatedText() const
103 {
104 return m_text;
105 }
106
107 /*!
108 * Finalize the translation.
109 *
110 * Creates translated QString. If the string has placeholders,
111 * make sure to call instead KLazyLocalizedString::subs and
112 * further KLocalizedString::subs methods before finalizing.
113 * Translated text is searched for based on the global locale.
114 *
115 * Returns finalized translation
116 */
117 Q_REQUIRED_RESULT inline QString toString() const
118 {
119 return this->operator KLocalizedString().toString();
120 }
121
122 /*!
123 * Like toString(), but look for translation only in given languages.
124 *
125 * Given languages override languages defined by the global locale.
126 * If \a languages is empty, original message is returned.
127 *
128 * \a languages list of language codes (by decreasing priority)
129 * Returns finalized translation
130 */
131 Q_REQUIRED_RESULT inline QString toString(const QStringList &languages) const
132 {
133 return this->operator KLocalizedString().toString(languages);
134 }
135
136 /*!
137 * Like toString(), but look for translation in the given domain.
138 *
139 * \a domain the translation domain
140 *
141 * Returns finalized translation
142 */
143 Q_REQUIRED_RESULT inline QString toString(const char *domain) const
144 {
145 return this->operator KLocalizedString().toString(domain);
146 }
147
148 /*!
149 * Like toString(), but resolve KUIT markup into given visual format.
150 *
151 * Given visual format overrides that implied by the context UI marker.
152 * If the message is not markup-aware, this is same as toString().
153 *
154 * \a format the target visual format
155 *
156 * Returns finalized translation
157 */
158 Q_REQUIRED_RESULT inline QString toString(Kuit::VisualFormat format) const
159 {
160 return this->operator KLocalizedString().toString(format);
161 }
162
163 /*!
164 * Indicate to look for translation only in given languages.
165 *
166 * \a languages list of language codes (by decreasing priority)
167 *
168 * Returns updated KLocalizedString
169 */
170 Q_REQUIRED_RESULT inline KLocalizedString withLanguages(const QStringList &languages) const
171 {
172 return this->operator KLocalizedString().withLanguages(languages);
173 }
174
175 /*!
176 * Indicate to look for translation in the given domain.
177 *
178 * \a domain the translation domain
179 *
180 * Returns updated KLocalizedString
181 */
182 Q_REQUIRED_RESULT inline KLocalizedString withDomain(const char *domain) const
183 {
184 return this->operator KLocalizedString().withDomain(domain);
185 }
186
187 /*!
188 * Indicate to resolve KUIT markup into given visual format.
189 *
190 * If the message is not markup-aware, this has no effect.
191 *
192 * \a format the target visual format
193 *
194 * Returns updated KLocalizedString
195 */
196 Q_REQUIRED_RESULT inline KLocalizedString withFormat(Kuit::VisualFormat format) const
197 {
198 return this->operator KLocalizedString().withFormat(format);
199 }
200
201 /*!
202 * Substitute an int argument into the message.
203 *
204 * \a a the argument
205 *
206 * \a fieldWidth width of the formatted field, padded by spaces.
207 * Positive value aligns right, negative aligns left
208 *
209 * \a base the radix used to represent the number as a string.
210 * Valid values range from 2 to 36
211 *
212 * \a fillChar the character used to fill up the empty places when
213 * field width is greater than argument width
214 *
215 * Returns updated KLocalizedString
216 */
217 Q_REQUIRED_RESULT inline KLocalizedString subs(int a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
218 {
219 return this->operator KLocalizedString().subs(a, fieldWidth, base, fillChar);
220 }
221
222 /*!
223 * Substitute an unsigned int argument into the message.
224 *
225 * \a a the argument
226 *
227 * \a fieldWidth width of the formatted field, padded by spaces.
228 * Positive value aligns right, negative aligns left
229 *
230 * \a base the radix used to represent the number as a string.
231 * Valid values range from 2 to 36
232 *
233 * \a fillChar the character used to fill up the empty places when
234 * field width is greater than argument width
235 *
236 * Returns updated KLocalizedString
237 */
238 Q_REQUIRED_RESULT inline KLocalizedString subs(uint a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
239 {
240 return this->operator KLocalizedString().subs(a, fieldWidth, base, fillChar);
241 }
242
243 /*!
244 * Substitute a long argument into the message.
245 *
246 * \a a the argument
247 *
248 * \a fieldWidth width of the formatted field, padded by spaces.
249 * Positive value aligns right, negative aligns left
250 *
251 * \a base the radix used to represent the number as a string.
252 * Valid values range from 2 to 36
253 *
254 * \a fillChar the character used to fill up the empty places when
255 * field width is greater than argument width
256 *
257 * Returns updated KLocalizedString
258 */
259 Q_REQUIRED_RESULT inline KLocalizedString subs(long a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
260 {
261 return this->operator KLocalizedString().subs(a, fieldWidth, base, fillChar);
262 }
263
264 /*!
265 * Substitute an unsigned long argument into the message.
266 *
267 * \a a the argument
268 *
269 * \a fieldWidth width of the formatted field, padded by spaces.
270 * Positive value aligns right, negative aligns left
271 *
272 * \a base the radix used to represent the number as a string.
273 * Valid values range from 2 to 36
274 *
275 * \a fillChar the character used to fill up the empty places when
276 * field width is greater than argument width
277 *
278 * Returns updated KLocalizedString
279 */
280 Q_REQUIRED_RESULT inline KLocalizedString subs(ulong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
281 {
282 return this->operator KLocalizedString().subs(a, fieldWidth, base, fillChar);
283 }
284
285 /*!
286 * Substitute a long long argument into the message.
287 *
288 * \a a the argument
289 *
290 * \a fieldWidth width of the formatted field, padded by spaces.
291 * Positive value aligns right, negative aligns left
292 *
293 * \a base the radix used to represent the number as a string.
294 * Valid values range from 2 to 36
295 *
296 * \a fillChar the character used to fill up the empty places when
297 * field width is greater than argument width
298 *
299 * Returns updated KLocalizedString
300 */
301 Q_REQUIRED_RESULT inline KLocalizedString subs(qlonglong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
302 {
303 return this->operator KLocalizedString().subs(a, fieldWidth, base, fillChar);
304 }
305
306 /*!
307 * Substitute an unsigned long long argument into the message.
308 *
309 * \a a the argument
310 *
311 * \a fieldWidth width of the formatted field, padded by spaces.
312 * Positive value aligns right, negative aligns left
313 *
314 * \a base the radix used to represent the number as a string.
315 * Valid values range from 2 to 36
316 *
317 * \a fillChar the character used to fill up the empty places when
318 * field width is greater than argument width
319 *
320 * Returns updated KLocalizedString
321 */
322 Q_REQUIRED_RESULT inline KLocalizedString subs(qulonglong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
323 {
324 return this->operator KLocalizedString().subs(a, fieldWidth, base, fillChar);
325 }
326 /*!
327 * Substitute a double argument into the message.
328 *
329 * \a a the argument
330 *
331 * \a fieldWidth width of the formatted field, padded by spaces.
332 * Positive value aligns right, negative aligns left
333 *
334 * \a format type of floating point formatting, like in QString::arg
335 *
336 * \a precision number of digits after the decimal separator
337 *
338 * \a fillChar the character used to fill up the empty places when
339 * field width is greater than argument width
340 *
341 * Returns updated KLocalizedString
342 */
343 Q_REQUIRED_RESULT inline KLocalizedString subs(double a, int fieldWidth = 0, char format = 'g', int precision = -1, QChar fillChar = QLatin1Char(' ')) const
344 {
345 return this->operator KLocalizedString().subs(a, fieldWidth, format, precision, fillChar);
346 }
347
348 /*!
349 * Substitute a QChar argument into the message.
350 *
351 * \a a the argument
352 *
353 * \a fieldWidth width of the formatted field, padded by spaces.
354 * Positive value aligns right, negative aligns left
355 *
356 * \a fillChar the character used to fill up the empty places when
357 * field width is greater than argument width
358 *
359 * Returns updated KLocalizedString
360 */
361 Q_REQUIRED_RESULT inline KLocalizedString subs(QChar a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const
362 {
363 return this->operator KLocalizedString().subs(a, fieldWidth, fillChar);
364 }
365
366 /*!
367 * Substitute a QString argument into the message.
368 *
369 * \a a the argument
370 *
371 * \a fieldWidth width of the formatted field, padded by spaces.
372 * Positive value aligns right, negative aligns left
373 *
374 * \a fillChar the character used to fill up the empty places when
375 * field width is greater than argument width
376 *
377 * Returns updated KLocalizedString
378 */
379 Q_REQUIRED_RESULT inline KLocalizedString subs(const QString &a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const
380 {
381 return this->operator KLocalizedString().subs(a, fieldWidth, fillChar);
382 }
383
384 /*!
385 * Substitute another KLocalizedString into the message.
386 *
387 * \a a the argument
388 *
389 * \a fieldWidth width of the formatted field, padded by spaces.
390 * Positive value aligns right, negative aligns left
391 *
392 * \a fillChar the character used to fill up the empty places when
393 * field width is greater than argument width
394 *
395 * Returns updated KLocalizedString
396 */
397 Q_REQUIRED_RESULT inline KLocalizedString subs(const KLocalizedString &a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const
398 {
399 return this->operator KLocalizedString().subs(a, fieldWidth, fillChar);
400 }
401
402 /*!
403 * Add dynamic context to the message.
404 *
405 * See dyn_ctxt for use cases.
406 *
407 * \a key context key
408 *
409 * \a value context value
410 *
411 * Returns updated KLocalizedString
412 */
413 Q_REQUIRED_RESULT inline KLocalizedString inContext(const QString &key, const QString &value) const
414 {
415 return this->operator KLocalizedString().inContext(key, value);
416 }
417
418 /*!
419 * Relax matching between placeholders and arguments.
420 *
421 * Normally the placeholders should start from %1 and have no gaps,
422 * and on finalization there must be exactly as many arguments
423 * supplied through subs methods as there are unique plaecholders.
424 * If this is not satisfied, in debug mode warnings are printed
425 * and the finalized string may contain error marks.
426 *
427 * This method relaxes the placeholder-argument matching,
428 * such that there must only be an argument available for
429 * every present unique placeholder (taking placeholder numbers
430 * to be 1-based indices into the argument list).
431 * This can come useful in some situations.
432 *
433 * Returns updated KLocalizedString
434 */
435 Q_REQUIRED_RESULT inline KLocalizedString relaxSubs() const
436 {
437 return this->operator KLocalizedString().relaxSubs();
438 }
439
440 /*!
441 * Do not resolve KUIT markup.
442 *
443 * If the message is markup-aware
444 * (constructed by one of xi18n calls),
445 * this function can be used to make it non-markup-aware.
446 * This may be useful for debugging markup.
447 *
448 * Returns updated KLocalizedString
449 */
450 Q_REQUIRED_RESULT inline KLocalizedString ignoreMarkup() const
451 {
452 return this->operator KLocalizedString().ignoreMarkup();
453 }
454
455private:
456 template<std::size_t TextSize>
457 friend inline constexpr KLazyLocalizedString kli18n(const char (&text)[TextSize]);
458 template<std::size_t ContextSize, std::size_t TextSize>
459 friend constexpr inline KLazyLocalizedString kli18nc(const char (&context)[ContextSize], const char (&text)[TextSize]);
460 template<std::size_t SingularSize, std::size_t PluralSize>
461 friend constexpr inline KLazyLocalizedString kli18np(const char (&singular)[SingularSize], const char (&plural)[PluralSize]);
462 template<std::size_t ContextSize, std::size_t SingularSize, std::size_t PluralSize>
463 friend constexpr inline KLazyLocalizedString
464 kli18ncp(const char (&context)[ContextSize], const char (&singular)[SingularSize], const char (&plural)[PluralSize]);
465 template<std::size_t TextSize>
466 friend constexpr inline KLazyLocalizedString klxi18n(const char (&text)[TextSize]);
467 template<std::size_t ContextSize, std::size_t TextSize>
468 friend constexpr inline KLazyLocalizedString klxi18nc(const char (&context)[ContextSize], const char (&text)[TextSize]);
469 template<std::size_t SingularSize, std::size_t PluralSize>
470 friend constexpr inline KLazyLocalizedString klxi18np(const char (&singular)[SingularSize], const char (&plural)[PluralSize]);
471 template<std::size_t ContextSize, std::size_t SingularSize, std::size_t PluralSize>
472 friend constexpr inline KLazyLocalizedString
473 klxi18ncp(const char (&context)[ContextSize], const char (&singular)[SingularSize], const char (&plural)[PluralSize]);
474
475 constexpr inline KLazyLocalizedString(const char *context, const char *text, const char *plural, bool markupAware)
476 : m_context(context)
477 , m_text(text)
478 , m_plural(plural)
479 , m_markupAware(markupAware)
480 {
481 }
482
483 const char *m_context = nullptr;
484 const char *m_text = nullptr;
485 const char *m_plural = nullptr;
486 bool m_markupAware = false;
487};
488
489/*!
490 * Mark the string \a text for extraction.
491 *
492 * \a text string to translate
493 *
494 * Returns KLazyLocalizedString for deferred translation.
495 *
496 * \relates KLazyLocalizedString
497 * \since 5.89
498 */
499#ifdef Q_QDOC
500KLazyLocalizedString kli18n(const char *text)
501#else
502template<std::size_t TextSize>
503constexpr inline KLazyLocalizedString kli18n(const char (&text)[TextSize])
504#endif
505{
506 return KLazyLocalizedString(nullptr, text, nullptr, false);
507}
508
509/*!
510 * Mark the string \a text with \a context for extraction.
511 *
512 * \a context context of the string
513 *
514 * \a text string to translate
515 *
516 * Returns KLazyLocalizedString for deferred translation.
517 *
518 * \relates KLazyLocalizedString
519 * \since 5.89
520 */
521#ifdef Q_QDOC
522KLazyLocalizedString kli18nc(const char *context, const char *text)
523#else
524template<std::size_t ContextSize, std::size_t TextSize>
525constexpr inline KLazyLocalizedString kli18nc(const char (&context)[ContextSize], const char (&text)[TextSize])
526#endif
527{
528 return KLazyLocalizedString(context, text, nullptr, false);
529}
530
531/*!
532 * Mark the string \a singular and \a plural for extraction.
533 *
534 * \a singular singular form of the string to translate
535 *
536 * \a plural plural form of the string to translate
537 *
538 * Returns KLazyLocalizedString for deferred translation.
539 *
540 * \relates KLazyLocalizedString
541 * \since 5.89
542 */
543#ifdef Q_QDOC
544KLazyLocalizedString kli18np(const char *singular, const char *plural)
545#else
546template<std::size_t SingularSize, std::size_t PluralSize>
547constexpr inline KLazyLocalizedString kli18np(const char (&singular)[SingularSize], const char (&plural)[PluralSize])
548#endif
549{
550 return KLazyLocalizedString(nullptr, singular, plural, false);
551}
552
553/*!
554 * Mark the string \a singular and \a plural with \a context for extraction.
555 *
556 * \a context context of the string
557 *
558 * \a singular singular form of the string to translate
559 *
560 * \a plural plural form of the string to translate
561 *
562 * Returns KLazyLocalizedString for deferred translation.
563 *
564 * \relates KLazyLocalizedString
565 * \since 5.89
566 */
567#ifdef Q_QDOC
568KLazyLocalizedString kli18ncp(const char *context, const char *singular, const char *plural)
569#else
570template<std::size_t ContextSize, std::size_t SingularSize, std::size_t PluralSize>
571constexpr inline KLazyLocalizedString kli18ncp(const char (&context)[ContextSize], const char (&singular)[SingularSize], const char (&plural)[PluralSize])
572#endif
573{
574 return KLazyLocalizedString(context, singular, plural, false);
575}
576
577/*!
578 * Mark the markup-aware string \a text for extraction.
579 *
580 * \a text string to translate
581 *
582 * Returns KLazyLocalizedString for deferred translation.
583 *
584 * \relates KLazyLocalizedString
585 * \since 5.89
586 */
587#ifdef Q_QDOC
588KLazyLocalizedString klxi18n(const char *text)
589#else
590template<std::size_t TextSize>
591constexpr inline KLazyLocalizedString klxi18n(const char (&text)[TextSize])
592#endif
593{
594 return KLazyLocalizedString(nullptr, text, nullptr, true);
595}
596
597/*!
598 * Mark the markup-aware string \a text with \a context for extraction.
599 *
600 * \a context context of the string
601 *
602 * \a text string to translate
603 *
604 * Returns KLazyLocalizedString for deferred translation.
605 *
606 * \relates KLazyLocalizedString
607 * \since 5.89
608 */
609#ifdef Q_QDOC
610KLazyLocalizedString klxi18nc(const char *context, const char *text)
611#else
612template<std::size_t ContextSize, std::size_t TextSize>
613constexpr inline KLazyLocalizedString klxi18nc(const char (&context)[ContextSize], const char (&text)[TextSize])
614#endif
615{
616 return KLazyLocalizedString(context, text, nullptr, true);
617}
618
619/*!
620 * Mark the markup-aware string \a singular and \a plural for extraction.
621 *
622 * \a singular singular form of the string to translate
623 *
624 * \a plural plural form of the string to translate
625 *
626 * Returns KLazyLocalizedString for deferred translation.
627 *
628 * \relates KLazyLocalizedString
629 * \since 5.89
630 */
631#ifdef Q_QDOC
632KLazyLocalizedString klxi18np(const char *singular, const char *plural)
633#else
634template<std::size_t SingularSize, std::size_t PluralSize>
635constexpr inline KLazyLocalizedString klxi18np(const char (&singular)[SingularSize], const char (&plural)[PluralSize])
636#endif
637{
638 return KLazyLocalizedString(nullptr, singular, plural, true);
639}
640
641/*!
642 * Mark the markup-aware string \a singular and \a plural with \a context for extraction.
643 *
644 * \a context context of the string
645 *
646 * \a singular singular form of the string to translate
647 *
648 * \a plural plural form of the string to translate
649 *
650 * Returns KLazyLocalizedString for deferred translation.
651 *
652 * \relates KLazyLocalizedString
653 * \since 5.89
654 */
655#ifdef Q_QDOC
656KLazyLocalizedString klxi18ncp(const char *context, const char *singular, const char *plural)
657#else
658template<std::size_t ContextSize, std::size_t SingularSize, std::size_t PluralSize>
659constexpr inline KLazyLocalizedString klxi18ncp(const char (&context)[ContextSize], const char (&singular)[SingularSize], const char (&plural)[PluralSize])
660#endif
661{
662 return KLazyLocalizedString(context, singular, plural, true);
663}
664
665#endif // KLAZYLOCALIZEDSTRING_H
666

source code of ki18n/src/i18n/klazylocalizedstring.h