File indexing completed on 2024-10-06 03:31:32

0001 // SPDX-FileCopyrightText: 1999-2003 Hans Petter Bieker <bieker@kde.org>
0002 // SPDX-FileCopyrightText: 2007 David Jarvie <djarvie@kde.org>
0003 // SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu>
0004 // SPDX-License-Identifier: LGPL-2.0-or-later
0005 
0006 #include "languagelistmodel.h"
0007 #include <KConfig>
0008 #include <KConfigGroup>
0009 #include <KLocalizedString>
0010 #include <QDebug>
0011 #include <QDir>
0012 #include <QLocale>
0013 #include <QStandardPaths>
0014 #include <qstringliteral.h>
0015 
0016 bool operator<(const LanguageListModel::Language &a, const LanguageListModel::Language &b)
0017 {
0018     return a.name < b.name;
0019 }
0020 
0021 static QString nameFromEntryFile(const QString &entryFile)
0022 {
0023     const KConfig entry(entryFile, KConfig::SimpleConfig);
0024     const KConfigGroup group(&entry, QStringLiteral("KCM Locale"));
0025     return group.readEntry("Name", QString());
0026 }
0027 
0028 LanguageListModel::LanguageListModel(QObject *parent)
0029     : QAbstractListModel(parent)
0030 {
0031     const QStringList localeDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("locale"), QStandardPaths::LocateDirectory);
0032     for (const QString &localeDir : localeDirs) {
0033         const QStringList entries = QDir(localeDir).entryList(QDir::Dirs, QDir::Name);
0034         for (const QString &languageCode : entries) {
0035             const QString entryFile = localeDir + QLatin1Char('/') + languageCode + QStringLiteral("/kf5_entry.desktop");
0036             if (QFile::exists(entryFile)) {
0037                 QString text;
0038                 const QString entryFile =
0039                     QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("locale/") + languageCode + QLatin1String("/kf5_entry.desktop"));
0040                 if (QFile::exists(entryFile)) {
0041                     text = nameFromEntryFile(entryFile);
0042                 }
0043 
0044                 if (text.isEmpty()) {
0045                     text = languageCode;
0046                     QLocale locale(languageCode);
0047                     if (locale != QLocale::c()) {
0048                         text = locale.nativeLanguageName();
0049                         // For some languages the native name might be empty.
0050                         // In this case use the non native language name as fallback.
0051                         // See: QTBUG-51323
0052                         text = text.isEmpty() ? QLocale::languageToString(locale.language()) : text;
0053                     }
0054                 }
0055 
0056                 m_availableLanguages.append(LanguageListModel::Language{languageCode, text});
0057             }
0058         }
0059     }
0060 }
0061 
0062 int LanguageListModel::rowCount(const QModelIndex &parent) const
0063 {
0064     return parent.isValid() ? 0 : m_availableLanguages.size();
0065 }
0066 
0067 QVariant LanguageListModel::data(const QModelIndex &index, int role) const
0068 {
0069     Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid));
0070 
0071     const auto &language = m_availableLanguages[index.row()];
0072 
0073     switch (role) {
0074     case Qt::DisplayRole:
0075     case NativeNameRole:
0076         return language.name;
0077     case LanguageCodeRole:
0078         return language.locale;
0079     case FlagRole:
0080         return flagFromName(language.locale);
0081     }
0082     return {};
0083 }
0084 
0085 QString LanguageListModel::languageName(const QString &language) const
0086 {
0087     const QStringList localeDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("locale"), QStandardPaths::LocateDirectory);
0088     for (const QString &localeDir : localeDirs) {
0089         const QString entryFile = localeDir + QLatin1Char('/') + language + QStringLiteral("/kf5_entry.desktop");
0090         if (QFile::exists(entryFile)) {
0091             QString text;
0092             const QString entryFile =
0093                 QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("locale/") + language + QLatin1String("/kf5_entry.desktop"));
0094             if (QFile::exists(entryFile)) {
0095                 text = nameFromEntryFile(entryFile);
0096             }
0097 
0098             if (!text.isEmpty()) {
0099                 return text;
0100             }
0101         }
0102     }
0103 
0104     QLocale locale(language);
0105     return locale.nativeLanguageName();
0106 }
0107 
0108 QString LanguageListModel::flagFromName(const QString &language) const
0109 {
0110     QString flagCode;
0111     const QStringList split = QLocale(language).name().split(QLatin1Char('_'));
0112     if (split.size() > 1) {
0113         flagCode = split[1].toLower();
0114     }
0115 
0116     static constexpr auto surrogatePairCodePoint = 0xD83C;
0117     static constexpr auto flagCodePointStart = 0xDDE6; // https://en.wikipedia.org/wiki/Regional_indicator_symbol
0118     static constexpr auto offsetCodePointA = QLatin1Char('A').unicode(); // offset from 0, the flag code points have the same offsets
0119     static constexpr auto basePoint = flagCodePointStart - offsetCodePointA;
0120 
0121     QString emoji;
0122     for (const auto &c : flagCode) {
0123         emoji.append(QChar(surrogatePairCodePoint));
0124         emoji.append(QChar(basePoint + c.toUpper().unicode()));
0125     }
0126 
0127     if (emoji.isEmpty()) {
0128         return QString();
0129     }
0130 
0131     return emoji;
0132 }
0133 
0134 QHash<int, QByteArray> LanguageListModel::roleNames() const
0135 {
0136     return {
0137         {NativeNameRole, QByteArrayLiteral("nativeName")},
0138         {LanguageCodeRole, QByteArrayLiteral("languageCode")},
0139         {FlagRole, QByteArrayLiteral("flag")},
0140     };
0141 }
0142 
0143 #include "moc_languagelistmodel.cpp"