ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

translation.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 #if defined(WIN32)
00024 #define WIN32_LEAN_AND_MEAN
00025 #include <windows.h>
00026 #endif
00027 
00028 #define TRANSLATIONS_DAT_VER 3
00029 
00030 #include "common/translation.h"
00031 #include "common/config-manager.h"
00032 #include "common/file.h"
00033 #include "common/fs.h"
00034 #include "common/system.h"
00035 #include "common/textconsole.h"
00036 
00037 #ifdef USE_TRANSLATION
00038 
00039 namespace Common {
00040 
00041 DECLARE_SINGLETON(TranslationManager);
00042 
00043 bool operator<(const TLanguage &l, const TLanguage &r) {
00044     return strcmp(l.name, r.name) < 0;
00045 }
00046 
00047 TranslationManager::TranslationManager() : _currentLang(-1), _charmap(nullptr) {
00048     loadTranslationsInfoDat();
00049 
00050     // Set the default language
00051     setLanguage("");
00052 }
00053 
00054 TranslationManager::~TranslationManager() {
00055     delete[] _charmap;
00056 }
00057 
00058 int32 TranslationManager::findMatchingLanguage(const String &lang) {
00059     uint langLength = lang.size();
00060     uint numLangs = _langs.size();
00061 
00062     // Try to match languages of the same length or longer ones
00063     // that can be cut at the length of the given one.
00064     for (uint i = 0; i < numLangs; ++i) {
00065         uint iLength = _langs[i].size();
00066         if (iLength >= langLength) {
00067             // Found a candidate; compare the full string by default.
00068             String cmpLang = _langs[i];
00069 
00070             if ((iLength > langLength) && (_langs[i][langLength] == '_')) {
00071                 // It has a separation mark at the length of the
00072                 // requested language, so we can cut it.
00073                 cmpLang = String(_langs[i].c_str(), langLength);
00074             }
00075             if (lang.equalsIgnoreCase(cmpLang))
00076                 return i;
00077         }
00078     }
00079 
00080     // Couldn't find a matching language.
00081     return -1;
00082 }
00083 
00084 void TranslationManager::setLanguage(const String &lang) {
00085     // Get lang index.
00086     int langIndex = -1;
00087     String langStr(lang);
00088     if (langStr.empty())
00089         langStr = g_system->getSystemLanguage();
00090 
00091     // Search for the given language or a variant of it.
00092     langIndex = findMatchingLanguage(langStr);
00093 
00094     // Try to find a partial match taking away parts of the original language.
00095     const char *lastSep;
00096     String langCut(langStr);
00097     while ((langIndex == -1) && (lastSep = strrchr(langCut.c_str(), '_'))) {
00098         langCut = String(langCut.c_str(), lastSep);
00099         langIndex = findMatchingLanguage(langCut);
00100     }
00101 
00102     // Load messages for that language.
00103     // Call it even if the index is -1 to unload previously loaded translations.
00104     if (langIndex != _currentLang) {
00105         loadLanguageDat(langIndex);
00106         _currentLang = langIndex;
00107     }
00108 }
00109 
00110 const char *TranslationManager::getTranslation(const char *message) const {
00111     return getTranslation(message, nullptr);
00112 }
00113 
00114 const char *TranslationManager::getTranslation(const char *message, const char *context) const {
00115     // If no language is set or message is empty, return msgid as is
00116     if (_currentTranslationMessages.empty() || *message == '\0')
00117         return message;
00118 
00119     // Binary-search for the msgid
00120     int leftIndex = 0;
00121     int rightIndex = _currentTranslationMessages.size() - 1;
00122 
00123     while (rightIndex >= leftIndex) {
00124         const int midIndex = (leftIndex + rightIndex) / 2;
00125         const PoMessageEntry *const m = &_currentTranslationMessages[midIndex];
00126 
00127         int compareResult = strcmp(message, _messageIds[m->msgid].c_str());
00128 
00129         if (compareResult == 0) {
00130             // Get the range of messages with the same ID (but different context)
00131             leftIndex = rightIndex = midIndex;
00132             while (
00133                 leftIndex > 0 &&
00134                 _currentTranslationMessages[leftIndex - 1].msgid == m->msgid
00135             ) {
00136                 --leftIndex;
00137             }
00138             while (
00139                 rightIndex < (int)_currentTranslationMessages.size() - 1 &&
00140                 _currentTranslationMessages[rightIndex + 1].msgid == m->msgid
00141             ) {
00142                 ++rightIndex;
00143             }
00144             // Find the context we want
00145             if (context == nullptr || *context == '\0' || leftIndex == rightIndex)
00146                 return _currentTranslationMessages[leftIndex].msgstr.c_str();
00147             // We could use again binary search, but there should be only a small number of contexts.
00148             while (rightIndex > leftIndex) {
00149                 compareResult = strcmp(context, _currentTranslationMessages[rightIndex].msgctxt.c_str());
00150                 if (compareResult == 0)
00151                     return _currentTranslationMessages[rightIndex].msgstr.c_str();
00152                 else if (compareResult > 0)
00153                     break;
00154                 --rightIndex;
00155             }
00156             return _currentTranslationMessages[leftIndex].msgstr.c_str();
00157         } else if (compareResult < 0)
00158             rightIndex = midIndex - 1;
00159         else
00160             leftIndex = midIndex + 1;
00161     }
00162 
00163     return message;
00164 }
00165 
00166 String TranslationManager::getCurrentCharset() const {
00167     if (_currentCharset.empty())
00168         return "ASCII";
00169     return _currentCharset;
00170 }
00171 
00172 String TranslationManager::getCurrentLanguage() const {
00173     if (_currentLang == -1)
00174         return "C";
00175     return _langs[_currentLang];
00176 }
00177 
00178 String TranslationManager::getTranslation(const String &message) const {
00179     return getTranslation(message.c_str());
00180 }
00181 
00182 String TranslationManager::getTranslation(const String &message, const String &context) const {
00183     return getTranslation(message.c_str(), context.c_str());
00184 }
00185 
00186 const TLangArray TranslationManager::getSupportedLanguageNames() const {
00187     TLangArray languages;
00188 
00189     for (unsigned int i = 0; i < _langNames.size(); i++) {
00190         TLanguage lng(_langNames[i].c_str(), i + 1);
00191         languages.push_back(lng);
00192     }
00193 
00194     sort(languages.begin(), languages.end());
00195 
00196     return languages;
00197 }
00198 
00199 int TranslationManager::parseLanguage(const String &lang) const {
00200     for (unsigned int i = 0; i < _langs.size(); i++) {
00201         if (lang == _langs[i])
00202             return i + 1;
00203     }
00204 
00205     return kTranslationBuiltinId;
00206 }
00207 
00208 String TranslationManager::getLangById(int id) const {
00209     switch (id) {
00210     case kTranslationAutodetectId:
00211         return "";
00212     case kTranslationBuiltinId:
00213         return "C";
00214     default:
00215         if (id >= 0 && id - 1 < (int)_langs.size())
00216             return _langs[id - 1];
00217     }
00218 
00219     // In case an invalid ID was specified, we will output a warning
00220     // and return the same value as the auto detection id.
00221     warning("Invalid language id %d passed to TranslationManager::getLangById", id);
00222     return "";
00223 }
00224 
00225 bool TranslationManager::openTranslationsFile(File &inFile) {
00226     // First look in the Themepath if we can find the file.
00227     if (ConfMan.hasKey("themepath") && openTranslationsFile(FSNode(ConfMan.get("themepath")), inFile))
00228         return true;
00229 
00230     // Then try to open it using the SearchMan.
00231     ArchiveMemberList fileList;
00232     SearchMan.listMatchingMembers(fileList, "translations.dat");
00233     for (ArchiveMemberList::iterator it = fileList.begin(); it != fileList.end(); ++it) {
00234         ArchiveMember       const &m      = **it;
00235         SeekableReadStream *const  stream = m.createReadStream();
00236         if (stream && inFile.open(stream, m.getName())) {
00237             if (checkHeader(inFile))
00238                 return true;
00239             inFile.close();
00240         }
00241     }
00242 
00243     return false;
00244 }
00245 
00246 bool TranslationManager::openTranslationsFile(const FSNode &node, File &inFile, int depth) {
00247     if (!node.exists() || !node.isReadable() || !node.isDirectory())
00248         return false;
00249 
00250     // Check if we can find the file in this directory
00251     // Since File::open(FSNode) makes all the needed tests, it is not really
00252     // necessary to make them here. But it avoid printing warnings.
00253     FSNode fileNode = node.getChild("translations.dat");
00254     if (fileNode.exists() && fileNode.isReadable() && !fileNode.isDirectory()) {
00255         if (inFile.open(fileNode)) {
00256             if (checkHeader(inFile))
00257                 return true;
00258             inFile.close();
00259         }
00260     }
00261 
00262     // Check if we exceeded the given recursion depth
00263     if (depth - 1 == -1)
00264         return false;
00265 
00266     // Otherwise look for it in sub-directories
00267     FSList fileList;
00268     if (!node.getChildren(fileList, FSNode::kListDirectoriesOnly))
00269         return false;
00270 
00271     for (FSList::iterator i = fileList.begin(); i != fileList.end(); ++i) {
00272         if (openTranslationsFile(*i, inFile, depth == -1 ? - 1 : depth - 1))
00273             return true;
00274     }
00275 
00276     // Not found in this directory or its sub-directories
00277     return false;
00278 }
00279 
00280 void TranslationManager::loadTranslationsInfoDat() {
00281     File in;
00282     if (!openTranslationsFile(in)) {
00283         warning("You are missing a valid 'translations.dat' file. GUI translation will not be available");
00284         return;
00285     }
00286 
00287     char buf[256];
00288     int len;
00289 
00290     // Get number of translations
00291     int nbTranslations = in.readUint16BE();
00292 
00293     // Get number of codepages
00294     int nbCodepages = in.readUint16BE();
00295 
00296     // Determine where the codepages start
00297     _charmapStart = 0;
00298     for (int i = 0; i < nbTranslations + 3; ++i)
00299         _charmapStart += in.readUint16BE();
00300     _charmapStart += in.pos();
00301 
00302     // Read list of languages
00303     _langs.resize(nbTranslations);
00304     _langNames.resize(nbTranslations);
00305     for (int i = 0; i < nbTranslations; ++i) {
00306         len = in.readUint16BE();
00307         in.read(buf, len);
00308         _langs[i] = String(buf, len - 1);
00309         len = in.readUint16BE();
00310         in.read(buf, len);
00311         _langNames[i] = String(buf, len - 1);
00312     }
00313 
00314     // Read list of codepages
00315     _charmaps.resize(nbCodepages);
00316     for (int i = 0; i < nbCodepages; ++i) {
00317         len = in.readUint16BE();
00318         in.read(buf, len);
00319         _charmaps[i] = String(buf, len - 1);
00320     }
00321 
00322     // Read messages
00323     int numMessages = in.readUint16BE();
00324     _messageIds.resize(numMessages);
00325     for (int i = 0; i < numMessages; ++i) {
00326         len = in.readUint16BE();
00327         String msg;
00328         while (len > 0) {
00329             in.read(buf, len > 256 ? 256 : len);
00330             msg += String(buf, len > 256 ? 256 : len - 1);
00331             len -= 256;
00332         }
00333         _messageIds[i] = msg;
00334     }
00335 }
00336 
00337 void TranslationManager::loadLanguageDat(int index) {
00338     _currentTranslationMessages.clear();
00339     _currentCharset.clear();
00340     // Sanity check
00341     if (index < 0 || index >= (int)_langs.size()) {
00342         if (index != -1)
00343             warning("Invalid language index %d passed to TranslationManager::loadLanguageDat", index);
00344         return;
00345     }
00346 
00347     File in;
00348     if (!openTranslationsFile(in))
00349         return;
00350 
00351     char buf[1024];
00352     int len;
00353 
00354     // Get number of translations
00355     int nbTranslations = in.readUint16BE();
00356     if (nbTranslations != (int)_langs.size()) {
00357         warning("The 'translations.dat' file has changed since starting ResidualVM. GUI translation will not be available");
00358         return;
00359     }
00360 
00361     // Get the number of codepages
00362     int nbCodepages = in.readUint16BE();
00363     if (nbCodepages != (int)_charmaps.size()) {
00364         warning("The 'translations.dat' file has changed since starting ResidualVM. GUI translation will not be available");
00365         return;
00366     }
00367 
00368     // Get size of blocks to skip.
00369     int skipSize = 0;
00370     for (int i = 0; i < index + 3; ++i)
00371         skipSize += in.readUint16BE();
00372     // We also need to skip the remaining block sizes
00373     skipSize += 2 * (nbTranslations - index);
00374 
00375     // Seek to start of block we want to read
00376     in.seek(skipSize, SEEK_CUR);
00377 
00378     // Read number of translated messages
00379     int nbMessages = in.readUint16BE();
00380     _currentTranslationMessages.resize(nbMessages);
00381 
00382     // Read charset
00383     len = in.readUint16BE();
00384     in.read(buf, len);
00385     _currentCharset = String(buf, len - 1);
00386 
00387     // Read messages
00388     for (int i = 0; i < nbMessages; ++i) {
00389         _currentTranslationMessages[i].msgid = in.readUint16BE();
00390         len = in.readUint16BE();
00391         String msg;
00392         while (len > 0) {
00393             in.read(buf, len > 256 ? 256 : len);
00394             msg += String(buf, len > 256 ? 256 : len - 1);
00395             len -= 256;
00396         }
00397         _currentTranslationMessages[i].msgstr = msg;
00398         len = in.readUint16BE();
00399         if (len > 0) {
00400             in.read(buf, len);
00401             _currentTranslationMessages[i].msgctxt = String(buf, len - 1);
00402         }
00403     }
00404 
00405     // Find the charset
00406     int charmapNum = -1;
00407     for (uint i = 0; i < _charmaps.size(); ++i) {
00408         if (_charmaps[i].equalsIgnoreCase(_currentCharset)) {
00409             charmapNum = i;
00410             break;
00411         }
00412     }
00413 
00414     // Setup the new charset mapping
00415     if (charmapNum == -1) {
00416         delete[] _charmap;
00417         _charmap = nullptr;
00418     } else {
00419         if (!_charmap)
00420             _charmap = new uint32[256];
00421 
00422         in.seek(_charmapStart + charmapNum * 256 * 4, SEEK_SET);
00423         for (int i = 0; i < 256; ++i)
00424             _charmap[i] = in.readUint32BE();
00425     }
00426 
00427 }
00428 
00429 bool TranslationManager::checkHeader(File &in) {
00430     char buf[13];
00431     int ver;
00432 
00433     in.read(buf, 12);
00434     buf[12] = '\0';
00435 
00436     // Check header
00437     if (strcmp(buf, "TRANSLATIONS") != 0) {
00438         warning("File '%s' is not a valid translations data file. Skipping this file", in.getName());
00439         return false;
00440     }
00441 
00442     // Check version
00443     ver = in.readByte();
00444 
00445     if (ver != TRANSLATIONS_DAT_VER) {
00446         warning("File '%s' has a mismatching version, expected was %d but you got %d. Skipping this file", in.getName(), TRANSLATIONS_DAT_VER, ver);
00447         return false;
00448     }
00449 
00450     return true;
00451 }
00452 
00453 } // End of namespace Common
00454 
00455 #endif // USE_TRANSLATION


Generated on Sat May 25 2019 05:00:55 for ResidualVM by doxygen 1.7.1
curved edge   curved edge