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

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