/* languagelistmodel.h SPDX-FileCopyrightText: 2021 Han Young SPDX-FileCopyrightText: 2019 Kevin Ottens SPDX-License-Identifier: GPL-2.0-or-later */ #include "languagelistmodel.h" #include "exampleutility.h" #include "kcm_regionandlang_debug.h" #include "kcmregionandlang.h" #include "regionandlangsettings.h" using namespace KCM_RegionAndLang; LanguageListModel::LanguageListModel(QObject *parent) : QAbstractListModel(parent) , m_selectedLanguageModel(new SelectedLanguageModel(this)) { connect(this, &LanguageListModel::isPreviewExampleChanged, this, &LanguageListModel::exampleChanged); connect(m_selectedLanguageModel, &SelectedLanguageModel::exampleChanged, this, &LanguageListModel::exampleChanged); m_availableLanguages = KLocalizedString::availableDomainTranslations("plasmashell").values(); m_availableLanguages.sort(); m_availableLanguages.push_front(QStringLiteral("C")); } int LanguageListModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) return m_availableLanguages.size(); } QVariant LanguageListModel::data(const QModelIndex &index, int role) const { const auto row = index.row(); if (row < 0 || row >= m_availableLanguages.size()) { return {}; } switch (role) { case NativeName: return languageCodeToName(m_availableLanguages.at(row)); case LanguageCode: return m_availableLanguages.at(row); case Flag: { QString flagCode; const QStringList split = QLocale(m_availableLanguages.at(row)).name().split(QLatin1Char('_')); if (split.size() > 1) { flagCode = split[1].toLower(); } return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf5/locale/countries/%1/flag.png").arg(flagCode)); } } Q_UNREACHABLE(); return {}; } QHash LanguageListModel::roleNames() const { return {{NativeName, QByteArrayLiteral("nativeName")}, {LanguageCode, QByteArrayLiteral("languageCode")}, {Flag, QByteArrayLiteral("flag")}}; } QString LanguageListModel::languageCodeToName(const QString &languageCode) { const QLocale locale(languageCode); const QString languageName = locale.nativeLanguageName(); if (languageName.isEmpty()) { return languageCode; } if (languageCode.contains(QLatin1Char('@'))) { return i18nc("%1 is language name, %2 is language code name", "%1 (%2)", languageName, languageCode); } // KDE languageCode got translated by QLocale to a locale code we also have on // the list. Currently this only happens with pt that gets translated to pt_BR. if (languageCode == QStringLiteral("pt_BR")) { return i18nc("%1 is português in system locale name, Brazil is to distinguish European português and Brazilian português", "%1 (Brazil)", languageName); } return languageName; } int LanguageListModel::currentIndex() const { return m_index; } void LanguageListModel::setCurrentIndex(int index) { if (index == m_index || index < 0 || index >= m_availableLanguages.size()) { return; } m_index = index; Q_EMIT exampleChanged(); } QString LanguageListModel::exampleHelper(const std::function &func) const { if (!m_settings) { return {}; } QString result = func(QLocale(m_settings->langWithFallback())); if (m_isPreviewExample) { if (m_index < 0) { result = func(QLocale(m_settings->langWithFallback())); } else { result = func(QLocale(m_availableLanguages[m_index])); } } return result; } QString LanguageListModel::numberExample() const { return exampleHelper(Utility::numericExample); } QString LanguageListModel::currencyExample() const { return exampleHelper(Utility::monetaryExample); } QString LanguageListModel::timeExample() const { return exampleHelper(Utility::timeExample); } QString LanguageListModel::paperSizeExample() const { return exampleHelper(Utility::paperSizeExample); } QString LanguageListModel::metric() const { return exampleHelper(Utility::measurementExample); } void LanguageListModel::setRegionAndLangSettings(QObject *settings) { if (auto *regionandlangsettings = dynamic_cast(settings)) { m_settings = regionandlangsettings; m_selectedLanguageModel->setRegionAndLangSettings(regionandlangsettings); Q_EMIT exampleChanged(); } } bool LanguageListModel::isPreviewExample() const { return m_isPreviewExample; } void LanguageListModel::setIsPreviewExample(bool preview) { m_isPreviewExample = preview; } void SelectedLanguageModel::setRegionAndLangSettings(RegionAndLangSettings *settings) { m_settings = settings; beginResetModel(); if (m_settings->language().isEmpty() && m_settings->isDefaultSetting(SettingType::Lang)) { // no language but have lang m_selectedLanguages = {m_settings->lang()}; m_selectedLanguages.first().remove(QStringLiteral(".UTF-8")); } else if (!m_settings->language().isEmpty()) { // have language, ignore lang m_selectedLanguages = m_settings->language().split(QLatin1Char(':')); } else { // have nothing, figure out from env QString lang = envLang(); QString language = envLanguage(); if (!language.isEmpty()) { QStringList langlist = language.split(QLatin1Char(':')); for (QString &lang : langlist) { lang = lang.split(QLatin1Char('.'))[0]; } m_selectedLanguages = langlist; } else if (!lang.isEmpty()) { lang.remove(QStringLiteral(".UTF-8")); m_selectedLanguages = {lang}; } m_hasImplicitLang = true; Q_EMIT hasImplicitLangChanged(); } endResetModel(); // check for invalid lang if (const QString lang = KCMRegionAndLang::toGlibcLocale(m_selectedLanguages.front()); lang.isEmpty()) { m_unsupportedLanguage = m_selectedLanguages.front(); Q_EMIT unsupportedLanguageChanged(); } else if (!m_unsupportedLanguage.isEmpty()) { m_unsupportedLanguage.clear(); Q_EMIT unsupportedLanguageChanged(); } } SelectedLanguageModel *LanguageListModel::selectedLanguageModel() const { return m_selectedLanguageModel; } int SelectedLanguageModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) return m_selectedLanguages.size(); } QVariant SelectedLanguageModel::data(const QModelIndex &index, int role) const { Q_UNUSED(role) const auto row = index.row(); if (row < 0 || row > m_selectedLanguages.size()) { return {}; } // "add Language" Item if (row == m_selectedLanguages.size()) { return {}; } return LanguageListModel::languageCodeToName(m_selectedLanguages.at(row)); } bool SelectedLanguageModel::shouldWarnMultipleLang() const { if (m_selectedLanguages.size() >= 2) { if (m_selectedLanguages.front() == QStringLiteral("en_US")) { return true; } } return false; } void SelectedLanguageModel::move(int from, int to) { if (from == to || from < 0 || from >= m_selectedLanguages.size() || to < 0 || to >= m_selectedLanguages.size()) { return; } if (m_hasImplicitLang) { m_hasImplicitLang = false; Q_EMIT hasImplicitLangChanged(); } beginResetModel(); m_selectedLanguages.move(from, to); endResetModel(); saveLanguages(); Q_EMIT shouldWarnMultipleLangChanged(); Q_EMIT exampleChanged(); } void SelectedLanguageModel::remove(int index) { if (index < 0 || index >= m_selectedLanguages.size()) { return; } beginRemoveRows(QModelIndex(), index, index); m_selectedLanguages.removeAt(index); endRemoveRows(); saveLanguages(); Q_EMIT shouldWarnMultipleLangChanged(); Q_EMIT exampleChanged(); } void SelectedLanguageModel::addLanguage(const QString &lang) { if (lang.isEmpty() || m_selectedLanguages.indexOf(lang) != -1) { return; } // fix Kirigami.SwipeListItem doesn't connect to Actions' visible property. // Reset model enforce a refresh of delegate beginResetModel(); if (m_hasImplicitLang) { m_hasImplicitLang = false; Q_EMIT hasImplicitLangChanged(); } m_selectedLanguages.push_back(lang); endResetModel(); saveLanguages(); Q_EMIT shouldWarnMultipleLangChanged(); Q_EMIT exampleChanged(); } void SelectedLanguageModel::replaceLanguage(int index, const QString &lang) { if (index < 0 || index >= m_selectedLanguages.size() || lang.isEmpty()) { return; } int existLangIndex = m_selectedLanguages.indexOf(lang); // return if no change, but allow change implicit lang to explicit if (existLangIndex == index && !m_hasImplicitLang) { return; } beginResetModel(); m_selectedLanguages[index] = lang; if (!m_hasImplicitLang) { // delete duplicate lang if (existLangIndex != -1) { m_selectedLanguages.removeAt(existLangIndex); } } else { m_hasImplicitLang = false; Q_EMIT hasImplicitLangChanged(); } endResetModel(); saveLanguages(); Q_EMIT shouldWarnMultipleLangChanged(); Q_EMIT exampleChanged(); } void SelectedLanguageModel::saveLanguages() { // implicit lang means no change if (!m_settings || m_hasImplicitLang) { return; } if (m_selectedLanguages.empty()) { m_settings->setLang(m_settings->defaultLangValue()); m_settings->config()->group(QStringLiteral("Formats")).deleteEntry("lang"); m_settings->config()->group(QStringLiteral("Translations")).deleteEntry("language"); } else { QString lang = KCMRegionAndLang::toGlibcLocale(m_selectedLanguages.front()); if (lang.isEmpty()) { m_unsupportedLanguage = m_selectedLanguages.front(); Q_EMIT unsupportedLanguageChanged(); } else { if (!m_unsupportedLanguage.isEmpty()) { m_unsupportedLanguage.clear(); Q_EMIT unsupportedLanguageChanged(); } // don't set LANG in non glibc systems, because in non glibc systems KCMRegionAndLang::toGlibcLocale return the language itself if (KCMRegionAndLang::isGlibc()) { m_settings->setLang(lang); } } QString languages; for (auto i = m_selectedLanguages.cbegin(); i != m_selectedLanguages.cend(); i++) { languages.push_back(*i); // no ':' at end if (i + 1 != m_selectedLanguages.cend()) { languages.push_back(QLatin1Char(':')); } } m_settings->setLanguage(languages); } } bool SelectedLanguageModel::hasImplicitLang() const { return m_hasImplicitLang; } const QString &SelectedLanguageModel::unsupportedLanguage() const { return m_unsupportedLanguage; } QString SelectedLanguageModel::envLang() const { return qEnvironmentVariable("LANG"); } QString SelectedLanguageModel::envLanguage() const { return qEnvironmentVariable("LANGUAGE"); }