diff --git a/src/corelib/text/qlocale.cpp b/src/corelib/text/qlocale.cpp index 7fcfd3dfa6..fb21b24ada 100644 --- a/src/corelib/text/qlocale.cpp +++ b/src/corelib/text/qlocale.cpp @@ -4427,18 +4427,19 @@ QString QLocale::formattedDataSize(qint64 bytes, int precision, DataSizeFormats QStringList QLocale::uiLanguages() const { QStringList uiLanguages; - QList locales; + QList localeIds; #ifdef QT_NO_SYSTEMLOCALE constexpr bool isSystem = false; #else const bool isSystem = d->m_data == &systemLocaleData; if (isSystem) { uiLanguages = systemLocale()->query(QSystemLocale::UILanguages).toStringList(); - // ... but we need to include likely-adjusted forms of each of those, too: + // ... but we need to include likely-adjusted forms of each of those, too. + // For now, collect up locale Ids representing the entries, for later processing: for (const auto &entry : std::as_const(uiLanguages)) - locales.append(QLocale(entry)); - if (locales.isEmpty()) - locales.append(systemLocale()->fallbackLocale()); + localeIds.append(QLocaleId::fromName(entry)); + if (localeIds.isEmpty()) + localeIds.append(systemLocale()->fallbackLocale().d->m_data->id()); // If the system locale (isn't C and) didn't include itself in the list, // or as fallback, presume to know better than it and put its name // first. (Known issue, QTBUG-104930, on some macOS versions when in @@ -4453,20 +4454,17 @@ QStringList QLocale::uiLanguages() const return QLocaleId::fromName(entry).withLikelySubtagsRemoved() == mine; }; if (std::none_of(uiLanguages.constBegin(), uiLanguages.constEnd(), isMine)) { - locales.prepend(*this); + localeIds.prepend(d->m_data->id()); uiLanguages.prepend(name); } } } else #endif { - locales.append(*this); + localeIds.append(d->m_data->id()); } - for (qsizetype i = locales.size(); i-- > 0; ) { - const QLocale &locale = locales.at(i); - const auto data = locale.d->m_data; - QLocaleId id = data->id(); - + for (qsizetype i = localeIds.size(); i-- > 0; ) { + QLocaleId id = localeIds.at(i); qsizetype j; QByteArray prior; if (isSystem && i < uiLanguages.size()) { @@ -4477,35 +4475,49 @@ QStringList QLocale::uiLanguages() const j = i + 1; } else if (id.language_id == C) { // Attempt no likely sub-tag amendments to C: - uiLanguages.append(locale.name()); + uiLanguages.append(QString::fromLatin1(id.name())); continue; } else { // Plain locale or empty system uiLanguages; just append. - const QString name = locale.bcp47Name(); - uiLanguages.append(name); - prior = name.toLatin1(); + prior = id.name(); + uiLanguages.append(QString::fromLatin1(prior)); j = uiLanguages.size(); } const QLocaleId max = id.withLikelySubtagsAdded(); const QLocaleId min = max.withLikelySubtagsRemoved(); - id.script_id = 0; // For re-use as script-less variant. // Include minimal version (last) unless it's what our locale is derived from: - if (min.name() != prior) - uiLanguages.insert(j, QString::fromLatin1(min.name())); + if (auto name = min.name(); name != prior) + uiLanguages.insert(j, QString::fromLatin1(name)); else if (!isSystem) --j; // bcp47Name() matches min(): put more specific forms *before* it. - // Include scriptless version if likely-equivalent and distinct: - if (data->m_script_id && id != min && id.name() != prior - && id.withLikelySubtagsAdded() == max) { - uiLanguages.insert(j, QString::fromLatin1(id.name())); + if (id.script_id) { + // Include scriptless version if likely-equivalent and distinct: + id.script_id = 0; + if (id != min && id.withLikelySubtagsAdded() == max) { + if (auto name = id.name(); name != prior) + uiLanguages.insert(j, QString::fromLatin1(name)); + } + } + + if (!id.territory_id) { + Q_ASSERT(!min.territory_id); + Q_ASSERT(!id.script_id); // because we just cleared it. + // Include version with territory if it likely-equivalent and distinct: + id.territory_id = max.territory_id; + if (id != max && id.withLikelySubtagsAdded() == max) { + if (auto name = id.name(); name != prior) + uiLanguages.insert(j, QString::fromLatin1(name)); + } } // Include version with all likely sub-tags (first) if distinct from the rest: - if (max != min && max != id && max.name() != prior) - uiLanguages.insert(j, QString::fromLatin1(max.name())); + if (max != min && max != id) { + if (auto name = max.name(); name != prior) + uiLanguages.insert(j, QString::fromLatin1(name)); + } } return uiLanguages; } diff --git a/src/corelib/text/qlocale_p.h b/src/corelib/text/qlocale_p.h index b8afbd0bc8..fc3d221971 100644 --- a/src/corelib/text/qlocale_p.h +++ b/src/corelib/text/qlocale_p.h @@ -121,7 +121,7 @@ namespace QIcu { struct QLocaleId { - [[nodiscard]] static QLocaleId fromName(QStringView name); + [[nodiscard]] Q_AUTOTEST_EXPORT static QLocaleId fromName(QStringView name); [[nodiscard]] inline bool operator==(QLocaleId other) const { return language_id == other.language_id && script_id == other.script_id && territory_id == other.territory_id; } [[nodiscard]] inline bool operator!=(QLocaleId other) const diff --git a/tests/auto/corelib/text/qlocale/tst_qlocale.cpp b/tests/auto/corelib/text/qlocale/tst_qlocale.cpp index f0fef7093a..c817d02634 100644 --- a/tests/auto/corelib/text/qlocale/tst_qlocale.cpp +++ b/tests/auto/corelib/text/qlocale/tst_qlocale.cpp @@ -122,8 +122,10 @@ private slots: void bcp47Name_data(); void bcp47Name(); +#ifdef QT_BUILD_INTERNAL void systemLocale_data(); void systemLocale(); +#endif #ifndef QT_NO_SYSTEMLOCALE void systemLocaleDayAndMonthNames_data(); @@ -2939,18 +2941,22 @@ void tst_QLocale::uiLanguages_data() << QLocale(QLocale::Chinese, QLocale::SimplifiedHanScript, QLocale::China) << QStringList{QString("zh-Hans-CN"), QString("zh-CN"), QString("zh")}; - // TODO: test actual system backends correctly handle locales with - // script-specificity (script listed first is the default, in CLDR v40): - // az_{Latn,Cyrl}_AZ, bs_{Latn,Cyrl}_BA, sr_{Cyrl,Latn}_{BA,RS,XK,UZ}, - // sr_{Latn,Cyrl}_ME, ff_{Latn,Adlm}_{BF,CM,GH,GM,GN,GW,LR,MR,NE,NG,SL,SN}, - // shi_{Tfng,Latn}_MA, vai_{Vaii,Latn}_LR, zh_{Hant,Hans}_{MO,HK} + // We presently map und (or any other unrecognized language) to C, ignoring + // what a sub-tag lookup would surely find us. + QTest::newRow("und_US") << QLocale("und_US") << QStringList{QString("C")}; + QTest::newRow("und_Latn") << QLocale("und_Latn") << QStringList{QString("C")}; } void tst_QLocale::uiLanguages() { + // Compare systemLocale(), which tests the same for a stub system locale. QFETCH(const QLocale, locale); QFETCH(const QStringList, all); + auto reporter = qScopeGuard([&locale]() { + qDebug("\n\t%s", qPrintable(locale.uiLanguages().join(u"\n\t"))); + }); QCOMPARE(locale.uiLanguages(), all); + reporter.dismiss(); } void tst_QLocale::weekendDays() @@ -3229,19 +3235,31 @@ void tst_QLocale::bcp47Name() QCOMPARE(QLocale(QLatin1String(QTest::currentDataTag())).bcp47Name(), expect); } +#ifdef QT_BUILD_INTERNAL class MySystemLocale : public QSystemLocale { public: - MySystemLocale(const QString &locale) : m_name(locale), m_locale(locale) + MySystemLocale(const QString &locale) + : m_name(locale), m_id(QLocaleId::fromName(locale)), m_locale(locale) { } QVariant query(QueryType type, QVariant /*in*/) const override { - if (type == UILanguages) { + switch (type) { + case UILanguages: if (m_name == u"en-DE") // QTBUG-104930: simulate macOS's list not including m_name. return QVariant(QStringList{QStringLiteral("en-GB"), QStringLiteral("de-DE")}); return QVariant(QStringList{m_name}); + case LanguageId: + return m_id.language_id; + case TerritoryId: + return m_id.territory_id; + case ScriptId: + return m_id.script_id; + + default: + break; } return QVariant(); } @@ -3253,6 +3271,7 @@ public: private: const QString m_name; + const QLocaleId m_id; const QLocale m_locale; }; @@ -3266,6 +3285,13 @@ void tst_QLocale::systemLocale_data() QTest::addRow("catalan") << QString("ca") << QLocale::Catalan << QStringList{QStringLiteral("ca"), QStringLiteral("ca-Latn-ES"), QStringLiteral("ca-ES")}; + QTest::addRow("catalan-spain") + << QString("ca-ES") << QLocale::Catalan + << QStringList{QStringLiteral("ca-ES"), QStringLiteral("ca-Latn-ES"), QStringLiteral("ca")}; + QTest::addRow("catalan-latin") + << QString("ca-Latn") << QLocale::Catalan + << QStringList{QStringLiteral("ca-Latn"), QStringLiteral("ca-Latn-ES"), + QStringLiteral("ca-ES"), QStringLiteral("ca")}; QTest::addRow("ukrainian") << QString("uk") << QLocale::Ukrainian << QStringList{QStringLiteral("uk"), QStringLiteral("uk-Cyrl-UA"), QStringLiteral("uk-UA")}; @@ -3278,16 +3304,55 @@ void tst_QLocale::systemLocale_data() QTest::addRow("german") << QString("de") << QLocale::German << QStringList{QStringLiteral("de"), QStringLiteral("de-Latn-DE"), QStringLiteral("de-DE")}; + QTest::addRow("german-britain") + << QString("de-GB") << QLocale::German + << QStringList{QStringLiteral("de-GB"), QStringLiteral("de-Latn-GB")}; QTest::addRow("chinese-min") << QString("zh") << QLocale::Chinese << QStringList{QStringLiteral("zh"), QStringLiteral("zh-Hans-CN"), QStringLiteral("zh-CN")}; QTest::addRow("chinese-full") << QString("zh-Hans-CN") << QLocale::Chinese << QStringList{QStringLiteral("zh-Hans-CN"), QStringLiteral("zh-CN"), QStringLiteral("zh")}; + + // For C, it should preserve what the system gave us but only add "C", never anything more: + QTest::addRow("C") << QString("C") << QLocale::C << QStringList{QStringLiteral("C")}; + QTest::addRow("C-Latn") + << QString("C-Latn") << QLocale::C + << QStringList{QStringLiteral("C-Latn"), QStringLiteral("C")}; + QTest::addRow("C-US") + << QString("C-US") << QLocale::C + << QStringList{QStringLiteral("C-US"), QStringLiteral("C")}; + QTest::addRow("C-Latn-US") + << QString("C-Latn-US") << QLocale::C + << QStringList{QStringLiteral("C-Latn-US"), QStringLiteral("C")}; + QTest::addRow("C-Hans") + << QString("C-Hans") << QLocale::C + << QStringList{QStringLiteral("C-Hans"), QStringLiteral("C")}; + QTest::addRow("C-CN") + << QString("C-CN") << QLocale::C + << QStringList{QStringLiteral("C-CN"), QStringLiteral("C")}; + QTest::addRow("C-Hans-CN") + << QString("C-Hans-CN") << QLocale::C + << QStringList{QStringLiteral("C-Hans-CN"), QStringLiteral("C")}; + + QTest::newRow("und-US") + << QString("und-US") << QLocale::C + << QStringList{QStringLiteral("und-US"), QStringLiteral("C")}; + + QTest::newRow("und-Latn") + << QString("und-Latn") << QLocale::C + << QStringList{QStringLiteral("und-Latn"), QStringLiteral("C")}; + + // TODO: test actual system backends correctly handle locales with + // script-specificity (script listed first is the default, in CLDR v40): + // az_{Latn,Cyrl}_AZ, bs_{Latn,Cyrl}_BA, sr_{Cyrl,Latn}_{BA,RS,XK,UZ}, + // sr_{Latn,Cyrl}_ME, ff_{Latn,Adlm}_{BF,CM,GH,GM,GN,GW,LR,MR,NE,NG,SL,SN}, + // shi_{Tfng,Latn}_MA, vai_{Vaii,Latn}_LR, zh_{Hant,Hans}_{MO,HK} } void tst_QLocale::systemLocale() { + // Compare uiLanguages(), which tests this for CLDR-derived locales. QLocale originalLocale; QLocale originalSystemLocale = QLocale::system(); @@ -3306,9 +3371,11 @@ void tst_QLocale::systemLocale() reporter.dismiss(); } + // Verify MySystemLocale tidy-up restored prior state: QCOMPARE(QLocale(), originalLocale); QCOMPARE(QLocale::system(), originalSystemLocale); } +#endif // QT_BUILD_INTERNAL #ifndef QT_NO_SYSTEMLOCALE