1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qlocale_p.h"
5
6#include "qstringbuilder.h"
7#include "qdatetime.h"
8#include "qstringlist.h"
9#include "qvariant.h"
10#include "qreadwritelock.h"
11
12QT_BEGIN_NAMESPACE
13
14using namespace Qt::StringLiterals;
15
16#ifndef QT_NO_SYSTEMLOCALE
17struct QSystemLocaleData
18{
19 QSystemLocaleData()
20 : lc_numeric(QLocale::C)
21 ,lc_time(QLocale::C)
22 ,lc_monetary(QLocale::C)
23 ,lc_messages(QLocale::C)
24 {
25 readEnvironment();
26 }
27
28 void readEnvironment();
29
30 QReadWriteLock lock;
31
32 QLocale lc_numeric;
33 QLocale lc_time;
34 QLocale lc_monetary;
35 QLocale lc_messages;
36 QByteArray lc_messages_var;
37 QByteArray lc_measurement_var;
38 QByteArray lc_collate_var;
39 QStringList uiLanguages;
40};
41
42void QSystemLocaleData::readEnvironment()
43{
44 QWriteLocker locker(&lock);
45
46 // See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_02
47 // for the semantics of each of these:
48 QByteArray all = qgetenv(varName: "LC_ALL");
49 QByteArray numeric = all.isEmpty() ? qgetenv(varName: "LC_NUMERIC") : all;
50 QByteArray time = all.isEmpty() ? qgetenv(varName: "LC_TIME") : all;
51 QByteArray monetary = all.isEmpty() ? qgetenv(varName: "LC_MONETARY") : all;
52 lc_messages_var = all.isEmpty() ? qgetenv(varName: "LC_MESSAGES") : all;
53 lc_measurement_var = all.isEmpty() ? qgetenv(varName: "LC_MEASUREMENT") : all;
54 lc_collate_var = all.isEmpty() ? qgetenv(varName: "LC_COLLATE") : all;
55 QByteArray lang = qgetenv(varName: "LANG");
56 if (lang.isEmpty())
57 lang = QByteArray("C");
58 if (numeric.isEmpty())
59 numeric = lang;
60 if (time.isEmpty())
61 time = lang;
62 if (monetary.isEmpty())
63 monetary = lang;
64 if (lc_messages_var.isEmpty())
65 lc_messages_var = lang;
66 if (lc_measurement_var.isEmpty())
67 lc_measurement_var = lang;
68 if (lc_collate_var.isEmpty())
69 lc_collate_var = lang;
70 lc_numeric = QLocale(QString::fromLatin1(ba: numeric));
71 lc_time = QLocale(QString::fromLatin1(ba: time));
72 lc_monetary = QLocale(QString::fromLatin1(ba: monetary));
73 lc_messages = QLocale(QString::fromLatin1(ba: lc_messages_var));
74}
75
76Q_GLOBAL_STATIC(QSystemLocaleData, qSystemLocaleData)
77
78#endif
79
80#ifndef QT_NO_SYSTEMLOCALE
81
82static bool contradicts(QStringView maybe, const QString &known)
83{
84 if (maybe.isEmpty())
85 return false;
86
87 /*
88 If \a known (our current best shot at deciding which language to use)
89 provides more information (e.g. script, country) than \a maybe (a
90 candidate to replace \a known) and \a maybe agrees with \a known in what
91 it does provide, we keep \a known; this happens when \a maybe comes from
92 LANGUAGE (usually a simple language code) and LANG includes script and/or
93 country. A textual comparison won't do because, for example, bn (Bengali)
94 isn't a prefix of ben_IN, but the latter is a refinement of the former.
95 (Meanwhile, bn is a prefix of bnt, Bantu; and a prefix of ben is be,
96 Belarusian. There are many more such prefixings between two- and
97 three-letter codes.)
98 */
99 QLocaleId knownId = QLocaleId::fromName(name: known);
100 QLocaleId maybeId = QLocaleId::fromName(name: maybe);
101 return !(maybeId.acceptLanguage(lang: knownId.language_id) && maybeId.acceptScriptTerritory(other: knownId));
102}
103
104QLocale QSystemLocale::fallbackLocale() const
105{
106 // See man 7 locale for precedence - LC_ALL beats LC_MESSAGES beats LANG:
107 QString lang = qEnvironmentVariable(varName: "LC_ALL");
108 if (lang.isEmpty())
109 lang = qEnvironmentVariable(varName: "LC_MESSAGES");
110 if (lang.isEmpty())
111 lang = qEnvironmentVariable(varName: "LANG");
112 // if the locale is the "C" locale, then we can return the language we found here:
113 if (lang.isEmpty() || lang == "C"_L1 || lang == "POSIX"_L1)
114 return QLocale(lang);
115
116 // ... otherwise, if the first part of LANGUAGE says more than or
117 // contradicts what we have, use that:
118 for (const auto &language : qEnvironmentVariable(varName: "LANGUAGE").tokenize(needle: u':')) {
119 if (contradicts(maybe: language, known: lang))
120 return QLocale(language);
121 break; // We only look at the first entry.
122 }
123
124 return QLocale(lang);
125}
126
127QVariant QSystemLocale::query(QueryType type, QVariant &&in) const
128{
129 QSystemLocaleData *d = qSystemLocaleData();
130 if (!d)
131 return QVariant();
132
133 if (type == LocaleChanged) {
134 d->readEnvironment();
135 return QVariant();
136 }
137
138 QReadLocker locker(&d->lock);
139
140 const QLocale &lc_numeric = d->lc_numeric;
141 const QLocale &lc_time = d->lc_time;
142 const QLocale &lc_monetary = d->lc_monetary;
143 const QLocale &lc_messages = d->lc_messages;
144
145 switch (type) {
146 case DecimalPoint:
147 return lc_numeric.decimalPoint();
148 case GroupSeparator:
149 return lc_numeric.groupSeparator();
150 case ZeroDigit:
151 return lc_numeric.zeroDigit();
152 case NegativeSign:
153 return lc_numeric.negativeSign();
154 case DateFormatLong:
155 return lc_time.dateFormat(format: QLocale::LongFormat);
156 case DateFormatShort:
157 return lc_time.dateFormat(format: QLocale::ShortFormat);
158 case TimeFormatLong:
159 return lc_time.timeFormat(format: QLocale::LongFormat);
160 case TimeFormatShort:
161 return lc_time.timeFormat(format: QLocale::ShortFormat);
162 case DayNameLong:
163 return lc_time.dayName(in.toInt(), format: QLocale::LongFormat);
164 case DayNameShort:
165 return lc_time.dayName(in.toInt(), format: QLocale::ShortFormat);
166 case DayNameNarrow:
167 return lc_time.dayName(in.toInt(), format: QLocale::NarrowFormat);
168 case StandaloneDayNameLong:
169 return lc_time.standaloneDayName(in.toInt(), format: QLocale::LongFormat);
170 case StandaloneDayNameShort:
171 return lc_time.standaloneDayName(in.toInt(), format: QLocale::ShortFormat);
172 case StandaloneDayNameNarrow:
173 return lc_time.standaloneDayName(in.toInt(), format: QLocale::NarrowFormat);
174 case MonthNameLong:
175 return lc_time.monthName(in.toInt(), format: QLocale::LongFormat);
176 case MonthNameShort:
177 return lc_time.monthName(in.toInt(), format: QLocale::ShortFormat);
178 case MonthNameNarrow:
179 return lc_time.monthName(in.toInt(), format: QLocale::NarrowFormat);
180 case StandaloneMonthNameLong:
181 return lc_time.standaloneMonthName(in.toInt(), format: QLocale::LongFormat);
182 case StandaloneMonthNameShort:
183 return lc_time.standaloneMonthName(in.toInt(), format: QLocale::ShortFormat);
184 case StandaloneMonthNameNarrow:
185 return lc_time.standaloneMonthName(in.toInt(), format: QLocale::NarrowFormat);
186 case DateToStringLong:
187 return lc_time.toString(date: in.toDate(), format: QLocale::LongFormat);
188 case DateToStringShort:
189 return lc_time.toString(date: in.toDate(), format: QLocale::ShortFormat);
190 case TimeToStringLong:
191 return lc_time.toString(time: in.toTime(), format: QLocale::LongFormat);
192 case TimeToStringShort:
193 return lc_time.toString(time: in.toTime(), format: QLocale::ShortFormat);
194 case DateTimeFormatLong:
195 return lc_time.dateTimeFormat(format: QLocale::LongFormat);
196 case DateTimeFormatShort:
197 return lc_time.dateTimeFormat(format: QLocale::ShortFormat);
198 case DateTimeToStringLong:
199 return lc_time.toString(dateTime: in.toDateTime(), format: QLocale::LongFormat);
200 case DateTimeToStringShort:
201 return lc_time.toString(dateTime: in.toDateTime(), format: QLocale::ShortFormat);
202 case PositiveSign:
203 return lc_numeric.positiveSign();
204 case AMText:
205 return lc_time.amText();
206 case PMText:
207 return lc_time.pmText();
208 case FirstDayOfWeek:
209 return lc_time.firstDayOfWeek();
210 case CurrencySymbol:
211 return lc_monetary.currencySymbol(QLocale::CurrencySymbolFormat(in.toUInt()));
212 case CurrencyToString: {
213 switch (in.userType()) {
214 case QMetaType::Int:
215 return lc_monetary.toCurrencyString(i: in.toInt());
216 case QMetaType::UInt:
217 return lc_monetary.toCurrencyString(i: in.toUInt());
218 case QMetaType::Double:
219 return lc_monetary.toCurrencyString(in.toDouble());
220 case QMetaType::LongLong:
221 return lc_monetary.toCurrencyString(in.toLongLong());
222 case QMetaType::ULongLong:
223 return lc_monetary.toCurrencyString(in.toULongLong());
224 default:
225 break;
226 }
227 return QString();
228 }
229 case MeasurementSystem: {
230 const QString meas_locale = QString::fromLatin1(ba: d->lc_measurement_var);
231 if (meas_locale.compare(other: "Metric"_L1, cs: Qt::CaseInsensitive) == 0)
232 return QLocale::MetricSystem;
233 if (meas_locale.compare(other: "Other"_L1, cs: Qt::CaseInsensitive) == 0)
234 return QLocale::MetricSystem;
235 return QVariant((int)QLocale(meas_locale).measurementSystem());
236 }
237 case Collation:
238 return QString::fromLatin1(ba: d->lc_collate_var);
239 case UILanguages: {
240 if (!d->uiLanguages.isEmpty())
241 return d->uiLanguages;
242 QString languages = QString::fromLatin1(ba: qgetenv(varName: "LANGUAGE"));
243 QStringList lst;
244 if (languages.isEmpty())
245 lst.append(t: QString::fromLatin1(ba: d->lc_messages_var));
246 else
247 lst = languages.split(sep: u':');
248
249 for (const QString &e : std::as_const(t&: lst)) {
250 QStringView language, script, territory;
251 if (qt_splitLocaleName(name: e, lang: &language, script: &script, cntry: &territory)) {
252 QString joined = language.isEmpty() ? u"und"_s : language.toString();
253 if (!script.isEmpty())
254 joined += u'-' + script;
255 if (!territory.isEmpty())
256 joined += u'-' + territory;
257 d->uiLanguages.append(t: joined);
258 }
259 }
260 return d->uiLanguages.isEmpty() ? QVariant() : QVariant(d->uiLanguages);
261 }
262 case StringToStandardQuotation:
263 return lc_messages.quoteString(str: qvariant_cast<QStringView>(v: std::move(in)));
264 case StringToAlternateQuotation:
265 return lc_messages.quoteString(str: qvariant_cast<QStringView>(v: std::move(in)),
266 style: QLocale::AlternateQuotation);
267 case ListToSeparatedString:
268 return lc_messages.createSeparatedList(strl: in.toStringList());
269 case LocaleChanged:
270 Q_ASSERT(false);
271 [[fallthrough]];
272 default:
273 break;
274 }
275 return QVariant();
276}
277#endif // QT_NO_SYSTEMLOCALE
278
279QT_END_NAMESPACE
280

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtbase/src/corelib/text/qlocale_unix.cpp