1/*
2 This file is part of the KDE Libraries
3 SPDX-FileCopyrightText: 2007 Krzysztof Lichota <lichota@mimuw.edu.pl>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kswitchlanguagedialog_p.h"
9
10#include "debug.h"
11
12#include <QApplication>
13#include <QDialogButtonBox>
14#include <QDir>
15#include <QGridLayout>
16#include <QLabel>
17#include <QMap>
18#include <QPushButton>
19#include <QSettings>
20#include <QStandardPaths>
21#include <private/qlocale_p.h>
22
23#include <KLanguageButton>
24#include <KLocalizedString>
25#include <KMessageBox>
26
27// Believe it or not we can't use KConfig from here
28// (we need KConfig during QCoreApplication ctor which is too early for it)
29// So we cooked a QSettings based solution
30static std::unique_ptr<QSettings> localeOverridesSettings()
31{
32 const QString configPath = QStandardPaths::writableLocation(type: QStandardPaths::GenericConfigLocation);
33 const QDir configDir(configPath);
34 if (!configDir.exists()) {
35 configDir.mkpath(QStringLiteral("."));
36 }
37
38 return std::make_unique<QSettings>(args: configPath + QLatin1String("/klanguageoverridesrc"), args: QSettings::IniFormat);
39}
40
41static QByteArray getApplicationSpecificLanguage(const QByteArray &defaultCode = QByteArray())
42{
43 std::unique_ptr<QSettings> settings = localeOverridesSettings();
44 settings->beginGroup(QStringLiteral("Language"));
45 return settings->value(key: qAppName(), defaultValue: defaultCode).toByteArray();
46}
47
48namespace KDEPrivate
49{
50Q_COREAPP_STARTUP_FUNCTION(initializeLanguages)
51
52void setApplicationSpecificLanguage(const QByteArray &languageCode)
53{
54 std::unique_ptr<QSettings> settings = localeOverridesSettings();
55 settings->beginGroup(QStringLiteral("Language"));
56
57 if (languageCode.isEmpty()) {
58 settings->remove(key: qAppName());
59 } else {
60 settings->setValue(key: qAppName(), value: languageCode);
61 }
62}
63
64void initializeLanguages()
65{
66 const QByteArray languageCode = getApplicationSpecificLanguage();
67
68 if (!languageCode.isEmpty()) {
69 QByteArray languages = qgetenv(varName: "LANGUAGE");
70 if (languages.isEmpty()) {
71 qputenv(varName: "LANGUAGE", value: languageCode);
72 } else {
73 qputenv(varName: "LANGUAGE", value: QByteArray(languageCode + ':' + languages));
74 }
75 // Ideally setting the LANGUAGE would change the default QLocale too
76 // but unfortunately this is too late since the QCoreApplication constructor
77 // already created a QLocale at this stage so we need to set the reset it
78 // by triggering the creation and destruction of a QSystemLocale
79 // this is highly dependent on Qt internals, so may break, but oh well
80 QSystemLocale *dummy = new QSystemLocale();
81 delete dummy;
82 }
83}
84
85struct LanguageRowData {
86 LanguageRowData()
87 {
88 label = nullptr;
89 languageButton = nullptr;
90 removeButton = nullptr;
91 }
92 QLabel *label;
93 KLanguageButton *languageButton;
94 QPushButton *removeButton;
95
96 void setRowWidgets(QLabel *label, KLanguageButton *languageButton, QPushButton *removeButton)
97 {
98 this->label = label;
99 this->languageButton = languageButton;
100 this->removeButton = removeButton;
101 }
102};
103
104class KSwitchLanguageDialogPrivate
105{
106public:
107 KSwitchLanguageDialogPrivate(KSwitchLanguageDialog *parent);
108
109 KSwitchLanguageDialog *p; // parent class
110
111 /**
112 Fills language button with names of languages for which given application has translation.
113 */
114 void fillApplicationLanguages(KLanguageButton *button);
115
116 /**
117 Adds one button with language to widget.
118 */
119 void addLanguageButton(const QString &languageCode, bool primaryLanguage);
120
121 /**
122 Returns list of languages chosen for application or default languages is they are not set.
123 */
124 QStringList applicationLanguageList();
125
126 QMap<QPushButton *, LanguageRowData> languageRows;
127 QList<KLanguageButton *> languageButtons;
128 QGridLayout *languagesLayout;
129};
130
131/*************************** KSwitchLanguageDialog **************************/
132
133KSwitchLanguageDialog::KSwitchLanguageDialog(QWidget *parent)
134 : QDialog(parent)
135 , d(new KSwitchLanguageDialogPrivate(this))
136{
137 setWindowTitle(i18nc("@title:window", "Configure Language"));
138
139 QVBoxLayout *topLayout = new QVBoxLayout(this);
140
141 QLabel *label = new QLabel(i18n("Please choose the language which should be used for this application:"), this);
142 topLayout->addWidget(label);
143
144 QHBoxLayout *languageHorizontalLayout = new QHBoxLayout();
145 topLayout->addLayout(layout: languageHorizontalLayout);
146
147 d->languagesLayout = new QGridLayout();
148 languageHorizontalLayout->addLayout(layout: d->languagesLayout);
149 languageHorizontalLayout->addStretch();
150
151 const QStringList defaultLanguages = d->applicationLanguageList();
152
153 int count = defaultLanguages.count();
154 for (int i = 0; i < count; ++i) {
155 QString language = defaultLanguages[i];
156 bool primaryLanguage = (i == 0);
157 d->addLanguageButton(languageCode: language, primaryLanguage);
158 }
159
160 if (!count) {
161 QLocale l;
162 d->addLanguageButton(languageCode: l.name(), primaryLanguage: true);
163 }
164
165 QHBoxLayout *addButtonHorizontalLayout = new QHBoxLayout();
166 topLayout->addLayout(layout: addButtonHorizontalLayout);
167
168 QPushButton *addLangButton = new QPushButton(i18nc("@action:button", "Add Fallback Language"), this);
169 addLangButton->setToolTip(i18nc("@info:tooltip", "Adds one more language which will be used if other translations do not contain a proper translation."));
170 connect(sender: addLangButton, signal: &QPushButton::clicked, context: this, slot: &KSwitchLanguageDialog::slotAddLanguageButton);
171 addButtonHorizontalLayout->addWidget(addLangButton);
172 addButtonHorizontalLayout->addStretch();
173
174 topLayout->addStretch(stretch: 10);
175
176 QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
177 buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults);
178 KGuiItem::assign(button: buttonBox->button(which: QDialogButtonBox::Ok), item: KStandardGuiItem::ok());
179 KGuiItem::assign(button: buttonBox->button(which: QDialogButtonBox::Cancel), item: KStandardGuiItem::cancel());
180 KGuiItem::assign(button: buttonBox->button(which: QDialogButtonBox::RestoreDefaults), item: KStandardGuiItem::defaults());
181
182 topLayout->addWidget(buttonBox);
183
184 connect(sender: buttonBox, signal: &QDialogButtonBox::accepted, context: this, slot: &KSwitchLanguageDialog::slotOk);
185 connect(sender: buttonBox, signal: &QDialogButtonBox::accepted, context: this, slot: &QDialog::accept);
186 connect(sender: buttonBox, signal: &QDialogButtonBox::rejected, context: this, slot: &QDialog::reject);
187 connect(sender: buttonBox->button(which: QDialogButtonBox::RestoreDefaults), signal: &QPushButton::clicked, context: this, slot: &KSwitchLanguageDialog::slotDefault);
188}
189
190KSwitchLanguageDialog::~KSwitchLanguageDialog()
191{
192 delete d;
193}
194
195void KSwitchLanguageDialog::slotAddLanguageButton()
196{
197 // adding new button with en_US as it should always be present
198 d->addLanguageButton(QStringLiteral("en_US"), primaryLanguage: d->languageButtons.isEmpty());
199}
200
201void KSwitchLanguageDialog::removeButtonClicked()
202{
203 QObject const *signalSender = sender();
204 if (!signalSender) {
205 qCCritical(DEBUG_KXMLGUI) << "KSwitchLanguageDialog::removeButtonClicked() called directly, not using signal";
206 return;
207 }
208
209 QPushButton *removeButton = const_cast<QPushButton *>(::qobject_cast<const QPushButton *>(object: signalSender));
210 if (!removeButton) {
211 qCCritical(DEBUG_KXMLGUI) << "KSwitchLanguageDialog::removeButtonClicked() called from something else than QPushButton";
212 return;
213 }
214
215 QMap<QPushButton *, LanguageRowData>::iterator it = d->languageRows.find(key: removeButton);
216 if (it == d->languageRows.end()) {
217 qCCritical(DEBUG_KXMLGUI) << "KSwitchLanguageDialog::removeButtonClicked called from unknown QPushButton";
218 return;
219 }
220
221 LanguageRowData languageRowData = it.value();
222
223 d->languageButtons.removeAll(t: languageRowData.languageButton);
224
225 languageRowData.label->deleteLater();
226 languageRowData.languageButton->deleteLater();
227 languageRowData.removeButton->deleteLater();
228 d->languageRows.erase(it);
229}
230
231void KSwitchLanguageDialog::languageOnButtonChanged(const QString &languageCode)
232{
233 Q_UNUSED(languageCode);
234#if 0
235 for (int i = 0, count = d->languageButtons.count(); i < count; ++i) {
236 KLanguageButton *languageButton = d->languageButtons[i];
237 if (languageButton->current() == languageCode) {
238 //update all buttons which have matching id
239 //might update buttons which were not changed, but well...
240 languageButton->setText(KLocale::global()->languageCodeToName(languageCode));
241 }
242 }
243#endif
244}
245
246void KSwitchLanguageDialog::slotOk()
247{
248 QStringList languages;
249 languages.reserve(asize: d->languageButtons.size());
250 for (auto *languageButton : std::as_const(t&: d->languageButtons)) {
251 languages << languageButton->current();
252 }
253
254 if (d->applicationLanguageList() != languages) {
255 QString languageString = languages.join(sep: QLatin1Char(':'));
256 // list is different from defaults or saved languages list
257 setApplicationSpecificLanguage(languageString.toLatin1());
258
259 KMessageBox::information(
260 parent: this,
261 i18n("The language for this application has been changed. The change will take effect the next time the application is started."), // text
262 i18nc("@title:window", "Application Language Changed"), // caption
263 QStringLiteral("ApplicationLanguageChangedWarning") // dontShowAgainName
264 );
265 }
266
267 accept();
268}
269
270void KSwitchLanguageDialog::slotDefault()
271{
272 const QStringList defaultLanguages = d->applicationLanguageList();
273
274 setApplicationSpecificLanguage(QByteArray());
275
276 // read back the new default
277 QString language = QString::fromLatin1(ba: getApplicationSpecificLanguage(defaultCode: "en_US"));
278
279 if (defaultLanguages != (QStringList() << language)) {
280 KMessageBox::information(
281 parent: this,
282 i18n("The language for this application has been changed. The change will take effect the next time the application is started."), // text
283 i18n("Application Language Changed"), // caption
284 QStringLiteral("ApplicationLanguageChangedWarning") // dontShowAgainName
285 );
286 }
287
288 accept();
289}
290
291/************************ KSwitchLanguageDialogPrivate ***********************/
292
293KSwitchLanguageDialogPrivate::KSwitchLanguageDialogPrivate(KSwitchLanguageDialog *parent)
294 : p(parent)
295{
296 // NOTE: do NOT use "p" in constructor, it is not fully constructed
297}
298
299static bool stripCountryCode(QString *languageCode)
300{
301 const int idx = languageCode->indexOf(c: QLatin1Char('_'));
302 if (idx != -1) {
303 *languageCode = languageCode->left(n: idx);
304 return true;
305 }
306 return false;
307}
308
309void KSwitchLanguageDialogPrivate::fillApplicationLanguages(KLanguageButton *button)
310{
311 const QLocale cLocale(QLocale::C);
312 QSet<QString> insertedLanguages;
313
314 const QList<QLocale> allLocales = QLocale::matchingLocales(language: QLocale::AnyLanguage, script: QLocale::AnyScript, territory: QLocale::AnyCountry);
315 for (const QLocale &l : allLocales) {
316 if (l != cLocale) {
317 QString languageCode = l.name();
318 if (!insertedLanguages.contains(value: languageCode) && KLocalizedString::isApplicationTranslatedInto(language: languageCode)) {
319 button->insertLanguage(languageCode);
320 insertedLanguages << languageCode;
321 } else if (stripCountryCode(languageCode: &languageCode)) {
322 if (!insertedLanguages.contains(value: languageCode) && KLocalizedString::isApplicationTranslatedInto(language: languageCode)) {
323 button->insertLanguage(languageCode);
324 insertedLanguages << languageCode;
325 }
326 }
327 }
328 }
329}
330
331QStringList KSwitchLanguageDialogPrivate::applicationLanguageList()
332{
333 QStringList languagesList;
334
335 QByteArray languageCode = getApplicationSpecificLanguage();
336 if (!languageCode.isEmpty()) {
337 languagesList = QString::fromLatin1(ba: languageCode).split(sep: QLatin1Char(':'));
338 }
339 if (languagesList.isEmpty()) {
340 QLocale l;
341 languagesList = l.uiLanguages();
342
343 // We get en-US here but we use en_US
344 for (auto &language : languagesList) {
345 language.replace(before: QLatin1Char('-'), after: QLatin1Char('_'));
346 }
347 }
348
349 for (int i = 0; i < languagesList.count();) {
350 QString languageCode = languagesList[i];
351 if (!KLocalizedString::isApplicationTranslatedInto(language: languageCode)) {
352 if (stripCountryCode(languageCode: &languageCode)) {
353 if (KLocalizedString::isApplicationTranslatedInto(language: languageCode)) {
354 languagesList[i] = languageCode;
355 ++i;
356 continue;
357 }
358 }
359 languagesList.removeAt(i);
360 } else {
361 ++i;
362 }
363 }
364
365 return languagesList;
366}
367
368void KSwitchLanguageDialogPrivate::addLanguageButton(const QString &languageCode, bool primaryLanguage)
369{
370 QString labelText = primaryLanguage ? i18n("Primary language:") : i18n("Fallback language:");
371
372 KLanguageButton *languageButton = new KLanguageButton(p);
373 languageButton->showLanguageCodes(show: true);
374
375 fillApplicationLanguages(button: languageButton);
376
377 languageButton->setCurrentItem(languageCode);
378
379 QObject::connect(sender: languageButton, signal: &KLanguageButton::activated, context: p, slot: &KSwitchLanguageDialog::languageOnButtonChanged);
380
381 LanguageRowData languageRowData;
382 QPushButton *removeButton = nullptr;
383
384 if (!primaryLanguage) {
385 removeButton = new QPushButton(i18nc("@action:button", "Remove"), p);
386
387 QObject::connect(sender: removeButton, signal: &QPushButton::clicked, context: p, slot: &KSwitchLanguageDialog::removeButtonClicked);
388 }
389
390 languageButton->setToolTip(
391 primaryLanguage ? i18nc("@info:tooltip", "This is the main application language which will be used first, before any other languages.")
392 : i18nc("@info:tooltip", "This is the language which will be used if any previous languages do not contain a proper translation."));
393
394 int numRows = languagesLayout->rowCount();
395
396 QLabel *languageLabel = new QLabel(labelText, p);
397 languagesLayout->addWidget(languageLabel, row: numRows + 1, column: 1, Qt::AlignLeft);
398 languagesLayout->addWidget(languageButton, row: numRows + 1, column: 2, Qt::AlignLeft);
399
400 if (!primaryLanguage) {
401 languagesLayout->addWidget(removeButton, row: numRows + 1, column: 3, Qt::AlignLeft);
402 languageRowData.setRowWidgets(label: languageLabel, languageButton, removeButton);
403 removeButton->show();
404 }
405
406 languageRows.insert(key: removeButton, value: languageRowData);
407
408 languageButtons.append(t: languageButton);
409 languageButton->show();
410 languageLabel->show();
411}
412
413}
414
415#include "moc_kswitchlanguagedialog_p.cpp"
416

source code of kxmlgui/src/kswitchlanguagedialog_p.cpp