Region & Language KCM: generate locales with glibc's localedef directly, instead of appending to /etc/locale.gen and running the distribution specific locale-gen wrapper, which T2 does not ship. The helper runs "localedef -i -c -f UTF-8 .UTF-8" for every selected locale, one after another. The KCM side no longer treats a missing /etc/locale.gen as "nothing to do". Signed-off-by: René Rebe --- plasma-workspace-6.7.3/kcms/region_language/localegenhelper/localegenhelper.h.vanilla +++ plasma-workspace-6.7.3/kcms/region_language/localegenhelper/localegenhelper.h @@ -1,6 +1,7 @@ /* localegenhelper.h SPDX-FileCopyrightText: 2021 Han Young + SPDX-FileCopyrightText: 2026 René Rebe SPDX-License-Identifier: GPL-2.0-or-later */ @@ -8,14 +9,11 @@ #include #include #include -#include #include #include #include #include -#include - using namespace std::chrono; class LocaleGenHelper : public QObject, protected QDBusContext { @@ -31,16 +29,17 @@ private Q_SLOTS: void enableLocalesPrivate(PolkitQt1::Authority::Result result); private: - void handleLocaleGen(int statusCode, QProcess::ExitStatus status, QProcess *process); - bool editLocaleGen(); + void handleLocaleDef(int statusCode, QProcess::ExitStatus status, QProcess *process); + bool generateLocales(); + void generateNextLocale(); void exitAfterTimeOut(); bool shouldGenerate(); void processLocales(const QStringList &locales); std::atomic m_isGenerating = false; - bool m_comment = false; - std::set m_alreadyEnabled; PolkitQt1::Authority *m_authority = nullptr; QStringList m_locales; + QStringList m_pendingLocales; + QString m_localeDefPath; QTimer m_timer; }; --- plasma-workspace-6.7.3/kcms/region_language/localegenhelper/localegenhelper.cpp.vanilla +++ plasma-workspace-6.7.3/kcms/region_language/localegenhelper/localegenhelper.cpp @@ -1,6 +1,7 @@ /* localegenhelper.cpp SPDX-FileCopyrightText: 2021 Han Young + SPDX-FileCopyrightText: 2026 René Rebe SPDX-License-Identifier: GPL-2.0-or-later */ @@ -11,11 +12,26 @@ #include #include +#include #include +#include using namespace Qt::StringLiterals; +// splits a locale name into its base and @modifier part, dropping the charset +static std::pair splitLocaleName(const QString &name) +{ + const qsizetype at = name.indexOf(u'@'); + const QString modifier = at < 0 ? QString() : name.mid(at); + QString base = at < 0 ? name : name.left(at); + const qsizetype dot = base.indexOf(u'.'); + if (dot >= 0) { + base.truncate(dot); + } + return {base, modifier}; +} + LocaleGenHelper::LocaleGenHelper() : m_authority(PolkitQt1::Authority::instance()) { @@ -62,100 +78,83 @@ void LocaleGenHelper::enableLocalesPriva { qDebug() << result; if (result != PolkitQt1::Authority::Result::Yes) { - Q_EMIT error(i18n("Unauthorized to edit locale configuration file")); + Q_EMIT error(i18n("Unauthorized to generate locales")); exitAfterTimeOut(); return; } - // if success, handleLocaleGen will call exit - if (editLocaleGen()) { + // handleLocaleDef schedules the exit once the last localedef run finished + if (!generateLocales()) { exitAfterTimeOut(); } } bool LocaleGenHelper::shouldGenerate() { - QFile localegen(QStringLiteral("/etc/locale.gen")); - if (!localegen.open(QIODevice::ReadOnly)) { - return false; - } - m_alreadyEnabled.clear(); - while (!localegen.atEnd()) { - QString locale = QString::fromLocal8Bit(localegen.readLine().simplified()); - if (!m_comment && locale == u"# generated by KDE Plasma Region & Language KCM") { - m_comment = true; - } - if (locale.isEmpty() || locale.front() == u'#') { - continue; - } - const QList localeAndCharset = QStringView(locale).split(u' '); - if (localeAndCharset.size() != 2 || localeAndCharset.at(1) != u"UTF-8") { - continue; - } else { - QString localeNameWithoutCharset = localeAndCharset.front().toString().remove(".UTF-8"_L1); - m_alreadyEnabled.insert(localeNameWithoutCharset); - } - } for (const auto &locale : std::as_const(m_locales)) { - if (locale == QLatin1Char('C')) { - continue; - } - if (m_alreadyEnabled.count(locale) == 0) { + if (locale != QLatin1Char('C')) { return true; } } return false; } -bool LocaleGenHelper::editLocaleGen() +bool LocaleGenHelper::generateLocales() { - bool result = false; - QFile localegen(QStringLiteral("/etc/locale.gen")); - if (!localegen.open(QIODevice::Append)) { - Q_EMIT error(i18n("Can't open file `/etc/locale.gen`")); - return result; + m_localeDefPath = QStandardPaths::findExecutable(QStringLiteral("localedef")); + if (m_localeDefPath.isEmpty()) { + m_localeDefPath = QStandardPaths::findExecutable(QStringLiteral("localedef"), + { + QStringLiteral("/usr/bin"), + QStringLiteral("/usr/sbin"), + QStringLiteral("/sbin"), + }); + } + if (m_localeDefPath.isEmpty()) { + Q_EMIT error(i18n("Can't locate executable `localedef`")); + return false; } + + m_pendingLocales.clear(); for (const auto &locale : std::as_const(m_locales)) { - if (m_alreadyEnabled.count(locale) || locale == QLatin1Char('C')) { - continue; + if (locale != QLatin1Char('C')) { + m_pendingLocales.append(locale); } - // start at newline first time - if (!m_comment) { - localegen.write("\n# generated by KDE Plasma Region & Language KCM\n"); - m_comment = true; - } - localegen.write(locale.toUtf8() + ".UTF-8 UTF-8\n"_ba); } - - QString localeGenPath = QStandardPaths::findExecutable(QStringLiteral("locale-gen")); - if (localeGenPath.isEmpty()) { - localeGenPath = QStandardPaths::findExecutable(QStringLiteral("locale-gen"), - { - QStringLiteral("/usr/sbin"), - QStringLiteral("/sbin"), - QStringLiteral("/usr/local/sbin"), - }); - } - if (!localeGenPath.isEmpty()) { - auto *process = new QProcess(this); - process->setProgram(localeGenPath); - connect(process, &QProcess::finished, this, [this, process](int statusCode, QProcess::ExitStatus status) { - handleLocaleGen(statusCode, status, process); - }); - process->start(); - result = true; - } else { - Q_EMIT error(i18n("Can't locate executable `locale-gen`")); + if (m_pendingLocales.isEmpty()) { + Q_EMIT success(); + return false; } - return result; + + generateNextLocale(); + return true; } -void LocaleGenHelper::handleLocaleGen(int statusCode, QProcess::ExitStatus status, QProcess *process) +void LocaleGenHelper::generateNextLocale() { - Q_UNUSED(status) - if (statusCode == 0) { - Q_EMIT success(); - } else { + const QString locale = m_pendingLocales.takeFirst(); + const auto [base, modifier] = splitLocaleName(locale); + + // the source definition carries the modifier, the compiled name has it after the charset + auto *process = new QProcess(this); + process->setProgram(m_localeDefPath); + process->setArguments({QStringLiteral("-i"), + base + modifier, + QStringLiteral("-c"), + QStringLiteral("-f"), + QStringLiteral("UTF-8"), + base + ".UTF-8"_L1 + modifier}); + connect(process, &QProcess::finished, this, [this, process](int statusCode, QProcess::ExitStatus status) { + handleLocaleDef(statusCode, status, process); + }); + process->start(); +} + +void LocaleGenHelper::handleLocaleDef(int statusCode, QProcess::ExitStatus status, QProcess *process) +{ + // localedef exits 1 if it only issued warnings, 4 if no output was written + const bool ok = status == QProcess::NormalExit && statusCode <= 1; + if (!ok) { QString all_error; if (!process) { all_error = i18n("Unknown"); @@ -164,8 +163,20 @@ void LocaleGenHelper::handleLocaleGen(in all_error.append(QLatin1Char('\n')); all_error.append(QString::fromLocal8Bit(process->readAllStandardError())); } + m_pendingLocales.clear(); Q_EMIT error(all_error); } + if (process) { + process->deleteLater(); + } + + if (!m_pendingLocales.isEmpty()) { + generateNextLocale(); + return; + } + if (ok) { + Q_EMIT success(); + } exitAfterTimeOut(); } --- plasma-workspace-6.7.3/kcms/region_language/localegeneratorglibc.cpp.vanilla +++ plasma-workspace-6.7.3/kcms/region_language/localegeneratorglibc.cpp @@ -2,6 +2,7 @@ localegeneratorglibc.cpp SPDX-FileCopyrightText: 2022 Han Young SPDX-FileCopyrightText: 2022 Harald Sitter + SPDX-FileCopyrightText: 2026 René Rebe SPDX-License-Identifier: GPL-2.0-or-later */ @@ -21,12 +22,6 @@ LocaleGeneratorGlibc::LocaleGeneratorGli void LocaleGeneratorGlibc::localesGenerate(const QStringList &list) { qCDebug(KCM_REGIONANDLANG) << "enable locales: " << list; - if (!QFile::exists(QStringLiteral("/etc/locale.gen"))) { - // When locale.gen is not present we assume that to mean that no generation is necessary, meaning we are done. - // e.g. fedora, centos and derivates - Q_EMIT needsFont(); - return; - } qCDebug(KCM_REGIONANDLANG) << "send polkit request"; auto reply = m_interface->enableLocales(list); if (reply.isError()) {