Perceptual Color

initializetranslation.cpp
1// SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR MIT
3
4// Own headers
5// First the interface, which forces the header to be self-contained.
6#include "initializetranslation.h"
7
8#include "initializelibraryresources.h"
9#include <qcoreapplication.h>
10#include <qdebug.h>
11#include <qglobal.h>
12#include <qlocale.h>
13#include <qmutex.h>
14#include <qpointer.h>
15#include <qstring.h>
16#include <qstringliteral.h>
17#include <qthread.h>
18#include <qtranslator.h>
19
20#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
21#include <qlist.h>
22#else
23#include <qstringlist.h>
24#endif
25
26/** @internal @file
27 *
28 * Provides the @ref PerceptualColor::initializeTranslation() function. */
29
30namespace PerceptualColor
31{
32
33/** @internal
34 *
35 * @brief Set the translation for the whole library.
36 *
37 * After calling this function, all objects of this library that are created
38 * from now on are translated according to translation that was set.
39 *
40 * Objects that were yet existing when calling are <em>not always</em>
41 * automatically updated: When calling this function, Qt sends
42 * a <tt>QEvent::LanguageChange</tt> event only to top-level widgets,
43 * and these will get updated then. You can send the event yourself
44 * to non-top-level widgets to update those widgets also. Note that
45 * also @ref RgbColorSpaceFactory generates objects that might have
46 * localized properties; these objects do not support translation
47 * updates.
48 *
49 * If you create objects that use translations <em>before</em> a translation
50 * has been set explicitly, than automatically an environment-dependant
51 * translation is loaded.
52 *
53 * It is safe to call this function multiple times.
54 *
55 * @pre There exists exactly <em>one</em> instance of <tt>QCoreApplication</tt>
56 * to which the parameter points. This function is called from the same thread
57 * in which the <tt>QCoreApplication</tt> instance lives.
58 *
59 * @param instance A pointer to the <tt>QCoreApplication</tt> instance for
60 * which the initialization will be done.
61 *
62 * @param newUiLanguages List of translations, ordered by priority, most
63 * important ones first, like in <tt>QLocale::uiLanguages()</tt>. If
64 * <tt>std::optional::has_value()</tt> than the translation is
65 * initialized for this value, and previously loaded translations are
66 * removed. If <tt>std::optional::has_value()</tt> is <tt>false</tt>,
67 * than it depends: If no translation has been initialized so far, than
68 * the translation is initialized to an environment-dependent default
69 * value; otherwise there last initialization is simply repeated.
70 *
71 * @post The translation is initialized, even if a previous initialization
72 * had been destroyed by deleting the previous QCoreApplication object. */
73void initializeTranslation(QCoreApplication *instance, std::optional<QStringList> newUiLanguages)
74{
75 // Mutex protection
76 static QMutex mutex;
77#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
78 QMutexLocker<QMutex> mutexLocker(&mutex);
79#else
80 QMutexLocker mutexLocker(&mutex);
81#endif
82
83 // Check of pre-conditions
84 // The mutex lowers the risk when using QCoreApplication::instance()
85 // and QThread::currentThread(), which are not explicitly documented
86 // as thread-safe.
87 if (instance == nullptr) {
88 qWarning() //
89 << __func__ //
90 << "must not be called without a QCoreApplication object.";
91 throw 0;
92 }
94 qWarning() //
95 << __func__ //
96 << "must not be called by any other thread "
97 "except the QCoreApplication thread.";
98 throw 0;
99 }
100
101 // Static variables
102 static QTranslator translator;
103 // The last UI language list that was loaded:
104 static std::optional<QStringList> translatorUiLanguages;
105 // A guarded pointer to the QCoreApplication object for which
106 // the translation is initialized. In the (strange) use case
107 // that a library user deletes his QCoreApplication (and maybe
108 // create a new one), this guarded pointer is set to nullptr.
109 // We provide support for this use case because Q_COREAPP_STARTUP_FUNCTION
110 // also does, and we want to provide a full-featured alternative
111 // to Q_COREAPP_STARTUP_FUNCTION.
112 static QPointer<QCoreApplication> instanceWhereTranslationIsInstalled;
113
114 // Actual function implementation…
115
116 if (!newUiLanguages.has_value()) {
117 if (translatorUiLanguages.has_value()) {
118 newUiLanguages = translatorUiLanguages;
119 } else {
120 newUiLanguages = //
122 }
123 }
124
125 // NOTE Currently, this library does not use any plural forms in the
126 // original English user-visible strings of the form "%1 color(s)".
127 // If it becomes necessary to have these strings later, we have to
128 // adapt this function: It has to install unconditionally first a
129 // QTranslator for English, which resolves to "1 color" or "2 colors"
130 // and so on. Then, a further QTranslator is installed for the target
131 // language (but only if the target language is not English?). A
132 // QTranslator that is installed later has higher priority. This makes
133 // sure that, if a string is not translated to the target language,
134 // the user does not see something like "1 color(s)" in the English
135 // fallback, but instead "1 color".
136
137 // QTranslator::load() will generate a QEvent::LanguageChange event
138 // even if it loads the same translation file that was loaded anyway.
139 // To avoid unnecessary events, we check if the new locale is really
140 // different from the old locale: only than, we try to load the new
141 // locale.
142 //
143 // Still, this will generate unnecessary events if in the previous call
144 // of this function, we had tried to load a non-existing translation, so
145 // that the QTranslator has an empty file path now. If we try to load now
146 // another non-existing translation, we get an unnecessary event. But
147 // to try to filter this out would be overkill…
148 if (translatorUiLanguages != newUiLanguages) {
149 // Resources can be loaded, and they can also be unloaded. Therefore,
150 // here we make sure that our resources are actually currently loaded.
151 // It is safe to call this function various times, and the overhead
152 // should not be big.
153 initializeLibraryResources();
154
155 if (newUiLanguages.value().count() <= 0) {
156 // QTranslator::load() will always delete the currently loaded
157 // translation. After that, it will try to load the new one.
158 // With this trick, we can delete the existing translation:
159 Q_UNUSED( // We expect load() to fail, so we discard return value.
160 translator.load(
161 // Filename of the binary translation file (.qm):
162 QStringLiteral("nonexistingfilename"),
163 // Directory within which filename is searched
164 // if filename is a relative path:
165 QStringLiteral(":/PerceptualColor/i18n")));
166 } else {
167 bool loaded = false;
168 int i = 0;
169 // NOTE We will load the first translation among the translation
170 // list for which we can find an actual translation file (qm file).
171 // Example: The list is "fr", "es", "de". The qm file for "fr" does
172 // not exist, but the "qm" filed for "es" and "de" exist. Only the
173 // "es" translation is loaded. If a specific string is missing in
174 // "es", but exists in "de", than the system will nevertheless
175 // fallback to the original source code language ("en"). Of course,
176 // it would be better to fallback to "de", but this would require
177 // to load various QTranslator and this might be a overkill. While
178 // KDE’s internationalization library explicitly supports this use
179 // case, Qt doesn’t. And we do not have too many strings to
180 // translate anyway. If we find out later that we have many
181 // incomplete translations, we can still implement this feature…
182 while (!loaded && i < newUiLanguages.value().count()) {
183 loaded = translator.load(
184 // The locale. From this locale are generated BCP47 codes.
185 // Various versions (upper-case and lower-case) are tried
186 // to load. If for more specific codes like "en-US" it
187 // does not succeed, than less specific variants like
188 // "en" are also tried.
189 QLocale(newUiLanguages.value().value(i)),
190 // First part of the filename
191 QStringLiteral("localization"),
192 // Separator after the first part of the filename
193 // (Intentionally NOT "_" or "-" to avoid confusion
194 // with the separators in BCP47 codes
195 QStringLiteral("."),
196 // Directory within which filename is searched
197 // if filename is a relative path:
198 QStringLiteral(":/PerceptualColor/i18n"));
199 ++i;
200 }
201 }
202 translatorUiLanguages = newUiLanguages;
203 }
204
205 // Make sure that the translator is installed into the current
206 // QCoreApplication instance. The library user cannot uninstall it
207 // because this is only possible when you have a pointer to the
208 // QTranslator object, but the pointer is kept a private information
209 // of this function. So we can be confident that, if once installed
210 // on a QCoreApplication object, the QTranslation object will stay
211 // available. However, the library user could delete the existing
212 // QCoreApplication object and create a new one. In this case,
213 // our guarded pointer instanceWhereTranslationIsInstalled will
214 // be set to nullptr, so we can detect this case:
215 if (instanceWhereTranslationIsInstalled != instance) {
216 if (instance->installTranslator(&translator)) {
217 instanceWhereTranslationIsInstalled = instance;
218 } else {
219 instanceWhereTranslationIsInstalled = nullptr;
220 }
221 }
222}
223
224} // namespace PerceptualColor
The namespace of this library.
bool installTranslator(QTranslator *translationFile)
QCoreApplication * instance()
QLocale system()
QStringList uiLanguages() const const
QThread * currentThread()
bool load(const QLocale &locale, const QString &filename, const QString &prefix, const QString &directory, const QString &suffix)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:36 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.