| 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 | |