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 .
*
* $ URL $
* $ Id $
*/
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-06-26 22:32:51 +00:00
# include "translation.h"
2010-08-28 15:51:23 +00:00
# include "common/archive.h"
# include "common/config-manager.h"
2010-06-26 22:32:51 +00:00
DECLARE_SINGLETON ( Common : : TranslationManager )
2010-06-26 22:44:43 +00:00
# ifdef USE_DETECTLANG
# ifndef WIN32
# include <locale.h>
# endif // !WIN32
# endif
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-06-15 19:20:58 +00:00
# ifdef USE_TRANSLATION
2010-06-15 10:44:51 +00:00
// Translation enabled
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
TranslationManager : : TranslationManager ( ) : _currentLang ( - 1 ) {
loadTranslationsInfoDat ( ) ;
2010-06-15 19:20:58 +00:00
# ifdef USE_DETECTLANG
2010-06-26 22:32:51 +00:00
# ifdef WIN32
2010-06-26 23:01:12 +00:00
// We can not use "setlocale" (at least not for MSVC builds), since it
// will return locales like: "English_USA.1252", thus we need a special
// way to determine the locale string for Win32.
2010-06-26 22:32:51 +00:00
char langName [ 9 ] ;
char ctryName [ 9 ] ;
const LCID languageIdentifier = GetThreadLocale ( ) ;
// GetLocalInfo is only supported starting from Windows 2000, according to this:
// http://msdn.microsoft.com/en-us/library/dd318101%28VS.85%29.aspx
// On the other hand the locale constants used, seem to exist on Windows 98 too,
// check this for that: http://msdn.microsoft.com/en-us/library/dd464799%28v=VS.85%29.aspx
2010-07-21 18:17:51 +00:00
//
2010-06-26 22:32:51 +00:00
// I am not exactly sure what is the truth now, it might be very well that this breaks
// support for systems older than Windows 2000....
//
// TODO: Check whether this (or ScummVM at all ;-) works on a system with Windows 98 for
// example and if it does not and we still want Windows 9x support, we should definitly
// think of another solution.
2010-06-26 22:44:43 +00:00
if ( GetLocaleInfo ( languageIdentifier , LOCALE_SISO639LANGNAME , langName , sizeof ( langName ) ) ! = 0 & &
GetLocaleInfo ( languageIdentifier , LOCALE_SISO3166CTRYNAME , ctryName , sizeof ( ctryName ) ) ! = 0 ) {
2010-06-26 22:32:51 +00:00
_syslang = langName ;
_syslang + = " _ " ;
_syslang + = ctryName ;
} else {
2010-06-26 22:51:13 +00:00
_syslang = " C " ;
2010-06-26 22:32:51 +00:00
}
# else // WIN32
2010-06-15 10:44:51 +00:00
// Activating current locale settings
2010-06-15 15:39:59 +00:00
const char * locale = setlocale ( LC_ALL , " " ) ;
2010-06-15 10:44:51 +00:00
// Detect the language from the locale
2010-06-15 17:47:23 +00:00
if ( ! locale ) {
2010-06-15 17:33:45 +00:00
_syslang = " C " ;
2010-06-15 17:47:23 +00:00
} else {
int length = 0 ;
// Strip out additional information, like
// ".UTF-8" or the like. We do this, since
// our translation languages are usually
// specified without any charset information.
for ( int i = 0 ; locale [ i ] ; + + i ) {
// TODO: Check whether "@" should really be checked
// here.
if ( locale [ i ] = = ' . ' | | locale [ i ] = = ' ' | | locale [ i ] = = ' @ ' ) {
length = i ;
break ;
}
length = i ;
}
_syslang = String ( locale , length ) ;
}
2010-06-26 22:32:51 +00:00
# endif // WIN32
2010-06-15 19:20:58 +00:00
# else // USE_DETECTLANG
2010-06-15 17:33:45 +00:00
_syslang = " C " ;
2010-06-15 19:20:58 +00:00
# endif // USE_DETECTLANG
2010-06-15 10:44:51 +00:00
// Set the default language
setLanguage ( " " ) ;
}
TranslationManager : : ~ TranslationManager ( ) {
}
2010-06-15 15:39:59 +00:00
void TranslationManager : : setLanguage ( const char * lang ) {
2010-08-19 11:46:55 +00:00
// Get lang index
int langIndex = - 1 ;
String langStr ( lang ) ;
if ( langStr . empty ( ) )
langStr = _syslang ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Searching for a valid language
for ( unsigned int i = 0 ; i < _langs . size ( ) & & langIndex = = - 1 ; + + i ) {
if ( langStr = = _langs [ i ] )
langIndex = i ;
}
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Try partial match
for ( unsigned int i = 0 ; i < _langs . size ( ) & & langIndex = = - 1 ; + + i ) {
if ( strncmp ( langStr . c_str ( ) , _langs [ i ] . c_str ( ) , 2 ) = = 0 )
langIndex = i ;
}
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// Load messages for that lang
// Call it even if the index is -1 to unload previously loaded translations
if ( langIndex ! = _currentLang ) {
loadLanguageDat ( langIndex ) ;
_currentLang = langIndex ;
}
2010-06-15 10:44:51 +00:00
}
2010-06-15 15:39:59 +00:00
const char * TranslationManager : : getTranslation ( const char * message ) {
2010-08-23 19:45:14 +00:00
return getTranslation ( message , NULL ) ;
}
const char * TranslationManager : : getTranslation ( const char * message , const char * context ) {
2010-08-19 11:46:55 +00:00
// if no language is set or message is empty, return msgid as is
if ( _currentTranslationMessages . empty ( ) | | * message = = ' \0 ' )
return message ;
2010-08-21 13:21:09 +00:00
2010-08-19 11:46:55 +00:00
// binary-search for the msgid
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-06-28 15:17:10 +00:00
const char * TranslationManager : : getCurrentCharset ( ) {
2010-08-19 11:46:55 +00:00
if ( _currentCharset . empty ( ) )
return " ASCII " ;
return _currentCharset . c_str ( ) ;
2010-06-28 15:17:10 +00:00
}
2010-10-01 21:41:40 +00:00
const char * TranslationManager : : getCurrentLanguage ( ) {
if ( _currentLang = = - 1 )
return " C " ;
return _langs [ _currentLang ] . c_str ( ) ;
}
2010-06-15 15:50:37 +00:00
String TranslationManager : : getTranslation ( const String & message ) {
2010-08-19 11:46:55 +00:00
return getTranslation ( message . c_str ( ) ) ;
2010-06-15 15:50:37 +00:00
}
2010-08-23 19:45:14 +00:00
String TranslationManager : : getTranslation ( const String & message , const String & context ) {
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 ;
}
int TranslationManager : : parseLanguage ( const String lang ) {
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 ;
}
const char * TranslationManager : : getLangById ( int id ) {
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 ( ) )
return _langs [ id - 1 ] . c_str ( ) ;
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 ) ;
_langs [ i ] = String ( buf , len ) ;
len = in . readUint16BE ( ) ;
in . read ( buf , len ) ;
_langNames [ i ] = String ( buf , len ) ;
}
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 ) ;
_messageIds [ i ] = String ( buf , len ) ;
}
}
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 ) ;
_currentCharset = String ( buf , len ) ;
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 ) ;
_currentTranslationMessages [ i ] . msgstr = String ( buf , len ) ;
2010-08-23 19:45:14 +00:00
len = in . readUint16BE ( ) ;
if ( len > 0 ) {
in . read ( buf , len ) ;
_currentTranslationMessages [ i ] . msgctxt = String ( buf , len ) ;
}
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-06-15 19:20:58 +00:00
# else // USE_TRANSLATION
2010-06-15 10:44:51 +00:00
// Translation disabled
TranslationManager : : TranslationManager ( ) { }
TranslationManager : : ~ TranslationManager ( ) { }
2010-06-15 15:39:59 +00:00
void TranslationManager : : setLanguage ( const char * lang ) { }
2010-06-15 10:44:51 +00:00
2010-06-18 02:25:33 +00:00
const char * TranslationManager : : getLangById ( int id ) {
return " " ;
}
int TranslationManager : : parseLanguage ( const String lang ) {
return kTranslationBuiltinId ;
}
2010-06-15 15:39:59 +00:00
const char * TranslationManager : : getTranslation ( const char * message ) {
2010-06-15 10:44:51 +00:00
return message ;
}
2010-06-18 02:25:33 +00:00
String TranslationManager : : getTranslation ( const String & message ) {
return message ;
}
2010-08-23 19:45:14 +00:00
const char * TranslationManager : : getTranslation ( const char * message , const char * ) {
return message ;
}
String TranslationManager : : getTranslation ( const String & message , const String & ) {
return message ;
}
2010-07-31 15:46:43 +00:00
const TLangArray TranslationManager : : getSupportedLanguageNames ( ) const {
2010-06-18 02:25:33 +00:00
return TLangArray ( ) ;
}
2010-10-12 02:18:11 +00:00
2010-08-12 23:09:38 +00:00
const char * TranslationManager : : getCurrentCharset ( ) {
return " ASCII " ;
}
2010-10-12 02:18:11 +00:00
2010-10-01 21:41:40 +00:00
const char * TranslationManager : : getCurrentLanguage ( ) {
return " C " ;
}
2010-06-18 02:25:33 +00:00
2010-06-15 19:20:58 +00:00
# endif // USE_TRANSLATION
2010-06-15 10:44:51 +00:00
2010-08-21 13:21:09 +00:00
} // End of namespace Common