File indexing completed on 2024-05-19 04:45:52
0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com> 0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT 0003 0004 // Own headers 0005 // First the interface, which forces the header to be self-contained. 0006 #include "initializetranslation.h" 0007 0008 #include "initializelibraryresources.h" 0009 #include <qcoreapplication.h> 0010 #include <qdebug.h> 0011 #include <qglobal.h> 0012 #include <qlocale.h> 0013 #include <qmutex.h> 0014 #include <qpointer.h> 0015 #include <qstring.h> 0016 #include <qstringliteral.h> 0017 #include <qthread.h> 0018 #include <qtranslator.h> 0019 0020 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0021 #include <qlist.h> 0022 #else 0023 #include <qstringlist.h> 0024 #endif 0025 0026 /** @internal @file 0027 * 0028 * Provides the @ref PerceptualColor::initializeTranslation() function. */ 0029 0030 namespace PerceptualColor 0031 { 0032 0033 /** @internal 0034 * 0035 * @brief Set the translation for the whole library. 0036 * 0037 * After calling this function, all objects of this library that are created 0038 * from now on are translated according to translation that was set. 0039 * 0040 * Objects that were yet existing when calling are <em>not always</em> 0041 * automatically updated: When calling this function, Qt sends 0042 * a <tt>QEvent::LanguageChange</tt> event only to top-level widgets, 0043 * and these will get updated then. You can send the event yourself 0044 * to non-top-level widgets to update those widgets also. Note that 0045 * also @ref RgbColorSpaceFactory generates objects that might have 0046 * localized properties; these objects do not support translation 0047 * updates. 0048 * 0049 * If you create objects that use translations <em>before</em> a translation 0050 * has been set explicitly, than automatically an environment-dependant 0051 * translation is loaded. 0052 * 0053 * It is safe to call this function multiple times. 0054 * 0055 * @pre There exists exactly <em>one</em> instance of <tt>QCoreApplication</tt> 0056 * to which the parameter points. This function is called from the same thread 0057 * in which the <tt>QCoreApplication</tt> instance lives. 0058 * 0059 * @param instance A pointer to the <tt>QCoreApplication</tt> instance for 0060 * which the initialization will be done. 0061 * 0062 * @param newUiLanguages List of translations, ordered by priority, most 0063 * important ones first, like in <tt>QLocale::uiLanguages()</tt>. If 0064 * <tt>std::optional::has_value()</tt> than the translation is 0065 * initialized for this value, and previously loaded translations are 0066 * removed. If <tt>std::optional::has_value()</tt> is <tt>false</tt>, 0067 * than it depends: If no translation has been initialized so far, than 0068 * the translation is initialized to an environment-dependent default 0069 * value; otherwise there last initialization is simply repeated. 0070 * 0071 * @post The translation is initialized, even if a previous initialization 0072 * had been destroyed by deleting the previous QCoreApplication object. */ 0073 void initializeTranslation(QCoreApplication *instance, std::optional<QStringList> newUiLanguages) 0074 { 0075 // Mutex protection 0076 static QMutex mutex; 0077 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0078 QMutexLocker<QMutex> mutexLocker(&mutex); 0079 #else 0080 QMutexLocker mutexLocker(&mutex); 0081 #endif 0082 0083 // Check of pre-conditions 0084 // The mutex lowers the risk when using QCoreApplication::instance() 0085 // and QThread::currentThread(), which are not explicitly documented 0086 // as thread-safe. 0087 if (instance == nullptr) { 0088 qWarning() // 0089 << __func__ // 0090 << "must not be called without a QCoreApplication object."; 0091 throw 0; 0092 } 0093 if (QThread::currentThread() != QCoreApplication::instance()->thread()) { 0094 qWarning() // 0095 << __func__ // 0096 << "must not be called by any other thread " 0097 "except the QCoreApplication thread."; 0098 throw 0; 0099 } 0100 0101 // Static variables 0102 static QTranslator translator; 0103 // The last UI language list that was loaded: 0104 static std::optional<QStringList> translatorUiLanguages; 0105 // A guarded pointer to the QCoreApplication object for which 0106 // the translation is initialized. In the (strange) use case 0107 // that a library user deletes his QCoreApplication (and maybe 0108 // create a new one), this guarded pointer is set to nullptr. 0109 // We provide support for this use case because Q_COREAPP_STARTUP_FUNCTION 0110 // also does, and we want to provide a full-featured alternative 0111 // to Q_COREAPP_STARTUP_FUNCTION. 0112 static QPointer<QCoreApplication> instanceWhereTranslationIsInstalled; 0113 0114 // Actual function implementation… 0115 0116 if (!newUiLanguages.has_value()) { 0117 if (translatorUiLanguages.has_value()) { 0118 newUiLanguages = translatorUiLanguages; 0119 } else { 0120 newUiLanguages = // 0121 QLocale().uiLanguages() + QLocale::system().uiLanguages(); 0122 } 0123 } 0124 0125 // NOTE Currently, this library does not use any plural forms in the 0126 // original English user-visible strings of the form "%1 color(s)". 0127 // If it becomes necessary to have these strings later, we have to 0128 // adapt this function: It has to install unconditionally first a 0129 // QTranslator for English, which resolves to "1 color" or "2 colors" 0130 // and so on. Then, a further QTranslator is installed for the target 0131 // language (but only if the target language is not English?). A 0132 // QTranslator that is installed later has higher priority. This makes 0133 // sure that, if a string is not translated to the target language, 0134 // the user does not see something like "1 color(s)" in the English 0135 // fallback, but instead "1 color". 0136 0137 // QTranslator::load() will generate a QEvent::LanguageChange event 0138 // even if it loads the same translation file that was loaded anyway. 0139 // To avoid unnecessary events, we check if the new locale is really 0140 // different from the old locale: only than, we try to load the new 0141 // locale. 0142 // 0143 // Still, this will generate unnecessary events if in the previous call 0144 // of this function, we had tried to load a non-existing translation, so 0145 // that the QTranslator has an empty file path now. If we try to load now 0146 // another non-existing translation, we get an unnecessary event. But 0147 // to try to filter this out would be overkill… 0148 if (translatorUiLanguages != newUiLanguages) { 0149 // Resources can be loaded, and they can also be unloaded. Therefore, 0150 // here we make sure that our resources are actually currently loaded. 0151 // It is safe to call this function various times, and the overhead 0152 // should not be big. 0153 initializeLibraryResources(); 0154 0155 if (newUiLanguages.value().count() <= 0) { 0156 // QTranslator::load() will always delete the currently loaded 0157 // translation. After that, it will try to load the new one. 0158 // With this trick, we can delete the existing translation: 0159 Q_UNUSED( // We expect load() to fail, so we discard return value. 0160 translator.load( 0161 // Filename of the binary translation file (.qm): 0162 QStringLiteral("nonexistingfilename"), 0163 // Directory within which filename is searched 0164 // if filename is a relative path: 0165 QStringLiteral(":/PerceptualColor/i18n"))); 0166 } else { 0167 bool loaded = false; 0168 int i = 0; 0169 // NOTE We will load the first translation among the translation 0170 // list for which we can find an actual translation file (qm file). 0171 // Example: The list is "fr", "es", "de". The qm file for "fr" does 0172 // not exist, but the "qm" filed for "es" and "de" exist. Only the 0173 // "es" translation is loaded. If a specific string is missing in 0174 // "es", but exists in "de", than the system will nevertheless 0175 // fallback to the original source code language ("en"). Of course, 0176 // it would be better to fallback to "de", but this would require 0177 // to load various QTranslator and this might be a overkill. While 0178 // KDE’s internationalization library explicitly supports this use 0179 // case, Qt doesn’t. And we do not have too many strings to 0180 // translate anyway. If we find out later that we have many 0181 // incomplete translations, we can still implement this feature… 0182 while (!loaded && i < newUiLanguages.value().count()) { 0183 loaded = translator.load( 0184 // The locale. From this locale are generated BCP47 codes. 0185 // Various versions (upper-case and lower-case) are tried 0186 // to load. If for more specific codes like "en-US" it 0187 // does not succeed, than less specific variants like 0188 // "en" are also tried. 0189 QLocale(newUiLanguages.value().value(i)), 0190 // First part of the filename 0191 QStringLiteral("localization"), 0192 // Separator after the first part of the filename 0193 // (Intentionally NOT "_" or "-" to avoid confusion 0194 // with the separators in BCP47 codes 0195 QStringLiteral("."), 0196 // Directory within which filename is searched 0197 // if filename is a relative path: 0198 QStringLiteral(":/PerceptualColor/i18n")); 0199 ++i; 0200 } 0201 } 0202 translatorUiLanguages = newUiLanguages; 0203 } 0204 0205 // Make sure that the translator is installed into the current 0206 // QCoreApplication instance. The library user cannot uninstall it 0207 // because this is only possible when you have a pointer to the 0208 // QTranslator object, but the pointer is kept a private information 0209 // of this function. So we can be confident that, if once installed 0210 // on a QCoreApplication object, the QTranslation object will stay 0211 // available. However, the library user could delete the existing 0212 // QCoreApplication object and create a new one. In this case, 0213 // our guarded pointer instanceWhereTranslationIsInstalled will 0214 // be set to nullptr, so we can detect this case: 0215 if (instanceWhereTranslationIsInstalled != instance) { 0216 if (instance->installTranslator(&translator)) { 0217 instanceWhereTranslationIsInstalled = instance; 0218 } else { 0219 instanceWhereTranslationIsInstalled = nullptr; 0220 } 0221 } 0222 } 0223 0224 } // namespace PerceptualColor