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 | */ |
50 | class KLazyLocalizedString |
51 | { |
52 | public: |
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 () const |
405 | { |
406 | return this->operator KLocalizedString().ignoreMarkup(); |
407 | } |
408 | |
409 | private: |
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 | */ |
450 | template<std::size_t TextSize> |
451 | constexpr 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 | */ |
464 | template<std::size_t ContextSize, std::size_t TextSize> |
465 | constexpr 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 | */ |
478 | template<std::size_t SingularSize, std::size_t PluralSize> |
479 | constexpr 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 | */ |
493 | template<std::size_t ContextSize, std::size_t SingularSize, std::size_t PluralSize> |
494 | constexpr 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 | */ |
506 | template<std::size_t TextSize> |
507 | constexpr 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 | */ |
520 | template<std::size_t ContextSize, std::size_t TextSize> |
521 | constexpr 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 | */ |
534 | template<std::size_t SingularSize, std::size_t PluralSize> |
535 | constexpr 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 | */ |
549 | template<std::size_t ContextSize, std::size_t SingularSize, std::size_t PluralSize> |
550 | constexpr 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 | |