1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "translator.h" |
5 | |
6 | #include <QtCore/QByteArray> |
7 | #include <QtCore/QDebug> |
8 | #include <QtCore/QDir> |
9 | #include <QtCore/QFile> |
10 | #include <QtCore/QFileInfo> |
11 | #include <QtCore/QMap> |
12 | |
13 | #include <private/qtranslator_p.h> |
14 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | static const uchar englishStyleRules[] = |
18 | { Q_EQ, 1 }; |
19 | static const uchar frenchStyleRules[] = |
20 | { Q_LEQ, 1 }; |
21 | static const uchar latvianRules[] = |
22 | { Q_MOD_10 | Q_EQ, 1, Q_AND, Q_MOD_100 | Q_NEQ, 11, Q_NEWRULE, |
23 | Q_NEQ, 0 }; |
24 | static const uchar icelandicRules[] = |
25 | { Q_MOD_10 | Q_EQ, 1, Q_AND, Q_MOD_100 | Q_NEQ, 11 }; |
26 | static const uchar irishStyleRules[] = |
27 | { Q_EQ, 1, Q_NEWRULE, |
28 | Q_EQ, 2 }; |
29 | static const uchar gaelicStyleRules[] = |
30 | { Q_EQ, 1, Q_OR, Q_EQ, 11, Q_NEWRULE, |
31 | Q_EQ, 2, Q_OR, Q_EQ, 12, Q_NEWRULE, |
32 | Q_BETWEEN, 3, 19 }; |
33 | static const uchar slovakStyleRules[] = |
34 | { Q_EQ, 1, Q_NEWRULE, |
35 | Q_BETWEEN, 2, 4 }; |
36 | static const uchar macedonianRules[] = |
37 | { Q_MOD_10 | Q_EQ, 1, Q_NEWRULE, |
38 | Q_MOD_10 | Q_EQ, 2 }; |
39 | static const uchar lithuanianRules[] = |
40 | { Q_MOD_10 | Q_EQ, 1, Q_AND, Q_MOD_100 | Q_NEQ, 11, Q_NEWRULE, |
41 | Q_MOD_10 | Q_NEQ, 0, Q_AND, Q_MOD_100 | Q_NOT_BETWEEN, 10, 19 }; |
42 | static const uchar russianStyleRules[] = |
43 | { Q_MOD_10 | Q_EQ, 1, Q_AND, Q_MOD_100 | Q_NEQ, 11, Q_NEWRULE, |
44 | Q_MOD_10 | Q_BETWEEN, 2, 4, Q_AND, Q_MOD_100 | Q_NOT_BETWEEN, 10, 19 }; |
45 | static const uchar polishRules[] = |
46 | { Q_EQ, 1, Q_NEWRULE, |
47 | Q_MOD_10 | Q_BETWEEN, 2, 4, Q_AND, Q_MOD_100 | Q_NOT_BETWEEN, 10, 19 }; |
48 | static const uchar romanianRules[] = |
49 | { Q_EQ, 1, Q_NEWRULE, |
50 | Q_EQ, 0, Q_OR, Q_MOD_100 | Q_BETWEEN, 1, 19 }; |
51 | static const uchar slovenianRules[] = |
52 | { Q_MOD_100 | Q_EQ, 1, Q_NEWRULE, |
53 | Q_MOD_100 | Q_EQ, 2, Q_NEWRULE, |
54 | Q_MOD_100 | Q_BETWEEN, 3, 4 }; |
55 | static const uchar malteseRules[] = |
56 | { Q_EQ, 1, Q_NEWRULE, |
57 | Q_EQ, 0, Q_OR, Q_MOD_100 | Q_BETWEEN, 1, 10, Q_NEWRULE, |
58 | Q_MOD_100 | Q_BETWEEN, 11, 19 }; |
59 | static const uchar welshRules[] = |
60 | { Q_EQ, 0, Q_NEWRULE, |
61 | Q_EQ, 1, Q_NEWRULE, |
62 | Q_BETWEEN, 2, 5, Q_NEWRULE, |
63 | Q_EQ, 6 }; |
64 | static const uchar arabicRules[] = |
65 | { Q_EQ, 0, Q_NEWRULE, |
66 | Q_EQ, 1, Q_NEWRULE, |
67 | Q_EQ, 2, Q_NEWRULE, |
68 | Q_MOD_100 | Q_BETWEEN, 3, 10, Q_NEWRULE, |
69 | Q_MOD_100 | Q_GEQ, 11 }; |
70 | static const uchar tagalogRules[] = |
71 | { Q_LEQ, 1, Q_NEWRULE, |
72 | Q_MOD_10 | Q_EQ, 4, Q_OR, Q_MOD_10 | Q_EQ, 6, Q_OR, Q_MOD_10 | Q_EQ, 9 }; |
73 | |
74 | static const char * const japaneseStyleForms[] = { "Universal Form" , 0 }; |
75 | static const char * const englishStyleForms[] = { "Singular" , "Plural" , 0 }; |
76 | static const char * const frenchStyleForms[] = { "Singular" , "Plural" , 0 }; |
77 | static const char * const icelandicForms[] = { "Singular" , "Plural" , 0 }; |
78 | static const char * const latvianForms[] = { "Singular" , "Plural" , "Nullar" , 0 }; |
79 | static const char * const irishStyleForms[] = { "Singular" , "Dual" , "Plural" , 0 }; |
80 | // Gaelic uses the grammatical Singular for the Plural cardinality, |
81 | // so using the Latin terms is expected to cause confusion. |
82 | static const char * const gaelicStyleForms[] = { "1/11" , "2/12" , "Few" , "Many" , 0 }; |
83 | static const char * const slovakStyleForms[] = { "Singular" , "Paucal" , "Plural" , 0 }; |
84 | static const char * const macedonianForms[] = { "Singular" , "Dual" , "Plural" , 0 }; |
85 | static const char * const lithuanianForms[] = { "Singular" , "Paucal" , "Plural" , 0 }; |
86 | static const char * const russianStyleForms[] = { "Singular" , "Dual" , "Plural" , 0 }; |
87 | static const char * const polishForms[] = { "Singular" , "Paucal" , "Plural" , 0 }; |
88 | static const char * const romanianForms[] = { "Singular" , "Paucal" , "Plural" , 0 }; |
89 | static const char * const slovenianForms[] = { "Singular" , "Dual" , "Trial" , "Plural" , 0 }; |
90 | static const char * const malteseForms[] = |
91 | { "Singular" , "Paucal" , "Greater Paucal" , "Plural" , 0 }; |
92 | static const char * const welshForms[] = |
93 | { "Nullar" , "Singular" , "Dual" , "Sexal" , "Plural" , 0 }; |
94 | static const char * const arabicForms[] = |
95 | { "Nullar" , "Singular" , "Dual" , "Minority Plural" , "Plural" , "Plural (100-102, ...)" , 0 }; |
96 | static const char * const tagalogForms[] = |
97 | { "Singular" , "Plural (consonant-ended)" , "Plural (vowel-ended)" , 0 }; |
98 | |
99 | #define EOL QLocale::C |
100 | |
101 | static const QLocale::Language japaneseStyleLanguages[] = { |
102 | QLocale::Bislama, |
103 | QLocale::Burmese, |
104 | QLocale::Chinese, |
105 | QLocale::Dzongkha, |
106 | QLocale::Fijian, |
107 | QLocale::Guarani, |
108 | QLocale::Hungarian, |
109 | QLocale::Indonesian, |
110 | QLocale::Japanese, |
111 | QLocale::Javanese, |
112 | QLocale::Korean, |
113 | QLocale::Malay, |
114 | QLocale::NauruLanguage, |
115 | QLocale::Oromo, |
116 | QLocale::Persian, |
117 | QLocale::Sundanese, |
118 | QLocale::Tatar, |
119 | QLocale::Thai, |
120 | QLocale::Tibetan, |
121 | QLocale::Turkish, |
122 | QLocale::Vietnamese, |
123 | QLocale::Yoruba, |
124 | QLocale::Zhuang, |
125 | EOL |
126 | }; |
127 | |
128 | static const QLocale::Language englishStyleLanguages[] = { |
129 | QLocale::Abkhazian, |
130 | QLocale::Afar, |
131 | QLocale::Afrikaans, |
132 | QLocale::Albanian, |
133 | QLocale::Amharic, |
134 | QLocale::Assamese, |
135 | QLocale::Aymara, |
136 | QLocale::Azerbaijani, |
137 | QLocale::Bashkir, |
138 | QLocale::Basque, |
139 | QLocale::Bengali, |
140 | QLocale::Bulgarian, |
141 | QLocale::Catalan, |
142 | QLocale::Cornish, |
143 | QLocale::Corsican, |
144 | QLocale::Danish, |
145 | QLocale::Dutch, |
146 | QLocale::English, |
147 | QLocale::Esperanto, |
148 | QLocale::Estonian, |
149 | QLocale::Faroese, |
150 | QLocale::Finnish, |
151 | QLocale::Friulian, |
152 | QLocale::WesternFrisian, |
153 | QLocale::Galician, |
154 | QLocale::Georgian, |
155 | QLocale::German, |
156 | QLocale::Greek, |
157 | QLocale::Greenlandic, |
158 | QLocale::Gujarati, |
159 | QLocale::Hausa, |
160 | QLocale::Hebrew, |
161 | QLocale::Hindi, |
162 | QLocale::Interlingua, |
163 | QLocale::Interlingue, |
164 | QLocale::Italian, |
165 | QLocale::Kannada, |
166 | QLocale::Kashmiri, |
167 | QLocale::Kazakh, |
168 | QLocale::Khmer, |
169 | QLocale::Kinyarwanda, |
170 | QLocale::Kirghiz, |
171 | QLocale::Kurdish, |
172 | QLocale::Lao, |
173 | QLocale::Latin, |
174 | QLocale::Lingala, |
175 | QLocale::Luxembourgish, |
176 | QLocale::Malagasy, |
177 | QLocale::Malayalam, |
178 | QLocale::Marathi, |
179 | QLocale::Mongolian, |
180 | // Missing: Nahuatl, |
181 | QLocale::Nepali, |
182 | QLocale::NorthernSotho, |
183 | QLocale::NorwegianBokmal, |
184 | QLocale::NorwegianNynorsk, |
185 | QLocale::Occitan, |
186 | QLocale::Oriya, |
187 | QLocale::Pashto, |
188 | QLocale::Portuguese, |
189 | QLocale::Punjabi, |
190 | QLocale::Quechua, |
191 | QLocale::Romansh, |
192 | QLocale::Rundi, |
193 | QLocale::Shona, |
194 | QLocale::Sindhi, |
195 | QLocale::Sinhala, |
196 | QLocale::Somali, |
197 | QLocale::SouthernSotho, |
198 | QLocale::Spanish, |
199 | QLocale::Swahili, |
200 | QLocale::Swati, |
201 | QLocale::Swedish, |
202 | QLocale::Tajik, |
203 | QLocale::Tamil, |
204 | QLocale::Telugu, |
205 | QLocale::Tongan, |
206 | QLocale::Tsonga, |
207 | QLocale::Tswana, |
208 | QLocale::Turkmen, |
209 | QLocale::Uigur, |
210 | QLocale::Urdu, |
211 | QLocale::Uzbek, |
212 | QLocale::Volapuk, |
213 | QLocale::Wolof, |
214 | QLocale::Xhosa, |
215 | QLocale::Yiddish, |
216 | QLocale::Zulu, |
217 | EOL |
218 | }; |
219 | static const QLocale::Language frenchStyleLanguages[] = { |
220 | // keep synchronized with frenchStyleCountries |
221 | QLocale::Armenian, |
222 | QLocale::Breton, |
223 | QLocale::French, |
224 | QLocale::Portuguese, |
225 | QLocale::Filipino, |
226 | QLocale::Tigrinya, |
227 | QLocale::Walloon, |
228 | EOL |
229 | }; |
230 | static const QLocale::Language latvianLanguage[] = { QLocale::Latvian, EOL }; |
231 | static const QLocale::Language icelandicLanguage[] = { QLocale::Icelandic, EOL }; |
232 | static const QLocale::Language irishStyleLanguages[] = { |
233 | QLocale::Divehi, |
234 | QLocale::Inuktitut, |
235 | QLocale::Inupiak, |
236 | QLocale::Irish, |
237 | QLocale::Manx, |
238 | QLocale::Maori, |
239 | QLocale::NorthernSami, |
240 | QLocale::Samoan, |
241 | QLocale::Sanskrit, |
242 | EOL |
243 | }; |
244 | static const QLocale::Language gaelicStyleLanguages[] = { QLocale::Gaelic, EOL }; |
245 | static const QLocale::Language slovakStyleLanguages[] = { QLocale::Slovak, QLocale::Czech, EOL }; |
246 | static const QLocale::Language macedonianLanguage[] = { QLocale::Macedonian, EOL }; |
247 | static const QLocale::Language lithuanianLanguage[] = { QLocale::Lithuanian, EOL }; |
248 | static const QLocale::Language russianStyleLanguages[] = { |
249 | QLocale::Bosnian, |
250 | QLocale::Belarusian, |
251 | QLocale::Croatian, |
252 | QLocale::Russian, |
253 | QLocale::Serbian, |
254 | QLocale::Ukrainian, |
255 | EOL |
256 | }; |
257 | static const QLocale::Language polishLanguage[] = { QLocale::Polish, EOL }; |
258 | static const QLocale::Language romanianLanguages[] = { |
259 | QLocale::Romanian, |
260 | EOL |
261 | }; |
262 | static const QLocale::Language slovenianLanguage[] = { QLocale::Slovenian, EOL }; |
263 | static const QLocale::Language malteseLanguage[] = { QLocale::Maltese, EOL }; |
264 | static const QLocale::Language welshLanguage[] = { QLocale::Welsh, EOL }; |
265 | static const QLocale::Language arabicLanguage[] = { QLocale::Arabic, EOL }; |
266 | static const QLocale::Language tagalogLanguage[] = { QLocale::Filipino, EOL }; |
267 | |
268 | static const QLocale::Territory frenchStyleCountries[] = { |
269 | // keep synchronized with frenchStyleLanguages |
270 | QLocale::AnyTerritory, |
271 | QLocale::AnyTerritory, |
272 | QLocale::AnyTerritory, |
273 | QLocale::Brazil, |
274 | QLocale::AnyTerritory, |
275 | QLocale::AnyTerritory, |
276 | QLocale::AnyTerritory |
277 | }; |
278 | struct NumerusTableEntry { |
279 | const uchar *rules; |
280 | int rulesSize; |
281 | const char * const *forms; |
282 | const QLocale::Language *languages; |
283 | const QLocale::Territory *countries; |
284 | const char * const gettextRules; |
285 | }; |
286 | |
287 | static const NumerusTableEntry numerusTable[] = { |
288 | { .rules: 0, .rulesSize: 0, .forms: japaneseStyleForms, .languages: japaneseStyleLanguages, .countries: 0, .gettextRules: "nplurals=1; plural=0;" }, |
289 | { .rules: englishStyleRules, .rulesSize: sizeof(englishStyleRules), .forms: englishStyleForms, .languages: englishStyleLanguages, .countries: 0, |
290 | .gettextRules: "nplurals=2; plural=(n != 1);" }, |
291 | { .rules: frenchStyleRules, .rulesSize: sizeof(frenchStyleRules), .forms: frenchStyleForms, .languages: frenchStyleLanguages, |
292 | .countries: frenchStyleCountries, .gettextRules: "nplurals=2; plural=(n > 1);" }, |
293 | { .rules: latvianRules, .rulesSize: sizeof(latvianRules), .forms: latvianForms, .languages: latvianLanguage, .countries: 0, |
294 | .gettextRules: "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);" }, |
295 | { .rules: icelandicRules, .rulesSize: sizeof(icelandicRules), .forms: icelandicForms, .languages: icelandicLanguage, .countries: 0, |
296 | .gettextRules: "nplurals=2; plural=(n%10==1 && n%100!=11 ? 0 : 1);" }, |
297 | { .rules: irishStyleRules, .rulesSize: sizeof(irishStyleRules), .forms: irishStyleForms, .languages: irishStyleLanguages, .countries: 0, |
298 | .gettextRules: "nplurals=3; plural=(n==1 ? 0 : n==2 ? 1 : 2);" }, |
299 | { .rules: gaelicStyleRules, .rulesSize: sizeof(gaelicStyleRules), .forms: gaelicStyleForms, .languages: gaelicStyleLanguages, .countries: 0, |
300 | .gettextRules: "nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3;" }, |
301 | { .rules: slovakStyleRules, .rulesSize: sizeof(slovakStyleRules), .forms: slovakStyleForms, .languages: slovakStyleLanguages, .countries: 0, |
302 | .gettextRules: "nplurals=3; plural=((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2);" }, |
303 | { .rules: macedonianRules, .rulesSize: sizeof(macedonianRules), .forms: macedonianForms, .languages: macedonianLanguage, .countries: 0, |
304 | .gettextRules: "nplurals=3; plural=(n%100==1 ? 0 : n%100==2 ? 1 : 2);" }, |
305 | { .rules: lithuanianRules, .rulesSize: sizeof(lithuanianRules), .forms: lithuanianForms, .languages: lithuanianLanguage, .countries: 0, |
306 | .gettextRules: "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);" }, |
307 | { .rules: russianStyleRules, .rulesSize: sizeof(russianStyleRules), .forms: russianStyleForms, .languages: russianStyleLanguages, .countries: 0, |
308 | .gettextRules: "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, |
309 | { .rules: polishRules, .rulesSize: sizeof(polishRules), .forms: polishForms, .languages: polishLanguage, .countries: 0, |
310 | .gettextRules: "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, |
311 | { .rules: romanianRules, .rulesSize: sizeof(romanianRules), .forms: romanianForms, .languages: romanianLanguages, .countries: 0, |
312 | .gettextRules: "nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2);" }, |
313 | { .rules: slovenianRules, .rulesSize: sizeof(slovenianRules), .forms: slovenianForms, .languages: slovenianLanguage, .countries: 0, |
314 | .gettextRules: "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);" }, |
315 | { .rules: malteseRules, .rulesSize: sizeof(malteseRules), .forms: malteseForms, .languages: malteseLanguage, .countries: 0, |
316 | .gettextRules: "nplurals=4; plural=(n==1 ? 0 : (n==0 || (n%100>=1 && n%100<=10)) ? 1 : (n%100>=11 && n%100<=19) ? 2 : 3);" }, |
317 | { .rules: welshRules, .rulesSize: sizeof(welshRules), .forms: welshForms, .languages: welshLanguage, .countries: 0, |
318 | .gettextRules: "nplurals=5; plural=(n==0 ? 0 : n==1 ? 1 : (n>=2 && n<=5) ? 2 : n==6 ? 3 : 4);" }, |
319 | { .rules: arabicRules, .rulesSize: sizeof(arabicRules), .forms: arabicForms, .languages: arabicLanguage, .countries: 0, |
320 | .gettextRules: "nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : (n%100>=3 && n%100<=10) ? 3 : n%100>=11 ? 4 : 5);" }, |
321 | { .rules: tagalogRules, .rulesSize: sizeof(tagalogRules), .forms: tagalogForms, .languages: tagalogLanguage, .countries: 0, |
322 | .gettextRules: "nplurals=3; plural=(n==1 ? 0 : (n%10==4 || n%10==6 || n%10== 9) ? 1 : 2);" }, |
323 | }; |
324 | |
325 | static const int NumerusTableSize = sizeof(numerusTable) / sizeof(numerusTable[0]); |
326 | |
327 | bool getNumerusInfo(QLocale::Language language, QLocale::Territory country, |
328 | QByteArray *rules, QStringList *forms, const char **gettextRules) |
329 | { |
330 | while (true) { |
331 | for (int i = 0; i < NumerusTableSize; ++i) { |
332 | const NumerusTableEntry &entry = numerusTable[i]; |
333 | for (int j = 0; entry.languages[j] != EOL; ++j) { |
334 | if (entry.languages[j] == language |
335 | && ((!entry.countries && country == QLocale::AnyTerritory) |
336 | || (entry.countries && entry.countries[j] == country))) { |
337 | if (rules) { |
338 | *rules = QByteArray::fromRawData(data: reinterpret_cast<const char *>(entry.rules), |
339 | size: entry.rulesSize); |
340 | } |
341 | if (gettextRules) |
342 | *gettextRules = entry.gettextRules; |
343 | if (forms) { |
344 | forms->clear(); |
345 | for (int k = 0; entry.forms[k]; ++k) |
346 | forms->append(t: QLatin1String(entry.forms[k])); |
347 | } |
348 | return true; |
349 | } |
350 | } |
351 | } |
352 | |
353 | if (country == QLocale::AnyTerritory) |
354 | break; |
355 | country = QLocale::AnyTerritory; |
356 | } |
357 | return false; |
358 | } |
359 | |
360 | QString getNumerusInfoString() |
361 | { |
362 | QStringList langs; |
363 | |
364 | for (int i = 0; i < NumerusTableSize; ++i) { |
365 | const NumerusTableEntry &entry = numerusTable[i]; |
366 | for (int j = 0; entry.languages[j] != EOL; ++j) { |
367 | QLocale loc(entry.languages[j], entry.countries ? entry.countries[j] |
368 | : QLocale::AnyTerritory); |
369 | QString lang = QLocale::languageToString(language: entry.languages[j]); |
370 | if (loc.language() == QLocale::C) |
371 | lang += QLatin1String(" (!!!)" ); |
372 | else if (entry.countries && entry.countries[j] != QLocale::AnyTerritory) |
373 | lang += QLatin1String(" (%1)" ).arg(args: QLocale::territoryToString(territory: loc.territory())); |
374 | else |
375 | lang += QLatin1String(" [%1]" ).arg(args: QLocale::territoryToString(territory: loc.territory())); |
376 | langs << QString::fromLatin1(ba: "%1 %2 %3\n" ).arg(a: lang, fieldWidth: -40).arg(a: loc.name(), fieldWidth: -8) |
377 | .arg(a: QString::fromLatin1(ba: entry.gettextRules)); |
378 | } |
379 | } |
380 | langs.sort(); |
381 | return langs.join(sep: QString()); |
382 | } |
383 | |
384 | QT_END_NAMESPACE |
385 | |