You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
287 lines
11 KiB
287 lines
11 KiB
/* |
|
kcmregionandlang.cpp |
|
SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas@kde.org> |
|
SPDX-FileCopyrightText: 2021 Han Young <hanyoung@protonmail.com> |
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
#include "config-workspace.h" |
|
|
|
#include "kcmregionandlang.h" |
|
|
|
#include <unistd.h> |
|
|
|
#include <QDBusConnection> |
|
#include <QDBusMessage> |
|
#include <QDBusPendingCall> |
|
|
|
#include <KLocalizedString> |
|
#include <KPluginFactory> |
|
#include <KSharedConfig> |
|
|
|
#include "languagelistmodel.h" |
|
#include "localegenerator.h" |
|
#include "localegeneratorbase.h" |
|
#include "localelistmodel.h" |
|
#include "optionsmodel.h" |
|
#include "regionandlangsettings.h" |
|
|
|
using namespace KCM_RegionAndLang; |
|
|
|
K_PLUGIN_CLASS_WITH_JSON(KCMRegionAndLang, "kcm_regionandlang.json") |
|
|
|
KCMRegionAndLang::KCMRegionAndLang(QObject *parent, const KPluginMetaData &data, const QVariantList &args) |
|
: KQuickAddons::ManagedConfigModule(parent, data, args) |
|
, m_settings(new RegionAndLangSettings(this)) |
|
, m_optionsModel(new OptionsModel(this)) |
|
, m_generator(LocaleGenerator::getGenerator()) |
|
{ |
|
connect(m_generator, &LocaleGeneratorBase::userHasToGenerateManually, this, &KCMRegionAndLang::userHasToGenerateManually); |
|
connect(m_generator, &LocaleGeneratorBase::success, this, &KCMRegionAndLang::generateFinished); |
|
connect(m_generator, &LocaleGeneratorBase::needsFont, this, &KCMRegionAndLang::requireInstallFont); |
|
connect(m_generator, &LocaleGeneratorBase::success, this, &KCMRegionAndLang::saveToConfigFile); |
|
connect(m_generator, &LocaleGeneratorBase::userHasToGenerateManually, this, &KCMRegionAndLang::saveToConfigFile); |
|
connect(m_generator, &LocaleGeneratorBase::needsFont, this, &KCMRegionAndLang::saveToConfigFile); |
|
|
|
// if we don't support auto locale generation for current system (BSD, musl etc.), userHasToGenerateManually regarded as success |
|
if (strcmp(m_generator->metaObject()->className(), "LocaleGeneratorBase") != 0) { |
|
connect(m_generator, &LocaleGeneratorBase::success, this, &KCMRegionAndLang::takeEffectNextTime); |
|
} else { |
|
connect(m_generator, &LocaleGeneratorBase::userHasToGenerateManually, this, &KCMRegionAndLang::takeEffectNextTime); |
|
} |
|
|
|
setQuickHelp(i18n("You can configure the formats used for time, dates, money and other numbers here.")); |
|
|
|
qmlRegisterAnonymousType<RegionAndLangSettings>("kcmregionandlang", 1); |
|
qmlRegisterAnonymousType<OptionsModel>("kcmregionandlang", 1); |
|
qmlRegisterAnonymousType<SelectedLanguageModel>("kcmregionandlang", 1); |
|
qmlRegisterType<LocaleListModel>("kcmregionandlang", 1, 0, "LocaleListModel"); |
|
qmlRegisterType<LanguageListModel>("kcmregionandlang", 1, 0, "LanguageListModel"); |
|
qRegisterMetaType<KCM_RegionAndLang::SettingType>(); |
|
qmlRegisterUncreatableMetaObject(KCM_RegionAndLang::staticMetaObject, "kcmregionandlang", 1, 0, "SettingType", "Error: SettingType is an enum"); |
|
} |
|
|
|
void KCMRegionAndLang::save() |
|
{ |
|
// assemble full locales in use |
|
QStringList locales; |
|
if (!settings()->isDefaultSetting(SettingType::Lang)) { |
|
locales.append(settings()->lang()); |
|
} |
|
if (!settings()->isDefaultSetting(SettingType::Numeric)) { |
|
locales.append(settings()->numeric()); |
|
} |
|
if (!settings()->isDefaultSetting(SettingType::Time)) { |
|
locales.append(settings()->time()); |
|
} |
|
if (!settings()->isDefaultSetting(SettingType::Measurement)) { |
|
locales.append(settings()->measurement()); |
|
} |
|
if (!settings()->isDefaultSetting(SettingType::Currency)) { |
|
locales.append(settings()->monetary()); |
|
} |
|
if (!settings()->isDefaultSetting(SettingType::PaperSize)) { |
|
locales.append(settings()->paperSize()); |
|
} |
|
if (settings()->isDefaultSetting(SettingType::Address)) { |
|
locales.append(settings()->address()); |
|
} |
|
if (settings()->isDefaultSetting(SettingType::NameStyle)) { |
|
locales.append(settings()->nameStyle()); |
|
} |
|
if (settings()->isDefaultSetting(SettingType::PhoneNumbers)) { |
|
locales.append(settings()->phoneNumbers()); |
|
} |
|
if (!settings()->language().isEmpty()) { |
|
QStringList languages = settings()->language().split(QLatin1Char(':')); |
|
for (const QString &lang : languages) { |
|
QString glibcLocale = toGlibcLocale(lang); |
|
if (!glibcLocale.isEmpty()) { |
|
locales.append(glibcLocale); |
|
} |
|
} |
|
} |
|
|
|
auto setLangCall = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.Accounts"), |
|
QStringLiteral("/org/freedesktop/Accounts/User%1").arg(getuid()), |
|
QStringLiteral("org.freedesktop.Accounts.User"), |
|
QStringLiteral("SetLanguage")); |
|
setLangCall.setArguments({settings()->lang()}); |
|
QDBusConnection::systemBus().asyncCall(setLangCall); |
|
|
|
if (!locales.isEmpty()) { |
|
Q_EMIT startGenerateLocale(); |
|
m_generator->localesGenerate(locales); |
|
} |
|
Q_EMIT saveClicked(); |
|
} |
|
|
|
void KCMRegionAndLang::saveToConfigFile() |
|
{ |
|
KQuickAddons::ManagedConfigModule::save(); |
|
} |
|
|
|
RegionAndLangSettings *KCMRegionAndLang::settings() const |
|
{ |
|
return m_settings; |
|
} |
|
|
|
OptionsModel *KCMRegionAndLang::optionsModel() const |
|
{ |
|
return m_optionsModel; |
|
} |
|
|
|
void KCMRegionAndLang::unset(SettingType setting) |
|
{ |
|
const char *entry = nullptr; |
|
if (setting == SettingType::Lang) { |
|
entry = "LANG"; |
|
settings()->setLang(settings()->defaultLangValue()); |
|
} else if (setting == SettingType::Numeric) { |
|
entry = "LC_NUMERIC"; |
|
settings()->setNumeric(settings()->defaultNumericValue()); |
|
} else if (setting == SettingType::Time) { |
|
entry = "LC_TIME"; |
|
settings()->setTime(settings()->defaultTimeValue()); |
|
} else if (setting == SettingType::Measurement) { |
|
entry = "LC_MEASUREMENT"; |
|
settings()->setMeasurement(settings()->defaultMeasurementValue()); |
|
} else if (setting == SettingType::Currency) { |
|
entry = "LC_MONETARY"; |
|
settings()->setMonetary(settings()->defaultMonetaryValue()); |
|
} else if (setting == SettingType::PaperSize) { |
|
entry = "LC_PAPER"; |
|
settings()->setPaperSize(settings()->defaultPaperSizeValue()); |
|
} else if (setting == SettingType::Address) { |
|
entry = "LC_ADDRESS"; |
|
settings()->setAddress(settings()->defaultAddressValue()); |
|
} else if (setting == SettingType::NameStyle) { |
|
entry = "LC_NAME"; |
|
settings()->setNameStyle(settings()->defaultNameStyleValue()); |
|
} else if (setting == SettingType::PhoneNumbers) { |
|
entry = "LC_TELEPHONE"; |
|
settings()->setPhoneNumbers(settings()->defaultPhoneNumbersValue()); |
|
} |
|
|
|
settings()->config()->group(QStringLiteral("Formats")).deleteEntry(entry); |
|
} |
|
|
|
void KCMRegionAndLang::reboot() |
|
{ |
|
auto method = QDBusMessage::createMethodCall(QStringLiteral("org.kde.LogoutPrompt"), |
|
QStringLiteral("/LogoutPrompt"), |
|
QStringLiteral("org.kde.LogoutPrompt"), |
|
QStringLiteral("promptReboot")); |
|
QDBusConnection::sessionBus().asyncCall(method); |
|
} |
|
|
|
bool KCMRegionAndLang::isGlibc() |
|
{ |
|
#ifdef OS_UBUNTU |
|
return true; |
|
#elif GLIBC_LOCALE |
|
return true; |
|
#else |
|
return false; |
|
#endif |
|
} |
|
|
|
QString KCMRegionAndLang::toGlibcLocale(const QString &lang) |
|
{ |
|
static bool inited = false; |
|
static std::unordered_map<QString, QString> map; |
|
|
|
if (!inited) { |
|
map = constructGlibcLocaleMap(); |
|
inited = true; |
|
} |
|
|
|
if (map.count(lang)) { |
|
return map[lang]; |
|
} else { |
|
return lang; |
|
} |
|
} |
|
|
|
std::unordered_map<QString, QString> KCMRegionAndLang::constructGlibcLocaleMap() |
|
{ |
|
std::unordered_map<QString, QString> localeMap; |
|
|
|
QDir glibcLocaleDir(QStringLiteral("/usr/share/i18n/locales")); |
|
auto availableLocales = glibcLocaleDir.entryList(QDir::Files); |
|
// not glibc system or corrupted system |
|
if (availableLocales.size() == 0) { |
|
return localeMap; |
|
} |
|
|
|
// map base locale code to actual glibc locale filename: "en" => ["en_US", "en_GB"] |
|
std::unordered_map<QString, std::vector<QString>> baseLocaleMap(availableLocales.size()); |
|
for (const auto &glibcLocale : availableLocales) { |
|
// we want only absolute base locale code, for sr@ijekavian and en_US, we get sr and en |
|
auto baseLocale = glibcLocale.split('_')[0].split('@')[0]; |
|
if (baseLocaleMap.count(baseLocale)) { |
|
baseLocaleMap[baseLocale].push_back(glibcLocale); |
|
} else { |
|
baseLocaleMap.insert({baseLocale, {glibcLocale}}); |
|
} |
|
} |
|
|
|
auto plasmaLocales = KLocalizedString::availableDomainTranslations(QByteArrayLiteral("plasmashell")).values(); |
|
for (const auto &plasmaLocale : plasmaLocales) { |
|
auto baseLocale = plasmaLocale.split('_')[0].split('@')[0]; |
|
if (baseLocaleMap.count(baseLocale)) { |
|
const auto &prefixedLocales = baseLocaleMap[baseLocale]; |
|
|
|
// if we have one to one match, use that. Eg. en_US to en_US |
|
auto fullMatch = std::find(prefixedLocales.begin(), prefixedLocales.end(), plasmaLocale); |
|
if (fullMatch != prefixedLocales.end()) { |
|
localeMap.insert({plasmaLocale, *fullMatch + ".UTF-8"}); |
|
continue; |
|
} |
|
|
|
// language name with same country code has higher priority, eg. es_ES > es_PA, de_DE > de_DE@euro |
|
auto mainLocale = plasmaLocale + "_" + plasmaLocale.toUpper(); |
|
fullMatch = std::find(prefixedLocales.begin(), prefixedLocales.end(), mainLocale); |
|
if (fullMatch != prefixedLocales.end()) { |
|
localeMap.insert({plasmaLocale, *fullMatch + ".UTF-8"}); |
|
continue; |
|
} |
|
|
|
// we try to match the locale with least characters diff (compare language code with country code, "sv" with "SE".lower() for "sv_SE"), |
|
// so ca@valencia matches with ca_ES@valencia |
|
// bad case: ca matches with ca_AD but not ca_ES |
|
int closestMatchIndex = 0; |
|
float minDiffPercentage = 1.0; |
|
std::array<int, 255> frequencyMap = {0}; |
|
for (QChar c : plasmaLocale) { |
|
// to lower so "sv_SE" has higher priority than "sv_FI" for language "sv" |
|
frequencyMap[int(c.toLower().toLatin1())]++; |
|
} |
|
|
|
int i = 0; |
|
for (const auto &glibcLocale : prefixedLocales) { |
|
auto duplicated = frequencyMap; |
|
int skipBase = baseLocale.size() + 1; // we skip "sv_" part of "sv_SE", eg. compare "SE" part with "sv" |
|
for (QChar c : glibcLocale) { |
|
if (skipBase--) { |
|
continue; |
|
} |
|
duplicated[int(c.toLower().toLatin1())]--; |
|
} |
|
int diffChars = std::reduce(duplicated.begin(), duplicated.end(), 0, [](int sum, int diff) { |
|
return sum + std::abs(diff); |
|
}); |
|
float diffPercentage = float(diffChars) / glibcLocale.size(); |
|
if (diffPercentage < minDiffPercentage) { |
|
minDiffPercentage = diffPercentage; |
|
closestMatchIndex = i; |
|
} |
|
i++; |
|
} |
|
localeMap.insert({plasmaLocale, prefixedLocales[closestMatchIndex] + ".UTF-8"}); |
|
} |
|
} |
|
return localeMap; |
|
} |
|
#include "kcmregionandlang.moc" |
|
#include "moc_kcmregionandlang.cpp"
|
|
|