1 /* LanguageUtils.cpp */
2
3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
4 *
5 * This file is part of sayonara player
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "LanguageUtils.h"
22
23 #include "Utils/Utils.h"
24 #include "Utils/FileUtils.h"
25 #include "Utils/Algorithm.h"
26 #include "Utils/Settings/Settings.h"
27 #include "Utils/StandardPaths.h"
28
29 #include <QFile>
30 #include <QRegExp>
31 #include <QDir>
32 #include <QMap>
33 #include <QSettings>
34 #include <QLocale>
35 #include <QLibraryInfo>
36
37 static bool s_test_mode = false;
38
39 class LanguageVersionHelper
40 {
41 QSettings* mSettings = nullptr;
42
LanguageVersionHelper()43 LanguageVersionHelper()
44 {
45 const auto filepath = (s_test_mode == false)
46 ? QDir(Util::translationsPath()).absoluteFilePath("versions")
47 : Util::tempPath("versions");
48
49 mSettings = new QSettings(filepath, QSettings::NativeFormat);
50 }
51
52 public:
~LanguageVersionHelper()53 ~LanguageVersionHelper()
54 {
55 delete mSettings;
56 }
57
instance()58 static LanguageVersionHelper* instance()
59 {
60 static LanguageVersionHelper hlp;
61 return &hlp;
62 }
63
setLanguageVersion(const QString & languageCode,const QString & version)64 void setLanguageVersion(const QString& languageCode, const QString& version)
65 {
66 // never allow to downgrade a downloaded version
67 if(isOutdated(languageCode))
68 {
69 mSettings->setValue(languageCode, version);
70 }
71 }
72
getLanguageVersion(const QString & languageCode)73 QString getLanguageVersion(const QString& languageCode)
74 {
75 return mSettings->value(languageCode).toString();
76 }
77
isOutdated(const QString & languageCode)78 bool isOutdated(const QString& languageCode)
79 {
80 QString lv = getLanguageVersion(languageCode);
81 QString pv = GetSetting(::Set::Player_Version);
82
83 if(lv.isEmpty())
84 {
85 return true;
86 }
87
88 return (lv < pv);
89 }
90 };
91
92 namespace Language = Util::Language;
93
checkLanguageCode(const QString & languageCode)94 static bool checkLanguageCode(const QString& languageCode)
95 {
96 QRegExp re("^[a-z]{2}(_[A-Z]{2})?(\\.[A-Z0-9\\-]+[0-9])?$");
97 int idx = re.indexIn(languageCode);
98
99 return (idx == 0);
100 }
101
getSharePath(const QString & languageCode)102 QString Language::getSharePath(const QString& languageCode)
103 {
104 if(!checkLanguageCode(languageCode))
105 {
106 return QString();
107 }
108 return Util::sharePath("translations") + "/" + QString("sayonara_lang_%1.qm").arg(languageCode);
109 }
110
getFtpPath(const QString & languageCode)111 QString Language::getFtpPath(const QString& languageCode)
112 {
113 if(!checkLanguageCode(languageCode))
114 {
115 return QString();
116 }
117 return QString("ftp://sayonara-player.com/translation/sayonara_lang_%1.qm").arg(languageCode);
118 }
119
getHttpPath(const QString & languageCode)120 QString Language::getHttpPath(const QString& languageCode)
121 {
122 if(!checkLanguageCode(languageCode))
123 {
124 return QString();
125 }
126
127 return QString("https://sayonara-player.com/files/translation/sayonara_lang_%1.qm").arg(
128 languageCode);
129 }
130
getChecksumFtpPath()131 QString Language::getChecksumFtpPath()
132 {
133 return "ftp://sayonara-player.com/translation/checksum";
134 }
135
getChecksumHttpPath()136 QString Language::getChecksumHttpPath()
137 {
138 return "https://sayonara-player.com/files/translation/checksum";
139 }
140
getHomeTargetPath(const QString & languageCode)141 QString Language::getHomeTargetPath(const QString& languageCode)
142 {
143 if(!checkLanguageCode(languageCode))
144 {
145 return QString();
146 }
147
148 const auto translationDir = Util::translationsPath();
149 const auto languagePath = QString("%1/sayonara_lang_%2.qm")
150 .arg(translationDir)
151 .arg(languageCode);
152
153 return languagePath;
154 }
155
isOutdated(const QString & languageCode)156 bool Language::isOutdated(const QString& languageCode)
157 {
158 return LanguageVersionHelper::instance()->isOutdated(languageCode);
159 }
160
getSimilarLanguage4(const QString & languageCode)161 QString Language::getSimilarLanguage4(const QString& languageCode)
162 {
163 if(!checkLanguageCode(languageCode))
164 {
165 return QString();
166 }
167
168 const auto twoLetter = languageCode.left(2);
169 const auto translationsPaths = QStringList
170 {
171 Util::translationsPath(),
172 Util::sharePath("translations")
173 };
174
175 for(const auto& translationPath : translationsPaths)
176 {
177 if(!Util::File::exists(translationPath))
178 {
179 continue;
180 }
181
182 const auto dir = QDir(translationPath);
183 const auto entries = dir.entryList(QDir::Files);
184 const auto re = QRegExp("sayonara_lang_([a-z]{2})_.+qm");
185
186 for(const auto& entry : entries)
187 {
188 if(re.indexIn(entry) < 0)
189 {
190 continue;
191 }
192
193 const auto entryTwoLetter = re.cap(1);
194 if(entryTwoLetter == twoLetter)
195 {
196 return translationPath + "/" + entry;
197 }
198 }
199 }
200
201 return QString();
202 }
203
getUsedLanguageFile(const QString & languageCode)204 QString Language::getUsedLanguageFile(const QString& languageCode)
205 {
206 if(!checkLanguageCode(languageCode))
207 {
208 return QString();
209 }
210
211 { // check if home path or share path version is better or exists
212 if(isOutdated(languageCode)) // not available or older than in share path
213 {
214 const auto languageInSharePath = getSharePath(languageCode);
215 if(Util::File::exists(languageInSharePath))
216 {
217 return languageInSharePath;
218 }
219 }
220
221 if(Util::File::exists(getHomeTargetPath(languageCode)))
222 {
223 return getHomeTargetPath(languageCode);
224 }
225 }
226
227 { // try to find from other region
228 return getSimilarLanguage4(languageCode);
229 }
230 }
231
extractLanguageCode(const QString & languageFile)232 QString Language::extractLanguageCode(const QString& languageFile)
233 {
234 const auto re = QRegExp
235 (
236 ".*sayonara_lang_"
237 "([a-z]{2}(_[A-Z]{2})?(\\.[A-Z0-9\\-]+[0-9])?)\\.(ts|qm)$"
238 );
239
240 const auto idx = re.indexIn(languageFile);
241 if(idx < 0)
242 {
243 return QString();
244 }
245
246 const auto languageCode = re.cap(1);
247 if(!checkLanguageCode(languageCode))
248 {
249 return QString();
250 }
251
252 return languageCode;
253 }
254
getIconPath(const QString & languageCode)255 QString Language::getIconPath(const QString& languageCode)
256 {
257 if(!checkLanguageCode(languageCode))
258 {
259 return QString();
260 }
261
262 auto filename = Util::sharePath("translations/icons/%1.png").arg(languageCode);
263 if(!QFile(filename).exists())
264 {
265 filename = Util::sharePath("translations/icons/%1.png").arg(languageCode.left(2));
266 if(!QFile(filename).exists())
267 {
268 return QString();
269 }
270 }
271
272 return filename;
273 }
274
getChecksum(const QString & languageCode)275 QString Language::getChecksum(const QString& languageCode)
276 {
277 if(!checkLanguageCode(languageCode))
278 {
279 return QString();
280 }
281
282 const auto path = getUsedLanguageFile(languageCode);
283 return QString::fromUtf8(Util::File::getMD5Sum(path));
284 }
285
importLanguageFile(const QString & filename)286 bool Language::importLanguageFile(const QString& filename)
287 {
288 const auto languageCode = extractLanguageCode(filename);
289 if(languageCode.isEmpty())
290 {
291 return false;
292 }
293
294 const auto targetPath = Language::getHomeTargetPath(languageCode);
295 auto file = QFile(filename);
296 bool success = file.copy(targetPath);
297 if(success)
298 {
299 updateLanguageVersion(languageCode);
300 }
301
302 return success;
303 }
304
getLanguageVersion(const QString & languageCode)305 QString Language::getLanguageVersion(const QString& languageCode)
306 {
307 if(!Util::File::exists(getHomeTargetPath(languageCode)))
308 {
309 LanguageVersionHelper::instance()->setLanguageVersion(languageCode, QString());
310 return QString();
311 }
312
313 return LanguageVersionHelper::instance()->getLanguageVersion(languageCode);
314 }
315
updateLanguageVersion(const QString & languageCode)316 void Language::updateLanguageVersion(const QString& languageCode)
317 {
318 const auto version = Util::File::exists(getHomeTargetPath(languageCode))
319 ? GetSetting(::Set::Player_Version)
320 : QString();
321
322 LanguageVersionHelper::instance()->setLanguageVersion(languageCode, version);
323 }
324
325 #ifdef SAYONARA_WITH_TESTS
326
setTestMode()327 void Language::setTestMode()
328 {
329 s_test_mode = true;
330 }
331
setLanguageVersion(const QString & languageCode,const QString & version)332 void Language::setLanguageVersion(const QString& languageCode, const QString& version)
333 {
334 LanguageVersionHelper::instance()->setLanguageVersion(languageCode, version);
335 }
336
337 #endif
338
getCurrentLocale()339 QLocale Language::getCurrentLocale()
340 {
341 const auto languageCode = GetSetting(::Set::Player_Language);
342 return QLocale(languageCode);
343 }
344
getCurrentQtTranslationPaths()345 QStringList Language::getCurrentQtTranslationPaths()
346 {
347 const auto languageCode = GetSetting(::Set::Player_Language);
348 if(languageCode.size() < 2)
349 {
350 return QStringList();
351 }
352
353 const auto twoLetter = languageCode.left(2);
354 const auto filePrefixes = QStringList
355 {
356 "qt",
357 "qtbase",
358 "qtlocation"
359 };
360
361 QStringList paths;
362 Util::Algorithm::transform(filePrefixes, paths, [twoLetter](const QString& prefix) {
363 return QString("%1/%2_%3.qm")
364 .arg(QLibraryInfo::location(QLibraryInfo::TranslationsPath))
365 .arg(prefix)
366 .arg(twoLetter);
367 });
368
369 QStringList ret;
370 Util::Algorithm::copyIf(paths, ret, [](const auto& path) {
371 return (Util::File::exists(path));
372 });
373
374 return ret;
375 }
376
377
convertOldLanguage(const QString & oldLanguageName)378 QString Language::convertOldLanguage(const QString& oldLanguageName)
379 {
380 const auto languages = availableLanguages().keys();
381 const auto languageCode = extractLanguageCode(oldLanguageName);
382 if(languageCode.size() != 2){
383 return "en";
384 }
385
386 const auto it = Util::Algorithm::find(languages, [&](const auto& language){
387 return (language.startsWith(languageCode) && (languages.size() > 4));
388 });
389
390 return (it != languages.end()) ? *it : "en";
391 }
392
availableLanguages()393 QMap<QString, QLocale> Language::availableLanguages()
394 {
395 const auto directories = QList<QDir>
396 {
397 QDir(Util::translationsSharePath()),
398 QDir(Util::translationsPath())
399 };
400
401 QMap<QString, QLocale> ret;
402 for(const auto& directory : directories)
403 {
404 if(!directory.exists()) {
405 continue;
406 }
407
408 const auto entries = directory.entryList(QStringList{"*.qm"}, QDir::Files);
409 for(const auto& entry : entries)
410 {
411 const auto key = extractLanguageCode(entry);
412 if(!key.isEmpty()) {
413 ret[key] = QLocale(key);
414 }
415 }
416 }
417
418 ret.remove("en_US");
419
420 return ret;
421 }