Give QLocale::uiLanguages() a separator parameter

It has always returned dash-joined forms of the locale names, and
callers who need an underscore-joined form have been obliged to
replace('-', '_') before using them. Given that everything it adds to
the list comes from QLocaleId methods that accept a separator, it's
trivial to let it offer the same choice to its callers and save them
this hassle.

Amended code in QTranslater and QMimeType to save them that hassle.

[ChangeLog][CoreLib][QLocale] QLocale::uiLanguages() now lets the
caller choose what separator to use between the tags that make up each
locale-identifier in the list returned.

Change-Id: I91fcd0b988d9a64e0e9ad9e851f6cb8c1be8ae50
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Edward Welbourne 2023-07-27 15:02:56 +02:00
parent d7bad11a87
commit 91e70f239e
7 changed files with 118 additions and 31 deletions

View File

@ -618,6 +618,13 @@ QStringView QXmlStreamAttributes::value(QLatin1StringView qualifiedName) const
#if QT_CORE_REMOVED_SINCE(6, 7)
#include "qlocale.h"
QStringList QLocale::uiLanguages() const
{
return uiLanguages(TagSeparator::Dash);
}
#include "qurl.h"
QUrl QUrl::fromEncoded(const QByteArray &input, ParsingMode mode)

View File

@ -627,7 +627,7 @@ static QString find_translation(const QLocale & locale,
// "prefix_en_us.qm" won't be found under the "en_US" locale. Note
// that the Qt resource system is always case-sensitive, even on
// Windows (in other words: this codepath is *not* UNIX-only).
QStringList languages = locale.uiLanguages();
QStringList languages = locale.uiLanguages(QLocale::TagSeparator::Underscore);
for (int i = languages.size()-1; i >= 0; --i) {
QString lang = languages.at(i);
QString lowerLang = lang.toLower();
@ -636,8 +636,6 @@ static QString find_translation(const QLocale & locale,
}
for (QString localeName : std::as_const(languages)) {
localeName.replace(u'-', u'_');
// try the complete locale name first and progressively truncate from
// the end until a matching language tag is found (with or without suffix)
for (;;) {

View File

@ -221,16 +221,16 @@ QString QMimeType::comment() const
{
QMimeDatabasePrivate::instance()->loadMimeTypePrivate(const_cast<QMimeTypePrivate&>(*d));
QStringList languageList = QLocale().uiLanguages();
qsizetype defaultIndex = languageList.indexOf(u"en-US"_s);
QStringList languageList = QLocale().uiLanguages(QLocale::TagSeparator::Underscore);
qsizetype defaultIndex = languageList.indexOf(u"en_US"_s);
// Include the default locale as fall-back.
if (defaultIndex >= 0) {
// en_US is generally the default, and may be omitted from the
// overtly-named locales in the MIME type's data (QTBUG-105007).
++defaultIndex; // Skip over en-US.
// That's typically followed by en-Latn-US and en (in that order):
if (defaultIndex < languageList.size() && languageList.at(defaultIndex) == u"en-Latn-US")
++defaultIndex; // Skip over en_US.
// That's typically followed by en_Latn_US and en (in that order):
if (defaultIndex < languageList.size() && languageList.at(defaultIndex) == u"en_Latn_US")
++defaultIndex;
if (defaultIndex < languageList.size() && languageList.at(defaultIndex) == u"en")
++defaultIndex;
@ -241,9 +241,7 @@ QString QMimeType::comment() const
languageList.insert(defaultIndex, u"default"_s);
for (const QString &language : std::as_const(languageList)) {
// uiLanguages() uses '-' as separator, MIME database uses '_'
const QString lang
= language == "C"_L1 ? u"en_US"_s : QString(language).replace(u'-', u'_');
const QString lang = language == "C"_L1 ? u"en_US"_s : language;
QString comm = d->localeComments.value(lang);
if (!comm.isEmpty())
return comm;

View File

@ -1340,6 +1340,33 @@ QLocale::Country QLocale::country() const
}
#endif
/*!
\since 6.7
\enum QLocale::TagSeparator
Indicate how to combine the parts that make up a locale identifier.
A locale identifier may be made up of several tags, indicating language,
script and territory (plus, potentially, other details), joined together to
form the identifier. Various standards and conventional forms use either a
dash (the Unicode HYPHEN-MINUS, U+002D) or an underscore (LOW LINE, U+005F).
Different clients of QLocale may thus need one or the other.
\value Dash Use \c{'-'}, the dash or hyphen character.
\value Underscore Use \c{'_'}, the underscore character.
\note Although dash and underscore are the only separators used in public
standards (as at 2023), it is possible to cast any \l
{https://en.cppreference.com/w/cpp/language/ascii} {ASCII} character to this
type if a non-standard ASCII separator is needed. Casting a non-ASCII
character (with decimal value above 127) is not supported: such values are
reserved for future use as enum members if some public standard ever uses a
non-ASCII separator. It is, of course, possible to use QString::replace() to
replace the separator used by a function taking a parameter of this type
with an arbitrary Unicode character or string.
*/
/*!
\brief The short name of this locale.
@ -4602,21 +4629,33 @@ QString QLocale::formattedDataSize(qint64 bytes, int precision, DataSizeFormats
\since 4.8
\brief List of locale names for use in selecting translations
Each entry in the returned list is the dash-joined name of a locale,
suitable to the user's preferences for what to translate the UI into. For
example, if the user has configured their system to use English as used in
the USA, the list would be "en-Latn-US", "en-US", "en". The order of entries
is the order in which to check for translations; earlier items in the list
are to be preferred over later ones.
Each entry in the returned list is the name of a locale suitable to the
user's preferences for what to translate the UI into. Where a name in the
list is composed of several tags, they are joined as indicated by \a
separator.
For example, using the default separator QLocale::TagSeparator::Dash, if the
user has configured their system to use English as used in the USA, the list
would be "en-Latn-US", "en-US", "en". The order of entries is the order in
which to check for translations; earlier items in the list are to be
preferred over later ones. If your translation files use underscores, rather
than dashes, to separate locale tags, pass QLocale::TagSeparator::Underscore
as \a separator.
Most likely you do not need to use this function directly, but just pass the
QLocale object to the QTranslator::load() function.
\sa QTranslator, bcp47Name()
*/
QStringList QLocale::uiLanguages() const
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)));
return uiLanguages;
}
QList<QLocaleId> localeIds;
#ifdef QT_NO_SYSTEMLOCALE
constexpr bool isSystem = false;
@ -4635,7 +4674,7 @@ QStringList QLocale::uiLanguages() 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 = bcp47Name();
const QString name = QString::fromLatin1(d->bcp47Name(sep));
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.
@ -4665,11 +4704,11 @@ QStringList QLocale::uiLanguages() const
j = i + 1;
} else if (id.language_id == C) {
// Attempt no likely sub-tag amendments to C:
uiLanguages.append(QString::fromLatin1(id.name()));
uiLanguages.append(QString::fromLatin1(id.name(sep)));
continue;
} else {
// Plain locale or empty system uiLanguages; just append.
prior = id.name();
prior = id.name(sep);
uiLanguages.append(QString::fromLatin1(prior));
j = uiLanguages.size();
}
@ -4678,7 +4717,7 @@ QStringList QLocale::uiLanguages() const
const QLocaleId min = max.withLikelySubtagsRemoved();
// Include minimal version (last) unless it's what our locale is derived from:
if (auto name = min.name(); name != prior)
if (auto name = min.name(sep); name != prior)
uiLanguages.insert(j, QString::fromLatin1(name));
else if (!isSystem)
--j; // bcp47Name() matches min(): put more specific forms *before* it.
@ -4687,7 +4726,7 @@ QStringList QLocale::uiLanguages() const
// 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)
if (auto name = id.name(sep); name != prior)
uiLanguages.insert(j, QString::fromLatin1(name));
}
}
@ -4698,14 +4737,14 @@ QStringList QLocale::uiLanguages() const
// 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)
if (auto name = id.name(sep); 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) {
if (auto name = max.name(); name != prior)
if (auto name = max.name(sep); name != prior)
uiLanguages.insert(j, QString::fromLatin1(name));
}
}

View File

@ -885,6 +885,8 @@ public:
FloatingPointShortest = -128
};
enum class TagSeparator : char { Dash = '-', Underscore = '_' };
enum CurrencySymbolFormat {
CurrencyIsoCode,
CurrencySymbol,
@ -1059,7 +1061,10 @@ public:
QString formattedDataSize(qint64 bytes, int precision = 2, DataSizeFormats format = DataSizeIecFormat) const;
#if QT_CORE_REMOVED_SINCE(6, 7)
QStringList uiLanguages() const;
#endif
QStringList uiLanguages(TagSeparator separator = TagSeparator::Dash) const;
enum LanguageCodeType {
ISO639Part1 = 1 << 0,

View File

@ -119,7 +119,7 @@ void tst_QTranslator::load()
void tst_QTranslator::loadLocale()
{
QLocale locale;
auto localeName = locale.uiLanguages().value(0).replace('-', '_');
auto localeName = locale.uiLanguages(QLocale::TagSeparator::Underscore).value(0);
if (localeName.isEmpty())
QSKIP("This test requires at least one available UI language.");

View File

@ -3602,11 +3602,51 @@ void tst_QLocale::uiLanguages()
// Compare mySystemLocale(), which tests the same for a custom 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();
const auto expected = [all](QChar sep) {
QStringList adjusted;
for (QString name : all)
adjusted << name.replace(u'-', sep);
return adjusted;
};
{
// By default tags are joined with a dash:
const QStringList actual = locale.uiLanguages();
auto reporter = qScopeGuard([&actual]() {
qDebug("\n\t%ls", qUtf16Printable(actual.join("\n\t"_L1)));
});
QCOMPARE(actual, all);
reporter.dismiss();
}
{
// We also support joining with an underscore:
const QStringList actual = locale.uiLanguages(QLocale::TagSeparator::Underscore);
auto reporter = qScopeGuard([&actual]() {
qDebug("\n\t%ls", qUtf16Printable(actual.join("\n\t"_L1)));
});
QCOMPARE(actual, expected(u'_'));
reporter.dismiss();
}
{
// Or, in fact, any ASCII character:
const QStringList actual = locale.uiLanguages(QLocale::TagSeparator{'|'});
auto reporter = qScopeGuard([&actual]() {
qDebug("\n\t%ls", qUtf16Printable(actual.join("\n\t"_L1)));
});
QCOMPARE(actual, expected(u'|'));
reporter.dismiss();
}
{
// Non-ASCII separator (here, y-umlaut) is unsupported.
QTest::ignoreMessage(QtWarningMsg, "QLocale::uiLanguages(): "
"Using non-ASCII separator '\u00ff' (ff) is unsupported");
const QStringList actual = locale.uiLanguages(QLocale::TagSeparator{'\xff'});
auto reporter = qScopeGuard([&actual]() {
qDebug("\n\t%ls", qUtf16Printable(actual.join("\n\t"_L1)));
});
QCOMPARE(actual, QStringList{});
reporter.dismiss();
}
}
void tst_QLocale::weekendDays()