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 | |
17 | class QDoubleSpinBox; |
18 | class QSpinBox; |
19 | |
20 | /*! |
21 | * \namespace KLocalization |
22 | * \inmodule KI18n |
23 | * \brief Namespace containing helpers for localization. |
24 | * \since 6.5 |
25 | */ |
26 | namespace KLocalization |
27 | { |
28 | |
29 | namespace Private |
30 | { |
31 | |
32 | constexpr 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 | */ |
57 | template<typename T> |
58 | inline 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 | */ |
140 | template<typename T> |
141 | inline 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 | |