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
131 if (type == LocaleChanged) {
132 d->readEnvironment();
133 return QVariant();
134 }
135
136 QReadLocker locker(&d->lock);
137
138 const QLocale &lc_numeric = d->lc_numeric;
139 const QLocale &lc_time = d->lc_time;
140 const QLocale &lc_monetary = d->lc_monetary;
141 const QLocale &lc_messages = d->lc_messages;
142
143 switch (type) {
144 case DecimalPoint:
145 return lc_numeric.decimalPoint();
146 case GroupSeparator:
147 return lc_numeric.groupSeparator();
148 case ZeroDigit:
149 return lc_numeric.zeroDigit();
150 case NegativeSign:
151 return lc_numeric.negativeSign();
152 case DateFormatLong:
153 return lc_time.dateFormat(format: QLocale::LongFormat);
154 case DateFormatShort:
155 return lc_time.dateFormat(format: QLocale::ShortFormat);
156 case TimeFormatLong:
157 return lc_time.timeFormat(format: QLocale::LongFormat);
158 case TimeFormatShort:
159 return lc_time.timeFormat(format: QLocale::ShortFormat);
160 case DayNameLong:
161 return lc_time.dayName(in.toInt(), format: QLocale::LongFormat);
162 case DayNameShort:
163 return lc_time.dayName(in.toInt(), format: QLocale::ShortFormat);
164 case DayNameNarrow:
165 return lc_time.dayName(in.toInt(), format: QLocale::NarrowFormat);
166 case StandaloneDayNameLong:
167 return lc_time.standaloneDayName(in.toInt(), format: QLocale::LongFormat);
168 case StandaloneDayNameShort:
169 return lc_time.standaloneDayName(in.toInt(), format: QLocale::ShortFormat);
170 case StandaloneDayNameNarrow:
171 return lc_time.standaloneDayName(in.toInt(), format: QLocale::NarrowFormat);
172 case MonthNameLong:
173 return lc_time.monthName(in.toInt(), format: QLocale::LongFormat);
174 case MonthNameShort:
175 return lc_time.monthName(in.toInt(), format: QLocale::ShortFormat);
176 case MonthNameNarrow:
177 return lc_time.monthName(in.toInt(), format: QLocale::NarrowFormat);
178 case StandaloneMonthNameLong:
179 return lc_time.standaloneMonthName(in.toInt(), format: QLocale::LongFormat);
180 case StandaloneMonthNameShort:
181 return lc_time.standaloneMonthName(in.toInt(), format: QLocale::ShortFormat);
182 case StandaloneMonthNameNarrow:
183 return lc_time.standaloneMonthName(in.toInt(), format: QLocale::NarrowFormat);
184 case DateToStringLong:
185 return lc_time.toString(date: in.toDate(), format: QLocale::LongFormat);
186 case DateToStringShort:
187 return lc_time.toString(date: in.toDate(), format: QLocale::ShortFormat);
188 case TimeToStringLong:
189 return lc_time.toString(time: in.toTime(), format: QLocale::LongFormat);
190 case TimeToStringShort:
191 return lc_time.toString(time: in.toTime(), format: QLocale::ShortFormat);
192 case DateTimeFormatLong:
193 return lc_time.dateTimeFormat(format: QLocale::LongFormat);
194 case DateTimeFormatShort:
195 return lc_time.dateTimeFormat(format: QLocale::ShortFormat);
196 case DateTimeToStringLong:
197 return lc_time.toString(dateTime: in.toDateTime(), format: QLocale::LongFormat);
198 case DateTimeToStringShort:
199 return lc_time.toString(dateTime: in.toDateTime(), format: QLocale::ShortFormat);
200 case PositiveSign:
201 return lc_numeric.positiveSign();
202 case AMText:
203 return lc_time.amText();
204 case PMText:
205 return lc_time.pmText();
206 case FirstDayOfWeek:
207 return lc_time.firstDayOfWeek();
208 case CurrencySymbol:
209 return lc_monetary.currencySymbol(QLocale::CurrencySymbolFormat(in.toUInt()));
210 case CurrencyToString: {
211 switch (in.userType()) {
212 case QMetaType::Int:
213 return lc_monetary.toCurrencyString(i: in.toInt());
214 case QMetaType::UInt:
215 return lc_monetary.toCurrencyString(i: in.toUInt());
216 case QMetaType::Double:
217 return lc_monetary.toCurrencyString(in.toDouble());
218 case QMetaType::LongLong:
219 return lc_monetary.toCurrencyString(in.toLongLong());
220 case QMetaType::ULongLong:
221 return lc_monetary.toCurrencyString(in.toULongLong());
222 default:
223 break;
224 }
225 return QString();
226 }
227 case MeasurementSystem: {
228 const QString meas_locale = QString::fromLatin1(ba: d->lc_measurement_var);
229 if (meas_locale.compare(other: "Metric"_L1, cs: Qt::CaseInsensitive) == 0)
230 return QLocale::MetricSystem;
231 if (meas_locale.compare(other: "Other"_L1, cs: Qt::CaseInsensitive) == 0)
232 return QLocale::MetricSystem;
233 return QVariant((int)QLocale(meas_locale).measurementSystem());
234 }
235 case Collation:
236 return QString::fromLatin1(ba: d->lc_collate_var);
237 case UILanguages: {
238 if (!d->uiLanguages.isEmpty())
239 return d->uiLanguages;
240 QString languages = QString::fromLatin1(ba: qgetenv(varName: "LANGUAGE"));
241 QStringList lst;
242 if (languages.isEmpty())
243 lst.append(t: QString::fromLatin1(ba: d->lc_messages_var));
244 else
245 lst = languages.split(sep: u':');
246
247 for (const QString &e : std::as_const(t&: lst)) {
248 QStringView language, script, territory;
249 if (qt_splitLocaleName(name: e, lang: &language, script: &script, cntry: &territory)) {
250 QString joined = language.isEmpty() ? u"und"_s : language.toString();
251 if (!script.isEmpty())
252 joined += u'-' + script;
253 if (!territory.isEmpty())
254 joined += u'-' + territory;
255 d->uiLanguages.append(t: joined);
256 }
257 }
258 return d->uiLanguages.isEmpty() ? QVariant() : QVariant(d->uiLanguages);
259 }
260 case StringToStandardQuotation:
261 return lc_messages.quoteString(str: qvariant_cast<QStringView>(v: in));
262 case StringToAlternateQuotation:
263 return lc_messages.quoteString(str: qvariant_cast<QStringView>(v: in), style: QLocale::AlternateQuotation);
264 case ListToSeparatedString:
265 return lc_messages.createSeparatedList(strl: in.toStringList());
266 case LocaleChanged:
267 Q_ASSERT(false);
268 default:
269 break;
270 }
271 return QVariant();
272}
273#endif // QT_NO_SYSTEMLOCALE
274
275QT_END_NAMESPACE
276

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