Make QLocale self-consistent on Windows

The constructor and update() method for the system locale were using
GetUserDefaultLCID(), where query() and the fallback locale first
checked the LANG environment variable, leading to inconsistent results
if the user set the environment variable to something different from
the system's configured locale.

Break out the logic of parsing %LANG% into a static helper, replace
the existing parsing with a call to this and add a helper to get the
right ID to use, possibly via it, using GetUserDefaultLCID() as
fall-back. Drive-by: initialize substititionType in its declaration.
Also look up %LANG% each time we want it; it's not that expensive,
given how rarely this code is called, and client code could change its
value at runtime. Partially inspired by a patch from Wladimir
Leuschner <wladimir.leuschner@qt.io>

Pick-to: 6.6 6.5
Fixes: QTBUG-120961
Change-Id: Ie706c7089bd2b3757a3eab627723ec34a5e2b07f
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
(cherry picked from commit fcd2a219c4c222309152f1ea2a3124ddc17d4ea5)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Edward Welbourne 2024-01-17 18:45:07 +01:00 committed by Qt Cherry-pick Bot
parent bb61ff9175
commit cbc3c63633

View File

@ -31,6 +31,41 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
// Shared interpretation of %LANG%
static auto scanLangEnv()
{
struct R
{
QByteArray name; // empty means unknown; lookup from id may work
LCID id = 0; // 0 means unknown; lookup from name may work
} result;
const QByteArray lang = qgetenv("LANG");
if (lang.size() && (lang == "C" || qt_splitLocaleName(QString::fromLocal8Bit(lang)))) {
// See if we have a Windows locale code instead of a locale name:
const auto [id, used] = qstrntoll(lang.data(), lang.size(), 0);
if (used > 0 && id && INT_MIN <= id && id <= INT_MAX)
return R {QByteArray(), static_cast<LCID>(id)};
return R {lang, 0};
}
return R{};
}
static auto getDefaultWinId()
{
const auto [name, id] = scanLangEnv();
if (id)
return id;
if (!name.isEmpty()) {
LCID id = LocaleNameToLCID(static_cast<LPCWSTR>(
QString::fromUtf8(name).toStdWString().data()), 0);
if (id)
return id;
}
return GetUserDefaultLCID();
}
static QByteArray getWinLocaleName(LCID id = LOCALE_USER_DEFAULT);
static QString winIso639LangName(LCID id = LOCALE_USER_DEFAULT);
static QString winIso3116CtryName(LCID id = LOCALE_USER_DEFAULT);
@ -111,7 +146,7 @@ private:
// cached values:
LCID lcid;
SubstitutionType substitutionType;
SubstitutionType substitutionType = SUnknown;
QString zero; // cached value for zeroDigit()
int getLocaleInfo(LCTYPE type, LPWSTR data, int size);
@ -133,9 +168,8 @@ private:
Q_GLOBAL_STATIC(QSystemLocalePrivate, systemLocalePrivate)
QSystemLocalePrivate::QSystemLocalePrivate()
: substitutionType(SUnknown)
: lcid(getDefaultWinId())
{
lcid = GetUserDefaultLCID();
}
inline int QSystemLocalePrivate::getCurrencyFormat(DWORD flags, LPCWSTR value, const CURRENCYFMTW *format, LPWSTR data, int size)
@ -720,7 +754,7 @@ QVariant QSystemLocalePrivate::nativeTerritoryName()
void QSystemLocalePrivate::update()
{
lcid = GetUserDefaultLCID();
lcid = getDefaultWinId();
substitutionType = SUnknown;
zero.resize(0);
}
@ -1123,20 +1157,15 @@ static QByteArray getWinLocaleName(LCID id)
{
QByteArray result;
if (id == LOCALE_USER_DEFAULT) {
static const QByteArray langEnvVar = qgetenv("LANG");
result = langEnvVar;
if (result == "C"
|| (!result.isEmpty() && qt_splitLocaleName(QString::fromLocal8Bit(result)))) {
// See if we have a Windows locale code instead of a locale name:
auto [id, used] = qstrntoll(result.data(), result.size(), 0);
if (used <= 0 || id == 0 || id < INT_MIN || id > INT_MAX) // Assume real locale name
return result;
return winLangCodeToIsoName(int(id));
}
const auto [name, lcid] = scanLangEnv();
if (!name.isEmpty())
return name;
if (lcid)
return winLangCodeToIsoName(lcid);
id = GetUserDefaultLCID();
}
if (id == LOCALE_USER_DEFAULT)
id = GetUserDefaultLCID();
QString resultusage = winIso639LangName(id);
QString country = winIso3116CtryName(id);
if (!country.isEmpty())