localization.cpp
1 #include "localization.h"
2 
3 #include <QApplication>
4 #include <QDebug>
5 #include <QDir>
6 #include <QFile>
7 #include <QLibraryInfo>
8 #include <QMessageBox>
9 #include <QTranslator>
10 #include <QSet>
11 #include <QStringList>
12 #include "plugins/engineplugin.h"
13 #include "plugins/pluginloader.h"
14 #include "datapaths.h"
15 #include "log.h"
16 
17 const QString DEFINITION_FILE_PATTERN = "*.def";
18 const QString TRANSLATIONS_LOCATION_SUBDIR = "translations";
19 const int NUM_VALID_VERSION_TOKENS = 2;
20 const int NUM_VALID_TOKENS = 3;
21 
22 QList<QTranslator*> Localization::currentlyLoadedTranslations;
23 
24 class Localization::LocalizationLoader
25 {
26  public:
27  LocalizationLoader() {};
28 
29  QList<LocalizationInfo> loadLocalizationsList(const QStringList& definitionsFileSearchDirs);
30 
31  private:
32  QList<LocalizationInfo> localizations;
33 
34  void loadLocalizationsListFile(const QString& definitionsFilePath);
35  void loadLocalizationsListFile(QIODevice& io);
36 
43  int obtainVersion(QIODevice& io);
44 
45  void sort();
46 };
47 
48 bool localizationInfoLessThan(const LocalizationInfo &o1, const LocalizationInfo &o2)
49 {
50  return o1.localeName.toLower() < o2.localeName.toLower();
51 }
52 
53 QList<LocalizationInfo> Localization::loadLocalizationsList(const QStringList& definitionsFileSearchDirs)
54 {
55  LocalizationLoader l;
56  return l.loadLocalizationsList(definitionsFileSearchDirs);
57 }
58 
59 bool Localization::loadTranslation(const QString& localeName)
60 {
61  // Out with the old.
62  qDeleteAll(currentlyLoadedTranslations);
63  currentlyLoadedTranslations.clear();
64  if (localeName == "en_EN")
65  return true;
66  // In with the new.
67  QStringList searchPaths = DataPaths::staticDataSearchDirs(
68  TRANSLATIONS_LOCATION_SUBDIR);
69  // Qt library translator.
70  installQtTranslations(localeName, searchPaths);
71 
72  // Doomseeker translator.
73  bool installed = installTranslation(localeName, searchPaths);
74 
75  // Plugins translators.
76  foreach (const PluginLoader::Plugin *plugin, gPlugins->plugins())
77  {
78  QString name = plugin->info()->nameCanonical();
79  QTranslator *pluginTranslator = loadTranslationFile(
80  QString("%1_%2").arg(name, localeName),
81  searchPaths);
82  if (pluginTranslator)
83  {
84  gLog << QString("Loaded translation for plugin %1").arg(name);
85  QCoreApplication::installTranslator(pluginTranslator);
86  currentlyLoadedTranslations.append(pluginTranslator);
87  }
88  }
89  return installed;
90 }
91 
92 void Localization::installQtTranslations(const QString &localeName, QStringList searchPaths)
93 {
94  // First let's try to load translation that is bundled with program.
95  // This behavior is valid for Windows.
96  //
97  // If Qt translation is not bundled with program then try to load
98  // it from system location. This behavior is valid for Linux.
99  searchPaths << QLibraryInfo::location(QLibraryInfo::TranslationsPath);
100 
101 #if (QT_VERSION < QT_VERSION_CHECK(5, 3, 0))
102  // https://sourceforge.net/p/nootka/hg/ci/d00c13d9223b5bf74802aaec209ebc171efb27ce/tree/src/libs/core/tinitcorelib.cpp?diff=ce9e1fc25b8c23c9713c0cfa4c350d5ac2452fab
103  installTranslation("qt_" + localeName, searchPaths);
104 #else
105  installTranslation("qtbase_" + localeName, searchPaths);
106  installTranslation("qtmultimedia_" + localeName, searchPaths);
107 #endif
108 }
109 
110 bool Localization::installTranslation(const QString &translationName, const QStringList &searchPaths)
111 {
112  QTranslator* translator = loadTranslationFile(translationName, searchPaths);
113  if (translator != NULL)
114  {
115  QCoreApplication::installTranslator(translator);
116  currentlyLoadedTranslations.append(translator);
117  return true;
118  }
119  else
120  {
121  gLog << QString("Translation '%1' not found.").arg(translationName);
122  return false;
123  }
124 }
125 
126 QTranslator* Localization::loadTranslationFile(const QString& translationName, const QStringList& searchPaths)
127 {
128  QTranslator* pTranslator = new QTranslator();
129  bool bLoaded = false;
130  foreach (const QString& dir, searchPaths)
131  {
132  if (pTranslator->load(translationName, dir))
133  {
134  gLog << QString("Found translation '%1' in dir '%2'.").arg(translationName, dir);
135  bLoaded = true;
136  break;
137  }
138  }
139  if (!bLoaded)
140  {
141  delete pTranslator;
142  pTranslator = NULL;
143  }
144  return pTranslator;
145 }
147 QList<LocalizationInfo> Localization::LocalizationLoader::loadLocalizationsList(const QStringList& definitionsFileSearchDirs)
148 {
149  foreach (const QString& dirPath, definitionsFileSearchDirs)
150  {
151  loadLocalizationsListFile(dirPath);
152  }
153  sort();
154  return localizations;
155 }
156 
157 void Localization::LocalizationLoader::loadLocalizationsListFile(const QString& definitionsFilePath)
158 {
159  QDir dir(definitionsFilePath);
160  QStringList defFiles = dir.entryList(QStringList(DEFINITION_FILE_PATTERN), QDir::Files);
161  foreach (const QString& defFileName, defFiles)
162  {
163  // No point in translating strings in this class because
164  // translation is not loaded yet.
165  gLog << QString("Reading localizations definitions file: %1").arg(defFileName);
166  QString filePath = dir.absoluteFilePath(defFileName);
167  QFile file(filePath);
168  if (file.open(QIODevice::ReadOnly))
169  {
170  loadLocalizationsListFile(file);
171  file.close();
172  }
173  else
174  {
175  gLog << QString("Failed to open localizations definitions file: %1").arg(definitionsFilePath);
176  }
177  }
178 }
179 
180 void Localization::LocalizationLoader::loadLocalizationsListFile(QIODevice& io)
181 {
182  int version = obtainVersion(io);
183  if (version <= 0)
184  {
185  gLog << QString("Translation definition file doesn't contain valid protocol version information.");
186  return;
187  }
188 
189  QString line = io.readLine();
190  while (!line.isEmpty())
191  {
192  line = line.trimmed();
193  // Discard empty and comment lines.
194  if (!line.isEmpty() && !line.startsWith("#"))
195  {
196  QStringList tokens = line.split(";");
197  if (tokens.size() == NUM_VALID_TOKENS)
198  {
199  LocalizationInfo info;
200  info.countryCodeName = tokens[0].trimmed();
201  info.localeName = tokens[1].trimmed();
202  info.niceName = tokens[2].trimmed();
203  if (!localizations.contains(info))
204  {
205  localizations.append(info);
206  }
207  }
208  else
209  {
210  gLog << QString("Invalid localization definition: %1").arg(line);
211  }
212  }
213  line = io.readLine();
214  }
215 }
216 
217 int Localization::LocalizationLoader::obtainVersion(QIODevice& io)
218 {
219  QString line = io.readLine();
220  int version = -1;
221  if (!line.isNull())
222  {
223  // First line contains protocol version.
224  QStringList tokens = line.split("=");
225  if (tokens.size() == NUM_VALID_VERSION_TOKENS)
226  {
227  QString versionToken = tokens[1];
228  bool bOk = false;
229  version = versionToken.toInt(&bOk);
230  if (!bOk)
231  {
232  version = -1;
233  }
234  }
235  }
236  return version;
237 }
238 
239 void Localization::LocalizationLoader::sort()
240 {
241  qSort(localizations.begin(), localizations.end(), localizationInfoLessThan);
242 }
QString localeName
Compliant with language_country standard. See QLocale::name()
QString niceName
Name that will be displayed to user.
EnginePlugin * info() const
Main plugin interface.
QString countryCodeName
The same as code used for country flags in IP2C.
QString nameCanonical() const
Derived from actual plugin name.
static QStringList staticDataSearchDirs(const QString &subdir=QString())
Paths to directories where program should search for its static data.
Definition: datapaths.cpp:296