1 | /* === This file is part of Calamares - <https://github.com/calamares> === |
---|
2 | * |
---|
3 | * Copyright 2016, Teo Mrnjavac <teo@kde.org> |
---|
4 | * Copyright 2017, Adriaan de Groot <groot@kde.org> |
---|
5 | * |
---|
6 | * Calamares is free software: you can redistribute it and/or modify |
---|
7 | * it under the terms of the GNU General Public License as published by |
---|
8 | * the Free Software Foundation, either version 3 of the License, or |
---|
9 | * (at your option) any later version. |
---|
10 | * |
---|
11 | * Calamares is distributed in the hope that it will be useful, |
---|
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
14 | * GNU General Public License for more details. |
---|
15 | * |
---|
16 | * You should have received a copy of the GNU General Public License |
---|
17 | * along with Calamares. If not, see <http://www.gnu.org/licenses/>. |
---|
18 | */ |
---|
19 | |
---|
20 | #include "LocaleConfiguration.h" |
---|
21 | #include <QLocale> |
---|
22 | |
---|
23 | LocaleConfiguration::LocaleConfiguration() |
---|
24 | : explicit_lang( false ) |
---|
25 | , explicit_lc( false ) |
---|
26 | { |
---|
27 | } |
---|
28 | |
---|
29 | |
---|
30 | LocaleConfiguration |
---|
31 | LocaleConfiguration::createDefault() |
---|
32 | { |
---|
33 | LocaleConfiguration lc = LocaleConfiguration(); |
---|
34 | lc.lang = lc.lc_numeric = lc.lc_time = lc.lc_monetary = lc.lc_paper = lc.lc_name |
---|
35 | = lc.lc_address = lc.lc_telephone = lc.lc_measurement |
---|
36 | = lc.lc_identification = "en_US.UTF-8"; |
---|
37 | return lc; |
---|
38 | } |
---|
39 | |
---|
40 | |
---|
41 | LocaleConfiguration |
---|
42 | LocaleConfiguration::fromLanguageAndLocation( const QString& languageLocale, |
---|
43 | const QStringList& availableLocales, |
---|
44 | const QString& countryCode ) |
---|
45 | { |
---|
46 | LocaleConfiguration lc; |
---|
47 | |
---|
48 | // Note that the documentation how this works is in packages.conf |
---|
49 | QString language = languageLocale.split( '_' ).first(); |
---|
50 | lc.myLanguageLocaleBcp47 = QLocale(language).bcp47Name().toLower(); |
---|
51 | |
---|
52 | QStringList linesForLanguage; |
---|
53 | for ( const QString &line : availableLocales ) |
---|
54 | { |
---|
55 | if ( line.startsWith( language ) ) |
---|
56 | linesForLanguage.append( line ); |
---|
57 | } |
---|
58 | |
---|
59 | QString lang; |
---|
60 | if ( linesForLanguage.length() == 0 || languageLocale.isEmpty() ) |
---|
61 | lang = "en_US.UTF-8"; |
---|
62 | else if ( linesForLanguage.length() == 1 ) |
---|
63 | lang = linesForLanguage.first(); |
---|
64 | else |
---|
65 | { |
---|
66 | QStringList linesForLanguageUtf; |
---|
67 | // FIXME: this might be useless if we already filter out non-UTF8 locales |
---|
68 | foreach ( QString line, linesForLanguage ) |
---|
69 | { |
---|
70 | if ( line.contains( "UTF-8", Qt::CaseInsensitive ) || |
---|
71 | line.contains( "utf8", Qt::CaseInsensitive ) ) |
---|
72 | linesForLanguageUtf.append( line ); |
---|
73 | } |
---|
74 | |
---|
75 | if ( linesForLanguageUtf.length() == 1 ) |
---|
76 | lang = linesForLanguageUtf.first(); |
---|
77 | } |
---|
78 | |
---|
79 | // lang could still be empty if we found multiple locales that satisfy myLanguage |
---|
80 | |
---|
81 | // The following block was inspired by Ubiquity, scripts/localechooser-apply. |
---|
82 | // No copyright statement found in file, assuming GPL v2 or later. |
---|
83 | /* # In the special cases of Portuguese and Chinese, selecting a |
---|
84 | # different location may imply a different dialect of the language. |
---|
85 | # In such cases, make LANG reflect the selected language (for |
---|
86 | # messages, character types, and collation) and make the other |
---|
87 | # locale categories reflect the selected location. */ |
---|
88 | if ( language == "pt" || language == "zh" ) |
---|
89 | { |
---|
90 | QString proposedLocale = QString( "%1_%2" ).arg( language ) |
---|
91 | .arg( countryCode ); |
---|
92 | foreach ( QString line, linesForLanguage ) |
---|
93 | { |
---|
94 | if ( line.contains( proposedLocale ) ) |
---|
95 | { |
---|
96 | lang = line; |
---|
97 | break; |
---|
98 | } |
---|
99 | } |
---|
100 | } |
---|
101 | |
---|
102 | // If we found no good way to set a default lang, do a search with the whole |
---|
103 | // language locale and pick the first result, if any. |
---|
104 | if ( lang.isEmpty() ) |
---|
105 | { |
---|
106 | for ( const QString &line : availableLocales ) |
---|
107 | { |
---|
108 | if ( line.startsWith( languageLocale ) ) |
---|
109 | { |
---|
110 | lang = line; |
---|
111 | break; |
---|
112 | } |
---|
113 | } |
---|
114 | |
---|
115 | } |
---|
116 | |
---|
117 | // Else we have an unrecognized or unsupported locale, all we can do is go with |
---|
118 | // en_US.UTF-8 UTF-8. This completes all default language setting guesswork. |
---|
119 | if ( lang.isEmpty() ) |
---|
120 | lang = "en_US.UTF-8"; |
---|
121 | |
---|
122 | |
---|
123 | // The following block was inspired by Ubiquity, scripts/localechooser-apply. |
---|
124 | // No copyright statement found in file, assuming GPL v2 or later. |
---|
125 | /* # It is relatively common for the combination of language and location (as |
---|
126 | # selected on the timezone page) not to identify a supported combined |
---|
127 | # locale. For example, this happens when the user is a migrant, or when |
---|
128 | # they prefer to use a different language to interact with their computer |
---|
129 | # because that language is better-supported. |
---|
130 | # |
---|
131 | # In such cases, we would like to be able to use a locale reflecting the |
---|
132 | # selected language in LANG for messages, character types, and collation, |
---|
133 | # and to make the other locale categories reflect the selected location. |
---|
134 | # This means that we have to guess at a suitable locale for the selected |
---|
135 | # location, and we do not want to ask yet another locale-related question. |
---|
136 | # Nevertheless, some cases are ambiguous: a user who has asked for the |
---|
137 | # English language and identifies their location as Switzerland will get |
---|
138 | # different numeric representation depending on which Swiss locale we pick. |
---|
139 | # |
---|
140 | # The goal of identifying a reasonable default for migrants makes things |
---|
141 | # easier: it is reasonable to default to French for France despite the |
---|
142 | # existence of several minority languages there, because anyone who prefers |
---|
143 | # those languages will probably already have selected them and won't arrive |
---|
144 | # here. However, in some cases we're unsure, and in some cases we actively |
---|
145 | # don't want to pick a "preferred" language: selecting either Greek or |
---|
146 | # Turkish as the default language for migrants to Cyprus would probably |
---|
147 | # offend somebody! In such cases we simply punt to the old behaviour of not |
---|
148 | # setting up a locale reflecting the location, which is suboptimal but is at |
---|
149 | # least unlikely to give offence. |
---|
150 | # |
---|
151 | # Our best shot at general criteria for selecting a default language in |
---|
152 | # these circumstances are as follows: |
---|
153 | # |
---|
154 | # * Exclude special-purpose (e.g. en_DK) and artificial (e.g. la_AU, |
---|
155 | # tlh_GB) locales. |
---|
156 | # * If there is a language specific to or very strongly associated with the |
---|
157 | # country in question, prefer it unless it has rather few native |
---|
158 | # speakers. |
---|
159 | # * Exclude minority languages that are relatively unlikely to be spoken by |
---|
160 | # migrants who have not already selected them as their preferred language |
---|
161 | # earlier in the installer. |
---|
162 | # * If there is an official national language likely to be seen in print |
---|
163 | # media, road signs, etc., then prefer that. |
---|
164 | # * In cases of doubt, selecting no default language is safe. */ |
---|
165 | |
---|
166 | // We make a proposed locale based on the UI language and the timezone's country. There is no |
---|
167 | // guarantee that this will be a valid, supported locale (often it won't). |
---|
168 | QString lc_formats; |
---|
169 | QString combined = QString( "%1_%2" ).arg( language ) |
---|
170 | .arg( countryCode ); |
---|
171 | // We look up if it's a supported locale. |
---|
172 | for ( const QString &line : availableLocales ) |
---|
173 | { |
---|
174 | if ( line.startsWith( combined ) ) |
---|
175 | { |
---|
176 | lang = line; |
---|
177 | lc_formats = line; |
---|
178 | break; |
---|
179 | } |
---|
180 | } |
---|
181 | |
---|
182 | if ( lc_formats.isEmpty() ) |
---|
183 | { |
---|
184 | QStringList available; |
---|
185 | for ( const QString &line : availableLocales ) |
---|
186 | { |
---|
187 | if ( line.contains( QString( "_%1" ).arg( countryCode ) ) ) |
---|
188 | { |
---|
189 | available.append( line ); |
---|
190 | } |
---|
191 | } |
---|
192 | available.sort(); |
---|
193 | if ( available.count() == 1 ) |
---|
194 | { |
---|
195 | lc_formats = available.first(); |
---|
196 | } |
---|
197 | else |
---|
198 | { |
---|
199 | QMap< QString, QString > countryToDefaultLanguage { |
---|
200 | { "AU", "en" }, |
---|
201 | { "CN", "zh" }, |
---|
202 | { "DE", "de" }, |
---|
203 | { "DK", "da" }, |
---|
204 | { "DZ", "ar" }, |
---|
205 | { "ES", "es" }, |
---|
206 | // Somewhat unclear: Oromo has the greatest number of |
---|
207 | // native speakers; English is the most widely spoken |
---|
208 | // language and taught in secondary schools; Amharic is |
---|
209 | // the official language and was taught in primary |
---|
210 | // schools. |
---|
211 | { "ET", "am" }, |
---|
212 | { "FI", "fi" }, |
---|
213 | { "FR", "fr" }, |
---|
214 | { "GB", "en" }, |
---|
215 | // Irish (Gaelic) is strongly associated with Ireland, |
---|
216 | // but nearly all its native speakers also speak English, |
---|
217 | // and migrants are likely to use English. |
---|
218 | { "IE", "en" }, |
---|
219 | { "IT", "it" }, |
---|
220 | { "MA", "ar" }, |
---|
221 | { "MK", "mk" }, |
---|
222 | { "NG", "en" }, |
---|
223 | { "NL", "nl" }, |
---|
224 | { "NZ", "en" }, |
---|
225 | { "IL", "he" }, |
---|
226 | // Filipino is a de facto version of Tagalog, which is |
---|
227 | // also spoken; English is also an official language. |
---|
228 | { "PH", "fil" }, |
---|
229 | { "PK", "ur" }, |
---|
230 | { "PL", "pl" }, |
---|
231 | { "RU", "ru" }, |
---|
232 | // Chinese has more speakers, but English is the "common |
---|
233 | // language of the nation" (Wikipedia) and official |
---|
234 | // documents must be translated into English to be |
---|
235 | // accepted. |
---|
236 | { "SG", "en" }, |
---|
237 | { "SN", "wo" }, |
---|
238 | { "TR", "tr" }, |
---|
239 | { "TW", "zh" }, |
---|
240 | { "UA", "uk" }, |
---|
241 | { "US", "en" }, |
---|
242 | { "ZM", "en" } |
---|
243 | }; |
---|
244 | if ( countryToDefaultLanguage.contains( countryCode ) ) |
---|
245 | { |
---|
246 | QString combinedLocale = |
---|
247 | QString( "%1_%2" ).arg( countryToDefaultLanguage.value( countryCode ) ) |
---|
248 | .arg( countryCode ); |
---|
249 | |
---|
250 | for ( const QString &line : availableLocales ) |
---|
251 | { |
---|
252 | if ( line.startsWith( combinedLocale ) ) |
---|
253 | { |
---|
254 | lc_formats = line; |
---|
255 | break; |
---|
256 | } |
---|
257 | } |
---|
258 | } |
---|
259 | } |
---|
260 | } |
---|
261 | |
---|
262 | // If we cannot make a good choice for a given country we go with the LANG |
---|
263 | // setting, which defaults to en_US.UTF-8 UTF-8 if all else fails. |
---|
264 | if ( lc_formats.isEmpty() ) |
---|
265 | lc_formats = lang; |
---|
266 | |
---|
267 | lc.lang = lang; |
---|
268 | lc.lc_address = lc.lc_identification = lc.lc_measurement = lc.lc_monetary |
---|
269 | = lc.lc_name = lc.lc_numeric = lc.lc_paper = lc.lc_telephone |
---|
270 | = lc.lc_time = lc_formats; |
---|
271 | |
---|
272 | return lc; |
---|
273 | } |
---|
274 | |
---|
275 | |
---|
276 | bool |
---|
277 | LocaleConfiguration::isEmpty() const |
---|
278 | { |
---|
279 | return lang.isEmpty() && |
---|
280 | lc_numeric.isEmpty() && |
---|
281 | lc_time.isEmpty() && |
---|
282 | lc_monetary.isEmpty() && |
---|
283 | lc_paper.isEmpty() && |
---|
284 | lc_name.isEmpty() && |
---|
285 | lc_address.isEmpty() && |
---|
286 | lc_telephone.isEmpty() && |
---|
287 | lc_measurement.isEmpty() && |
---|
288 | lc_identification.isEmpty(); |
---|
289 | } |
---|
290 | |
---|
291 | |
---|
292 | QMap< QString, QString > |
---|
293 | LocaleConfiguration::toMap() const |
---|
294 | { |
---|
295 | QMap< QString, QString > map; |
---|
296 | |
---|
297 | if ( !lang.isEmpty() ) |
---|
298 | map.insert( "LANG", lang ); |
---|
299 | |
---|
300 | if ( !lc_numeric.isEmpty() ) |
---|
301 | map.insert( "LC_NUMERIC", lc_numeric ); |
---|
302 | |
---|
303 | if ( !lc_time.isEmpty() ) |
---|
304 | map.insert( "LC_TIME", lc_time ); |
---|
305 | |
---|
306 | if ( !lc_monetary.isEmpty() ) |
---|
307 | map.insert( "LC_MONETARY", lc_monetary ); |
---|
308 | |
---|
309 | if ( !lc_paper.isEmpty() ) |
---|
310 | map.insert( "LC_PAPER", lc_paper ); |
---|
311 | |
---|
312 | if ( !lc_name.isEmpty() ) |
---|
313 | map.insert( "LC_NAME", lc_name ); |
---|
314 | |
---|
315 | if ( !lc_address.isEmpty() ) |
---|
316 | map.insert( "LC_ADDRESS", lc_address ); |
---|
317 | |
---|
318 | if ( !lc_telephone.isEmpty() ) |
---|
319 | map.insert( "LC_TELEPHONE", lc_telephone ); |
---|
320 | |
---|
321 | if ( !lc_measurement.isEmpty() ) |
---|
322 | map.insert( "LC_MEASUREMENT", lc_measurement ); |
---|
323 | |
---|
324 | if ( !lc_identification.isEmpty() ) |
---|
325 | map.insert( "LC_IDENTIFICATION", lc_identification ); |
---|
326 | |
---|
327 | return map; |
---|
328 | } |
---|
329 | |
---|
330 | QString |
---|
331 | LocaleConfiguration::toBcp47() const |
---|
332 | { |
---|
333 | return myLanguageLocaleBcp47; |
---|
334 | } |
---|