diff --git a/src/corelib/compat/removed_api.cpp b/src/corelib/compat/removed_api.cpp index 28207d26d51..088b418aeff 100644 --- a/src/corelib/compat/removed_api.cpp +++ b/src/corelib/compat/removed_api.cpp @@ -625,6 +625,16 @@ QStringList QLocale::uiLanguages() const return uiLanguages(TagSeparator::Dash); } +QString QLocale::name() const +{ + return name(TagSeparator::Underscore); +} + +QString QLocale::bcp47Name() const +{ + return bcp47Name(TagSeparator::Dash); +} + #include "qurl.h" QUrl QUrl::fromEncoded(const QByteArray &input, ParsingMode mode) diff --git a/src/corelib/text/qlocale.cpp b/src/corelib/text/qlocale.cpp index bb38d827021..e3d0842561d 100644 --- a/src/corelib/text/qlocale.cpp +++ b/src/corelib/text/qlocale.cpp @@ -705,7 +705,7 @@ static QLocalePrivate *c_private() system locale. This is only intended as a way to let a platform plugin install its own system locale, overriding what might otherwise be provided for its class of platform (as Android does, differing from Linux), and to - let tests transiently over-ride the system or plugin-supplied one. As such, + let tests transiently override the system or plugin-supplied one. As such, there should not be diverse threads creating and destroying QSystemLocale instances concurrently, so no attempt is made at thread-safety in managing the stack. @@ -1367,6 +1367,12 @@ QLocale::Country QLocale::country() const with an arbitrary Unicode character or string. */ +Q_DECL_COLD_FUNCTION static void badSeparatorWarning(const char *method, char sep) +{ + qWarning("QLocale::%s(): Using non-ASCII separator '%c' (%02x) is unsupported", + method, sep, uint(uchar(sep))); +} + /*! \brief The short name of this locale. @@ -1385,8 +1391,13 @@ QLocale::Country QLocale::country() const \sa QLocale(), language(), script(), territory(), bcp47Name(), uiLanguages() */ -QString QLocale::name() const +QString QLocale::name(TagSeparator separator) const { + const char sep = char(separator); + if (uchar(sep) > 0x7f) { + badSeparatorWarning("name", sep); + return {}; + } const auto code = d->languageCode(); QLatin1StringView view{code.data()}; @@ -1398,7 +1409,7 @@ QString QLocale::name() const if (c == AnyTerritory) return view; - return view + u'_' + d->territoryCode(); + return view + QLatin1Char(sep) + d->territoryCode(); } template static inline @@ -1442,13 +1453,22 @@ T toIntegral_helper(const QLocalePrivate *d, QStringView str, bool *ok) locale name of the QLocale data; this need not be the language the user-interface should be in. - This function tries to conform the locale name to BCP47. + This function tries to conform the locale name to the IETF Best Common + Practice 47, defined by RFC 5646. It supports an optional \a separator + parameter which can be used to override the BCP47-specified use of a hyphen + to separate the tags. For use in IETF-defined protocols, however, the + default, QLocale::TagSeparator::Dash, should be retained. \sa name(), language(), territory(), script(), uiLanguages() */ -QString QLocale::bcp47Name() const +QString QLocale::bcp47Name(TagSeparator separator) const { - return QString::fromLatin1(d->bcp47Name()); + const char sep = char(separator); + if (uchar(sep) > 0x7f) { + badSeparatorWarning("bcp47Name", sep); + return {}; + } + return QString::fromLatin1(d->bcp47Name(sep)); } /*! @@ -4652,8 +4672,7 @@ QStringList QLocale::uiLanguages(TagSeparator separator) const const char sep = char(separator); QStringList uiLanguages; if (uchar(sep) > 0x7f) { - qWarning("QLocale::uiLanguages(): Using non-ASCII separator '%c' (%02x) is unsupported", - sep, uint(uchar(sep))); + badSeparatorWarning("uiLanguages", sep); return uiLanguages; } QList localeIds; @@ -4674,7 +4693,7 @@ QStringList QLocale::uiLanguages(TagSeparator separator) const // first. (Known issue, QTBUG-104930, on some macOS versions when in // locale en_DE.) Our translation system might have a translation for a // locale the platform doesn't believe in. - const QString name = QString::fromLatin1(d->bcp47Name(sep)); + const QString name = bcp47Name(separator); if (!name.isEmpty() && language() != C && !uiLanguages.contains(name)) { // That uses contains(name) as a cheap pre-test, but there may be an // entry that matches this on purging likely subtags. diff --git a/src/corelib/text/qlocale.h b/src/corelib/text/qlocale.h index ca437ed92d7..18e5a71cf7c 100644 --- a/src/corelib/text/qlocale.h +++ b/src/corelib/text/qlocale.h @@ -926,9 +926,14 @@ public: QT_DEPRECATED_VERSION_X_6_6("Use territory() instead") Country country() const; #endif - QString name() const; +#if QT_CORE_REMOVED_SINCE(6, 7) + QString name() const; QString bcp47Name() const; +#endif + QString name(TagSeparator separator = TagSeparator::Underscore) const; + QString bcp47Name(TagSeparator separator = TagSeparator::Dash) const; + QString nativeLanguageName() const; QString nativeTerritoryName() const; #if QT_DEPRECATED_SINCE(6, 6) diff --git a/tests/auto/corelib/text/qlocale/tst_qlocale.cpp b/tests/auto/corelib/text/qlocale/tst_qlocale.cpp index 78f3ce63595..469e2d6333f 100644 --- a/tests/auto/corelib/text/qlocale/tst_qlocale.cpp +++ b/tests/auto/corelib/text/qlocale/tst_qlocale.cpp @@ -770,14 +770,24 @@ void tst_QLocale::unixLocaleName_data() void tst_QLocale::unixLocaleName() { - QFETCH(QLocale::Language, lang); - QFETCH(QLocale::Territory, land); - QFETCH(QString, expect); + QFETCH(const QLocale::Language, lang); + QFETCH(const QLocale::Territory, land); + QFETCH(const QString, expect); + const auto expected = [expect](QChar ch) { + // Kludge around QString::replace() not being const. + QString copy = expect; + return copy.replace(u'_', ch); + }; QLocale::setDefault(QLocale(QLocale::C)); - QLocale locale(lang, land); + const QLocale locale(lang, land); QCOMPARE(locale.name(), expect); + QCOMPARE(locale.name(QLocale::TagSeparator::Dash), expected(u'-')); + QCOMPARE(locale.name(QLocale::TagSeparator{'|'}), expected(u'|')); + QTest::ignoreMessage(QtWarningMsg, "QLocale::name(): " + "Using non-ASCII separator '\u00ff' (ff) is unsupported"); + QCOMPARE(locale.name(QLocale::TagSeparator{'\xff'}), QString()); } void tst_QLocale::toReal_data() @@ -3923,8 +3933,20 @@ void tst_QLocale::bcp47Name_data() void tst_QLocale::bcp47Name() { - QFETCH(QString, expect); - QCOMPARE(QLocale(QLatin1String(QTest::currentDataTag())).bcp47Name(), expect); + QFETCH(const QString, expect); + const auto expected = [expect](QChar ch) { + // Kludge around QString::replace() not being const. + QString copy = expect; + return copy.replace(u'-', ch); + }; + + const auto locale = QLocale(QLatin1String(QTest::currentDataTag())); + QCOMPARE(locale.bcp47Name(), expect); + QCOMPARE(locale.bcp47Name(QLocale::TagSeparator::Underscore), expected(u'_')); + QCOMPARE(locale.bcp47Name(QLocale::TagSeparator{'|'}), expected(u'|')); + QTest::ignoreMessage(QtWarningMsg, "QLocale::bcp47Name(): " + "Using non-ASCII separator '\u00ff' (ff) is unsupported"); + QCOMPARE(locale.bcp47Name(QLocale::TagSeparator{'\xff'}), QString()); } #ifndef QT_NO_SYSTEMLOCALE