1/*
2 SPDX-FileCopyrightText: 2024 Lukas Sommer <sommerluk@gmail.com>
3 SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#ifndef KLOCALIZATION_H
8#define KLOCALIZATION_H
9
10#include "klocalizedstring.h"
11
12#include <QObject>
13#include <QVariant>
14
15#include <type_traits>
16
17class QDoubleSpinBox;
18class QSpinBox;
19
20/*!
21 * \namespace KLocalization
22 * \inmodule KI18n
23 * \brief Namespace containing helpers for localization.
24 * \since 6.5
25 */
26namespace KLocalization
27{
28
29namespace Private
30{
31
32constexpr inline const char SpinBoxFormatStringProperty[] = "__KLocalizationFormatStringPrivate";
33
34}
35
36/*!
37 * Retranslates a previously set up format string to the current
38 * language and updates the spin box.
39 *
40 * The format string is initially set up by setupSpinBoxFormatString().
41 * This function updates the prefix and suffix of a spin box to reflect the
42 * current language settings. It is useful for responding to language changes,
43 * such as those triggered by QEvent::LanguageChange.
44 *
45 * \a T The type of the spin box, which must be either QSpinBox or
46 * QDoubleSpinBox.
47 *
48 * \a spinBox Pointer to the spin box.
49 *
50 * The prefix and suffix of the spin box are updated to reflect the
51 * current language.
52 *
53 * \sa setupSpinBoxFormatString
54 *
55 * \since 6.5
56 */
57template<typename T>
58inline void retranslateSpinBoxFormatString(T *spinBox)
59{
60 constexpr bool isSpinBox = std::is_base_of_v<QSpinBox, T> || std::is_base_of_v<QDoubleSpinBox, T>;
61 static_assert(isSpinBox, "First argument must be a QSpinBox or QDoubleSpinBox.");
62
63 const auto lString = spinBox->property(Private::SpinBoxFormatStringProperty).template value<KLocalizedString>();
64 // The KLocalizedString::subs() method performs two tasks:
65 // 1. It replaces placeholders (%1, %2, ...) in the string with actual
66 // content.
67 // 2. If the argument is an integer, it selects the appropriate plural form
68 // based on the value.
69 // In this context, the string is expected not to contain any standard
70 // placeholders (%1, %2, ...). Instead, it should contain a custom
71 // placeholder (%v) which is ignored by KLocalizedString::subs().
72 // The only purpose of calling KLocalizedString::subs() here is to ensure
73 // the correct plural form is used when spinBox->value() is an integer.
74 // If spinBox->value() is a double, KLocalizedString::subs() does not
75 // perform any operations on the string since plural handling applies only
76 // to integer values.
77 const auto translation = lString.subs(spinBox->value()).toString();
78 const auto parts = translation.split(QLatin1StringView("%v"));
79 if (parts.count() == 2) {
80 spinBox->setPrefix(parts.at(0));
81 spinBox->setSuffix(parts.at(1));
82 } else {
83 spinBox->setPrefix(QString());
84 spinBox->setSuffix(QString());
85 }
86}
87
88/*!
89 * Sets up a format string for internationalizing spin boxes.
90 *
91 * This function allows the customization of prefix and suffix for spin boxes
92 * (QSpinBox and QDoubleSpinBox), considering internationalization needs.
93 *
94 * Spin boxes display a number and possibly a prefix and/or suffix. However,
95 * in some languages, the position of the prefix and suffix may be reversed
96 * compared to English. Example: In English, you write 50%, but in Turkish,
97 * you write %50. Qt does not offer an out-of-the-box solution for this. This
98 * helper now provides complete internationalization for prefixes and suffixes
99 * of spin boxes.
100 *
101 * For QSpinBox it also provides correct plural handling by installing a
102 * handler for the valueChanged() signal to update prefix and suffix whenever
103 * the spin box value changes in the future.
104 *
105 * Example usage:
106 * \code
107 * QDoubleSpinBox doubleBox;
108 * KLocalization::setupSpinBoxFormatString(
109 * &doubleBox,
110 * ki18nc("@item %v is a number and the second % is the percent sign", "%v%"));
111 * // Turkish translation: "%%v"
112 *
113 * QSpinBox intBox;
114 * KLocalization::setupSpinBoxFormatString(
115 * &intBox,
116 * ki18ncp("@item %v is a number", "Baking %v cake", "Baking %v cakes"));
117 * \endcode
118 *
119 * \a T The type of the spin box, which must be either QSpinBox or QDoubleSpinBox.
120 *
121 * \a spinBox Pointer to the spin box.
122 *
123 * \a formatString A localized string in the format "PREFIX%vSUFFIX".
124 * \list
125 * \li For QDoubleSpinBox, plural forms in \a formatString are ignored
126 * and should be avoided. Use KLocalizedString::ki18nc "ki18nc()"
127 * to generate the format string.
128 * \li For QSpinBox, if \a formatString includes plural forms, they are
129 * utilized. While optional, their use is highly recommended for
130 * accurate pluralization. Use KLocalizedString::ki18ncp "ki18ncp()"
131 * to generate the format string.
132 * \endlist
133 *
134 * It is safe to call this function multiple times on the same spin box.
135 *
136 * \sa retranslateSpinBoxFormatString
137 *
138 * \since 6.5
139 */
140template<typename T>
141inline void setupSpinBoxFormatString(T *spinBox, const KLocalizedString &formatString)
142{
143 constexpr bool isSpinBox = std::is_base_of_v<QSpinBox, T>;
144 constexpr bool isDoubleSpinBox = std::is_base_of_v<QDoubleSpinBox, T>;
145 static_assert(isSpinBox || isDoubleSpinBox, "First argument must be a QSpinBox or QDoubleSpinBox.");
146
147 if constexpr (isSpinBox) {
148 const bool hasSetup = !spinBox->property(Private::SpinBoxFormatStringProperty).isNull();
149 if (!hasSetup) {
150 QObject::connect(spinBox, &T::valueChanged, spinBox, [spinBox]() {
151 retranslateSpinBoxFormatString(spinBox);
152 });
153 }
154 }
155 // Using relaxSubs() to avoid error marks if the library user did pass
156 // a singular-only KLocalizedString.
157 spinBox->setProperty(Private::SpinBoxFormatStringProperty, QVariant::fromValue(value: formatString.relaxSubs()));
158 retranslateSpinBoxFormatString(spinBox);
159}
160
161}
162
163#endif
164

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