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 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | using namespace Qt::StringLiterals; |
15 | |
16 | #ifndef QT_NO_SYSTEMLOCALE |
17 | struct 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 | |
42 | void 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 | |
76 | Q_GLOBAL_STATIC(QSystemLocaleData, qSystemLocaleData) |
77 | |
78 | #endif |
79 | |
80 | #ifndef QT_NO_SYSTEMLOCALE |
81 | |
82 | static 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 | |
104 | QLocale 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 | |
127 | QVariant 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 | |
275 | QT_END_NAMESPACE |
276 | |