2010-06-15 10:44:51 +00:00
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers , whose names
* are too numerous to list here . Please refer to the COPYRIGHT
* file distributed with this source distribution .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version 2
* of the License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*/
2010-06-26 22:32:51 +00:00
# ifdef WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
// winnt.h defines ARRAYSIZE, but we want our own one... - this is needed before including util.h
# undef ARRAYSIZE
2010-06-15 10:44:51 +00:00
# endif
2010-08-23 19:45:14 +00:00
# define TRANSLATIONS_DAT_VER 2
2010-08-19 11:46:55 +00:00
2010-11-11 18:22:35 +00:00
# include "common/translation.h"
2010-08-28 15:51:23 +00:00
# include "common/config-manager.h"
2010-11-19 01:03:05 +00:00
# include "common/file.h"
# include "common/fs.h"
2010-11-25 18:42:05 +00:00
# include "common/system.h"
2011-04-24 08:34:27 +00:00
# include "common/textconsole.h"
2010-06-26 22:32:51 +00:00
2010-11-30 18:50:19 +00:00
# ifdef USE_TRANSLATION
2010-11-16 08:23:13 +00:00
DECLARE_SINGLETON ( Common : : TranslationManager ) ;
2010-06-26 22:32:51 +00:00
2010-06-15 10:44:51 +00:00
namespace Common {
2010-08-21 13:21:09 +00:00
bool operator < ( const TLanguage & l , const TLanguage & r ) {
return strcmp ( l . name , r . name ) < 0 ;
2010-08-20 21:04:39 +00:00
}
2010-06-15 10:44:51 +00:00
2010-08-19 11:46:55 +00:00
TranslationManager : : TranslationManager ( ) : _currentLang ( - 1 ) {
loadTranslationsInfoDat ( ) ;
2010-06-15 10:44:51 +00:00
// Set the default language
setLanguage ( " " ) ;
}
TranslationManager : : ~ TranslationManager ( ) {
}
2010-12-01 20:00:40 +00:00
int32 TranslationManager : : findMatchingLanguage ( const String & lang ) {
uint langLength = lang . size ( ) ;
uint numLangs = _langs . size ( ) ;
// Try to match languages of the same length or longer ones
// that can be cut at the length of the given one.
for ( uint i = 0 ; i < numLangs ; + + i ) {
uint iLength = _langs [ i ] . size ( ) ;
if ( iLength > = langLength ) {
// Found a candidate; compare the full string by default.
String cmpLang = _langs [ i ] ;
if ( ( iLength > langLength ) & & ( _langs [ i ] [ langLength ] = = ' _ ' ) ) {
// It has a separation mark at the length of the
// requested language, so we can cut it.
cmpLang = String ( _langs [ i ] . c_str ( ) , langLength ) ;
}
if ( lang . equalsIgnoreCase ( cmpLang ) )
return i ;
}
}
// Couldn't find a matching language.
return - 1 ;
}
2010-11-11 18:22:35 +00:00
void TranslationManager : : setLanguage ( const String & lang ) {
2010-12-01 20:00:40 +00:00
// Get lang index.
2010-08-19 11:46:55 +00:00
int langIndex = - 1 ;
String langStr ( lang ) ;
if ( langStr . empty ( ) )
2010-12-01 20:00:40 +00:00
langStr = g_system - > getSystemLanguage ( ) ;
2010-08-21 13:21:09 +00:00
2010-12-01 20:00:40 +00:00
// Search for the given language or a variant of it.
langIndex = findMatchingLanguage ( langStr ) ;
2010-08-21 13:21:09 +00:00
2010-12-01 20:00:40 +00:00
// Try to find a partial match taking away parts of the original language.
const char * lastSep ;
String langCut ( langStr ) ;
while ( ( langIndex = = - 1 ) & & ( lastSep = strrchr ( langCut . c_str ( ) , ' _ ' ) ) ) {
langCut = String ( langCut . c_str ( ) , lastSep ) ;
langIndex = findMatchingLanguage ( langCut ) ;
2010-08-19 11:46:55 +00:00
}
2010-08-21 13:21:09 +00:00
2010-12-01 20:00:40 +00:00
// Load messages for that language.
// Call it even if the index is -1 to unload previously loaded translations.
2010-08-19 11:46:55 +00:00
if ( langIndex ! = _currentLang ) {
loadLanguageDat ( langIndex ) ;
_currentLang = langIndex ;
}
2010-06-15 10:44:51 +00:00
}
2010-11-11 18:22:35 +00:00
const char * TranslationManager : : getTranslation ( const char * message ) const {
2010-08-23 19:45:14 +00:00
return getTranslation ( message , NULL ) ;
}
2010-11-11 18:22:35 +00:00
const char * TranslationManager : : getTranslation ( const char * message , const char * context ) const {
2010-12-01 20:00:40 +00:00
// If no language is set or message is empty, return msgid as is
2010-08-19 11:46:55 +00:00
if ( _currentTranslationMessages . empty ( ) | | * message = = ' \0 ' )
return message ;
2010-08-21 13:21:09 +00:00
2010-12-01 20:00:40 +00:00
// Binary-search for the msgid
2010-08-19 11:46:55 +00:00
int leftIndex = 0 ;
int rightIndex = _currentTranslationMessages . size ( ) - 1 ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
while ( rightIndex > = leftIndex ) {
const int midIndex = ( leftIndex + rightIndex ) / 2 ;
2010-08-23 19:45:14 +00:00
const PoMessageEntry * const m = & _currentTranslationMessages [ midIndex ] ;
int compareResult = strcmp ( message , _messageIds [ m - > msgid ] . c_str ( ) ) ;
if ( compareResult = = 0 ) {
// Get the range of messages with the same ID (but different context)
leftIndex = rightIndex = midIndex ;
while (
leftIndex > 0 & &
_currentTranslationMessages [ leftIndex - 1 ] . msgid = = m - > msgid
) {
- - leftIndex ;
}
while (
rightIndex < ( int ) _currentTranslationMessages . size ( ) - 1 & &
_currentTranslationMessages [ rightIndex + 1 ] . msgid = = m - > msgid
) {
+ + rightIndex ;
}
// Find the context we want
if ( context = = NULL | | * context = = ' \0 ' | | leftIndex = = rightIndex )
return _currentTranslationMessages [ leftIndex ] . msgstr . c_str ( ) ;
// We could use again binary search, but there should be only a small number of contexts.
while ( rightIndex > leftIndex ) {
compareResult = strcmp ( context , _currentTranslationMessages [ rightIndex ] . msgctxt . c_str ( ) ) ;
if ( compareResult = = 0 )
return _currentTranslationMessages [ rightIndex ] . msgstr . c_str ( ) ;
else if ( compareResult > 0 )
break ;
- - rightIndex ;
}
return _currentTranslationMessages [ leftIndex ] . msgstr . c_str ( ) ;
} else if ( compareResult < 0 )
2010-08-19 11:46:55 +00:00
rightIndex = midIndex - 1 ;
else
leftIndex = midIndex + 1 ;
}
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
return message ;
2010-06-15 10:44:51 +00:00
}
2010-11-11 18:22:35 +00:00
String TranslationManager : : getCurrentCharset ( ) const {
2010-08-19 11:46:55 +00:00
if ( _currentCharset . empty ( ) )
return " ASCII " ;
2010-11-11 18:22:35 +00:00
return _currentCharset ;
2010-06-28 15:17:10 +00:00
}
2010-11-11 18:22:35 +00:00
String TranslationManager : : getCurrentLanguage ( ) const {
2010-10-01 21:41:40 +00:00
if ( _currentLang = = - 1 )
return " C " ;
2010-11-11 18:22:35 +00:00
return _langs [ _currentLang ] ;
2010-10-01 21:41:40 +00:00
}
2010-11-11 18:22:35 +00:00
String TranslationManager : : getTranslation ( const String & message ) const {
2010-08-19 11:46:55 +00:00
return getTranslation ( message . c_str ( ) ) ;
2010-06-15 15:50:37 +00:00
}
2010-11-11 18:22:35 +00:00
String TranslationManager : : getTranslation ( const String & message , const String & context ) const {
2010-08-23 19:45:14 +00:00
return getTranslation ( message . c_str ( ) , context . c_str ( ) ) ;
}
2010-07-31 15:46:43 +00:00
const TLangArray TranslationManager : : getSupportedLanguageNames ( ) const {
2010-06-15 10:44:51 +00:00
TLangArray languages ;
2010-08-19 11:46:55 +00:00
for ( unsigned int i = 0 ; i < _langNames . size ( ) ; i + + ) {
TLanguage lng ( _langNames [ i ] . c_str ( ) , i + 1 ) ;
2010-06-15 10:44:51 +00:00
languages . push_back ( lng ) ;
}
2010-08-20 20:42:13 +00:00
sort ( languages . begin ( ) , languages . end ( ) ) ;
2010-06-15 10:44:51 +00:00
return languages ;
}
2010-11-11 18:22:35 +00:00
int TranslationManager : : parseLanguage ( const String & lang ) const {
2010-08-19 11:46:55 +00:00
for ( unsigned int i = 0 ; i < _langs . size ( ) ; i + + ) {
if ( lang = = _langs [ i ] )
2010-06-15 10:44:51 +00:00
return i + 1 ;
}
return kTranslationBuiltinId ;
}
2010-11-11 18:22:35 +00:00
String TranslationManager : : getLangById ( int id ) const {
2010-06-15 10:44:51 +00:00
switch ( id ) {
case kTranslationAutodetectId :
return " " ;
case kTranslationBuiltinId :
return " C " ;
default :
2010-08-19 11:46:55 +00:00
if ( id > = 0 & & id - 1 < ( int ) _langs . size ( ) )
2010-11-13 23:27:13 +00:00
return _langs [ id - 1 ] ;
2010-06-15 10:44:51 +00:00
}
2010-06-15 17:34:26 +00:00
// In case an invalid ID was specified, we will output a warning
// and return the same value as the auto detection id.
warning ( " Invalid language id %d passed to TranslationManager::getLangById " , id ) ;
return " " ;
2010-06-15 10:44:51 +00:00
}
2010-08-28 15:51:23 +00:00
bool TranslationManager : : openTranslationsFile ( File & inFile ) {
// First try to open it directly (i.e. using the SearchMan).
if ( inFile . open ( " translations.dat " ) )
return true ;
2010-10-12 02:18:11 +00:00
2010-08-28 15:51:23 +00:00
// Then look in the Themepath if we can find the file.
if ( ConfMan . hasKey ( " themepath " ) )
return openTranslationsFile ( FSNode ( ConfMan . get ( " themepath " ) ) , inFile ) ;
2010-10-12 02:18:11 +00:00
2010-08-28 15:51:23 +00:00
return false ;
}
bool TranslationManager : : openTranslationsFile ( const FSNode & node , File & inFile , int depth ) {
if ( ! node . exists ( ) | | ! node . isReadable ( ) | | ! node . isDirectory ( ) )
return false ;
2010-10-12 02:18:11 +00:00
2010-08-28 15:51:23 +00:00
// Check if we can find the file in this directory
// Since File::open(FSNode) makes all the needed tests, it is not really
// necessary to make them here. But it avoid printing warnings.
FSNode fileNode = node . getChild ( " translations.dat " ) ;
if ( fileNode . exists ( ) & & fileNode . isReadable ( ) & & ! fileNode . isDirectory ( ) ) {
if ( inFile . open ( fileNode ) )
return true ;
}
2010-10-12 02:18:11 +00:00
2010-08-28 15:51:23 +00:00
// Check if we exceeded the given recursion depth
if ( depth - 1 = = - 1 )
2010-10-12 02:18:11 +00:00
return false ;
2010-08-28 15:51:23 +00:00
// Otherwise look for it in sub-directories
FSList fileList ;
if ( ! node . getChildren ( fileList , FSNode : : kListDirectoriesOnly ) )
return false ;
2010-10-12 02:18:11 +00:00
2010-08-28 15:51:23 +00:00
for ( FSList : : iterator i = fileList . begin ( ) ; i ! = fileList . end ( ) ; + + i ) {
if ( openTranslationsFile ( * i , inFile , depth = = - 1 ? - 1 : depth - 1 ) )
return true ;
}
2010-10-12 02:18:11 +00:00
2010-08-28 15:51:23 +00:00
// Not found in this directory or its sub-directories
return false ;
}
2010-08-19 11:46:55 +00:00
void TranslationManager : : loadTranslationsInfoDat ( ) {
2010-08-21 13:21:09 +00:00
File in ;
2010-08-28 15:51:23 +00:00
if ( ! openTranslationsFile ( in ) ) {
warning ( " You are missing the 'translations.dat' file. GUI translation will not be available " ) ;
return ;
}
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
if ( ! checkHeader ( in ) )
return ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
char buf [ 256 ] ;
int len ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Get number of translations
int nbTranslations = in . readUint16BE ( ) ;
2010-10-12 02:18:11 +00:00
2010-08-19 11:46:55 +00:00
// Skip all the block sizes
2010-08-21 13:38:08 +00:00
for ( int i = 0 ; i < nbTranslations + 2 ; + + i )
2010-08-19 11:46:55 +00:00
in . readUint16BE ( ) ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Read list of languages
_langs . resize ( nbTranslations ) ;
_langNames . resize ( nbTranslations ) ;
for ( int i = 0 ; i < nbTranslations ; + + i ) {
len = in . readUint16BE ( ) ;
in . read ( buf , len ) ;
2010-12-01 20:00:40 +00:00
_langs [ i ] = String ( buf , len - 1 ) ;
2010-08-19 11:46:55 +00:00
len = in . readUint16BE ( ) ;
in . read ( buf , len ) ;
2010-12-01 20:00:40 +00:00
_langNames [ i ] = String ( buf , len - 1 ) ;
2010-08-19 11:46:55 +00:00
}
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Read messages
int numMessages = in . readUint16BE ( ) ;
_messageIds . resize ( numMessages ) ;
for ( int i = 0 ; i < numMessages ; + + i ) {
len = in . readUint16BE ( ) ;
in . read ( buf , len ) ;
2010-12-01 20:00:40 +00:00
_messageIds [ i ] = String ( buf , len - 1 ) ;
2010-08-19 11:46:55 +00:00
}
}
void TranslationManager : : loadLanguageDat ( int index ) {
_currentTranslationMessages . clear ( ) ;
_currentCharset . clear ( ) ;
// Sanity check
if ( index < 0 | | index > = ( int ) _langs . size ( ) ) {
if ( index ! = - 1 )
warning ( " Invalid language index %d passed to TranslationManager::loadLanguageDat " , index ) ;
return ;
}
2010-08-21 13:21:09 +00:00
File in ;
2010-08-28 15:51:23 +00:00
if ( ! openTranslationsFile ( in ) )
return ;
2010-08-19 11:46:55 +00:00
if ( ! checkHeader ( in ) )
return ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
char buf [ 1024 ] ;
int len ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Get number of translations
int nbTranslations = in . readUint16BE ( ) ;
if ( nbTranslations ! = ( int ) _langs . size ( ) ) {
warning ( " The 'translations.dat' file has changed since starting ScummVM. GUI translation will not be available " ) ;
return ;
}
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Get size of blocks to skip.
int skipSize = 0 ;
2010-08-21 13:38:08 +00:00
for ( int i = 0 ; i < index + 2 ; + + i )
2010-08-19 11:46:55 +00:00
skipSize + = in . readUint16BE ( ) ;
// We also need to skip the remaining block sizes
skipSize + = 2 * ( nbTranslations - index ) ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Seek to start of block we want to read
in . seek ( skipSize , SEEK_CUR ) ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Read number of translated messages
int nbMessages = in . readUint16BE ( ) ;
_currentTranslationMessages . resize ( nbMessages ) ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Read charset
len = in . readUint16BE ( ) ;
in . read ( buf , len ) ;
2010-12-01 20:00:40 +00:00
_currentCharset = String ( buf , len - 1 ) ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Read messages
for ( int i = 0 ; i < nbMessages ; + + i ) {
_currentTranslationMessages [ i ] . msgid = in . readUint16BE ( ) ;
len = in . readUint16BE ( ) ;
in . read ( buf , len ) ;
2010-12-01 20:00:40 +00:00
_currentTranslationMessages [ i ] . msgstr = String ( buf , len - 1 ) ;
2010-08-23 19:45:14 +00:00
len = in . readUint16BE ( ) ;
if ( len > 0 ) {
in . read ( buf , len ) ;
2010-12-01 20:00:40 +00:00
_currentTranslationMessages [ i ] . msgctxt = String ( buf , len - 1 ) ;
2010-08-23 19:45:14 +00:00
}
2010-08-19 11:46:55 +00:00
}
}
2010-08-21 13:38:08 +00:00
bool TranslationManager : : checkHeader ( File & in ) {
2010-08-19 11:46:55 +00:00
char buf [ 13 ] ;
int ver ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
in . read ( buf , 12 ) ;
buf [ 12 ] = ' \0 ' ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Check header
if ( strcmp ( buf , " TRANSLATIONS " ) ) {
2010-08-21 14:05:38 +00:00
warning ( " Your 'translations.dat' file is corrupt. GUI translation will not be available " ) ;
2010-08-19 11:46:55 +00:00
return false ;
}
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Check version
ver = in . readByte ( ) ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
if ( ver ! = TRANSLATIONS_DAT_VER ) {
2010-08-21 14:05:38 +00:00
warning ( " Your 'translations.dat' file has a mismatching version, expected was %d but you got %d. GUI translation will not be available " , TRANSLATIONS_DAT_VER , ver ) ;
2010-08-19 11:46:55 +00:00
return false ;
}
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
return true ;
}
2010-11-30 18:50:19 +00:00
} // End of namespace Common
2010-06-18 02:25:33 +00:00
2010-06-15 19:20:58 +00:00
# endif // USE_TRANSLATION