2012-05-29 13:31:11 +00:00
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
# include "stdpch.h"
# include "string_manager.h"
# include "input_output_service.h"
# include "nel/misc/i18n.h"
# include "nel/misc/path.h"
# include "nel/misc/file.h"
# include "nel/net/unified_network.h"
# include "nel/net/service.h"
# include "nel/misc/bit_mem_stream.h"
# include "nel/misc/diff_tool.h"
# include "game_share/ryzom_mirror_properties.h"
# include "game_share/backup_service_interface.h"
# include "nel/georges/u_form_elm.h"
# include "nel/georges/load_form.h"
# include "server_share/r2_variables.h"
# include <time.h>
//---------------------------------------------------------------------------------------
// Stuff used for management of log messages
//static bool VerboseLog=false;
//bool DebugReplacementParameter = false;
//NLMISC_VARIABLE(bool, DebugReplacementParameter, "Debug missing replacement parameter.");
NLMISC : : CVariable < bool > DebugReplacementParameter ( " ios " , " DebugReplacementParameter " , " Insert error debugging information in generated text " , false , 0 , true ) ;
NLMISC : : CVariable < bool > VerboseStringManager ( " ios " , " VerboseStringManager " , " Turn on or off or check the state of verbose string manager logging " , false , 0 , true ) ;
NLMISC : : CVariable < bool > VerboseStringManagerParser ( " ios " , " VerboseStringManagerParser " , " Turn on or off or check the state of verbose string manager logging when parsing files " , false , 0 , true ) ;
NLMISC : : CVariable < std : : string > StringManagerCacheDirectory ( " ios " , " StringManagerCacheDirectory " , " Directory to read/write string cache file (default (empty) is service SaveFilesDirectory) " , " " , 0 , true ) ;
# define LOG if (!VerboseStringManager) {} else nlinfo
# define LOGPARSE if (!VerboseStringManagerParser) {} else nlinfo
// Instantiate the string manager.
CStringManager Instance ;
CStringManager * SM = & Instance ;
CIosLocalSender IosLocalSender ;
using namespace STRING_MANAGER ;
using namespace NLMISC ;
using namespace NLNET ;
using namespace std ;
const ucstring nl ( " \r \n " ) ;
std : : string CStringManager : : _LanguageCode [ NB_LANGUAGES ] =
{
" wk " , // Work
" en " , // english
" de " ,
" fr " ,
" ru " ,
" es " ,
/* ace: currently, we only want english, i remove other language to remove warning during IOS launch
" fr " , // french
" zh " , // traditionnal chinese
" zh-CN " // simplified chinese
*/
} ;
/*
ucstring CStringManager : : getEntityDisplayName ( const NLMISC : : CEntityId & eid )
{
ucstring ret ;
NLMISC : : CSheetId sid = SM - > getSheetId ( eid ) ;
if ( sid ! = NLMISC : : CSheetId : : Unknown )
{
// TSheetInfoContainer::iterator it(_SheetInfo.find(sid));
if ( it ! = _SheetInfo . end ( ) )
ret = it - > second . DisplayName ;
}
// not found.
if ( ret . empty ( ) )
ret = ucstring ( eid . toString ( ) ) ;
return ret ;
}
*/
uint32 CStringManager : : CEntityWords : : getStringId ( const std : : string & rowName , const std : : string columnName ) const
{
std : : map < std : : string , uint32 > : : const_iterator colIt ( _ColumnInfo . find ( columnName ) ) ;
if ( colIt ! = _ColumnInfo . end ( ) )
{
std : : map < std : : string , uint32 > : : const_iterator rowIt ( _RowInfo . find ( rowName ) ) ;
if ( rowIt ! = _RowInfo . end ( ) )
{
return _Data [ rowIt - > second * _NbColums + colIt - > second ] ;
}
}
// not found, return rowName.columnName.
if ( DebugReplacementParameter )
return SM - > storeString ( ucstring ( std : : string ( " < " ) + rowName + " . " + columnName + " > " ) ) ;
else
{
ucstring s ;
s + = ucchar ( 8 ) ;
return SM - > storeString ( s ) ;
}
}
CStringManager : : CStringManager ( )
{
_TestOnly = false ;
_CacheLoaded = false ;
_Mapper = NLMISC : : CStringMapper : : createLocalMapper ( ) ;
_DefaultSetPhraseLanguage = NB_LANGUAGES ;
// init the game share string manager pointer.
// GameShareSM = this;
}
void CStringManager : : loadCache ( )
{
if ( _CacheLoaded )
{
return ;
}
nlassert ( _StringIdx . empty ( ) ) ;
nlassert ( _StringBase . empty ( ) ) ;
// std::string filename("data_shard/ios.string_cache");
// _CacheFilename = NLMISC::CPath::lookup(filename, false, false);
// if (!_CacheFilename.empty())
std : : string cacheDirectory = ( ( StringManagerCacheDirectory . get ( ) = = " " ) ? Bsi . getLocalPath ( ) : StringManagerCacheDirectory . get ( ) ) ;
_CacheFilename = CPath : : standardizePath ( cacheDirectory ) + " ios.string_cache " ;
if ( CFile : : fileExists ( _CacheFilename ) )
{
nlinfo ( " Reading string cache... " ) ;
// ok, load the cache data
NLMISC : : CIFile file ( _CacheFilename ) ;
file . serial ( _CacheTimestamp ) ;
uint32 start = CTime : : getSecondsSince1970 ( ) ;
uint32 t1 = start ;
while ( ! file . eof ( ) )
{
uint32 id ;
ucstring str ;
file . serial ( id ) ;
file . serial ( str ) ;
// nldebug("Loaded from cache [%u][%s]", id, str.toString().c_str());
// create a new entry
std : : pair < TMappedUStringContainer : : iterator , bool > ret ;
ret = _StringIdx . insert ( std : : make_pair ( str , id ) ) ;
// nlassert(ret.second);
if ( ! ret . second )
{
TMappedUStringContainer : : iterator it = _StringIdx . find ( str ) ;
nlassert ( it ! = _StringIdx . end ( ) ) ;
nlwarning ( " String cache : string [%s][%u] already in the string map with id [%u] ! " , str . c_str ( ) , id , it - > second ) ;
if ( id ! = it - > second )
{
nlwarning ( " !!! ID diff : string cache invalide. " ) ;
}
}
// if (_StringBase.size() <= id)
// {
// _StringBase.reserve(_StringBase.size()*2);
// }
// _StringBase.resize(id+1);
// _StringBase[id] = str;
while ( _StringBase . size ( ) < = id )
_StringBase . push_back ( string ( ) ) ;
_StringBase [ id ] = str ;
// some logging
uint32 now = CTime : : getSecondsSince1970 ( ) ;
if ( ( now - t1 ) > 10 )
{
// more than 10 s... log where we are
double progress = 100 * ( file . getPos ( ) / double ( file . getFileSize ( ) ) ) ;
nlinfo ( " %6.2f%% read " , progress ) ;
t1 = now ;
}
}
uint32 now = CTime : : getSecondsSince1970 ( ) ;
nlinfo ( " Done, %u strings read in %u seconds. " , _StringBase . size ( ) , now - start ) ;
}
else
{
// create a new cache
if ( ! CFile : : isExists ( cacheDirectory ) )
CFile : : createDirectoryTree ( cacheDirectory ) ;
_CacheTimestamp = CTime : : getSecondsSince1970 ( ) ;
NLMISC : : COFile file ( _CacheFilename ) ;
file . serial ( _CacheTimestamp ) ;
}
_CacheLoaded = true ;
}
void CStringManager : : clearCache ( NLMISC : : CLog * log )
{
if ( _CacheFilename . empty ( ) )
{
log - > displayNL ( " Clear cache requested but no cache file name available! " ) ;
return ;
}
log - > displayNL ( " Clearing cache file and reloading string files... " ) ;
CFile : : deleteFile ( _CacheFilename ) ;
_StringIdx . clear ( ) ;
_StringBase . clear ( ) ;
_CacheLoaded = false ;
// this will clear all loaded string, then reinit the string manager
reload ( log ) ;
// warn all the client that they must dropout there cache file
}
const CStringManager : : CEntityWords & CStringManager : : getEntityWords ( TLanguages lang , STRING_MANAGER : : TParamType type ) const
{
nlassert ( lang < NB_LANGUAGES ) ;
nlassert ( type < STRING_MANAGER : : NB_PARAM_TYPES | | type = = STRING_MANAGER : : self ) ;
return _AllEntityWords [ lang ] [ type ] ;
}
bool CStringManager : : CClause : : eval ( CStringManager : : TLanguages lang , const CCharacterInfos * charInfo , const CPhrase * phrase )
{
bool ret = false ;
// if no condition, it's true !
if ( Conditions . empty ( ) )
return true ;
for ( uint i = 0 ; ! ret & & i < Conditions . size ( ) ; + + i )
{
std : : vector < TCondition > & andedCond = Conditions [ i ] ;
bool temp = true ;
for ( uint j = 0 ; temp & & j < andedCond . size ( ) ; + + j )
{
const CParameterTraits * param = phrase - > Params [ andedCond [ j ] . ParamIndex ] ;
temp & = param - > eval ( lang , charInfo , andedCond [ j ] ) ;
}
ret | = temp ;
}
return ret ;
}
NLMISC : : CSheetId CStringManager : : getSheetId ( const NLMISC : : CEntityId & entityId )
{
TDataSetRow entityIndex = TheDataset . getDataSetRow ( entityId ) ;
if ( ! entityIndex . isValid ( ) )
return NLMISC : : CSheetId : : Unknown ;
CMirrorPropValueBase < TYPE_SHEET > sheetId ( TheDataset , entityIndex , DSPropertySHEET ) ;
return NLMISC : : CSheetId ( sheetId ) ;
}
NLMISC : : CSheetId CStringManager : : getSheetServerId ( const NLMISC : : CEntityId & entityId )
{
TDataSetRow entityIndex = TheDataset . getDataSetRow ( entityId ) ;
if ( ! entityIndex . isValid ( ) )
return NLMISC : : CSheetId : : Unknown ;
CMirrorPropValueBase < uint32 > sheetServerId ( TheDataset , entityIndex , DSPropertySHEET_SERVER ) ;
return NLMISC : : CSheetId ( sheetServerId ) ;
}
const CStringManager : : TSheetInfo & CStringManager : : getSheetInfo ( const NLMISC : : CSheetId & sheetId )
{
TSheetInfoContainer : : iterator it ( _SheetInfo . find ( sheetId ) ) ;
if ( it ! = _SheetInfo . end ( ) )
return it - > second ;
static TSheetInfo unknown ;
// nlwarning("Unknown sheetId : %s", sheetId.toString().c_str());
return unknown ;
}
void CStringManager : : buildMissingPhraseStream ( CCharacterInfos * charInfo , uint32 seqNum , NLMISC : : CBitMemStream & bmsOut , const std : : string & phraseName )
{
// store a string for this error message.
uint32 id = storeString ( ucstring ( " <missing: " ) + phraseName + " > " ) ;
// now, build the message for the client.
GenericXmlMsgHeaderMngr . pushNameToStream ( " STRING_MANAGER:PHRASE_SEND " , bmsOut ) ;
bmsOut . serial ( seqNum ) ;
bmsOut . serial ( id ) ;
LOG ( " Sending phrase [%s] content " , phraseName . c_str ( ) ) ;
}
bool CStringManager : : buildPhraseStream ( CCharacterInfos * charInfo , uint32 seqNum , NLNET : : CMessage & message , bool debug , CStringManager : : TLanguages lang , NLMISC : : CBitMemStream & bmsOut )
{
// uint32 seqNum;
std : : string phraseName ;
message . serial ( phraseName ) ;
LOG ( " Receiving phrase %u as '%s' " , seqNum , phraseName . c_str ( ) ) ;
if ( phraseName . empty ( ) )
{
nlwarning ( " buildPhraseStream: phrase name is empty ! " ) ;
return false ;
}
// ok, try to find this phrase
bool found = false ;
TPhrasesContainer : : iterator it ( _AllPhrases [ lang ] . find ( phraseName ) ) ;
if ( it ! = _AllPhrases [ lang ] . end ( ) )
found = true ;
// if not found try to get the working text
if ( ! found & & lang ! = english )
{
it = _AllPhrases [ english ] . find ( phraseName ) ;
if ( it ! = _AllPhrases [ english ] . end ( ) )
found = true ;
}
// if still not found try to get the work text
if ( ! found & & lang ! = work )
{
it = _AllPhrases [ work ] . find ( phraseName ) ;
if ( it ! = _AllPhrases [ work ] . end ( ) )
{
found = true ;
// every phrases should be at least translated in english
nlwarning ( " CStringManager::buildPhraseStream the phrase [%s] has not been translated in english! " , phraseName . c_str ( ) ) ;
}
}
if ( ! found )
{
nlwarning ( " CStringManager::buildPhraseStream the phrase [%s] is unknown in %s, in english and in work " , phraseName . c_str ( ) , _LanguageCode [ lang ] . c_str ( ) ) ;
buildMissingPhraseStream ( charInfo , seqNum , bmsOut , phraseName ) ;
return true ;
}
// ok, we have the phrase, we can parse the parameter from the message
CPhrase & phrase = it - > second ;
// std::vector<TStringParam> params;
// params.resize(phrase.Params.size());
uint i ;
try
{
bool result = true ;
for ( i = 1 ; i < phrase . Params . size ( ) ; + + i )
{
result & = phrase . Params [ i ] - > extractFromMessage ( message , debug ) ;
}
if ( ! result )
nlwarning ( " Format error extracting parameters in phrase %s, result string could be erroneous ! " , phrase . Name . c_str ( ) ) ;
}
catch ( . . . )
{
nlwarning ( " Exception while extracting parameters in phrase %s, result string could be erroneous ! " , phrase . Name . c_str ( ) ) ;
// init the rest with default values
for ( ; i < phrase . Params . size ( ) ; + + i )
{
phrase . Params [ i ] - > setDefaultValue ( ) ;
}
// return;
}
// update the self parameter with dest eid.
if ( charInfo )
phrase . Params [ 0 ] - > EId = charInfo - > EntityId ;
else
phrase . Params [ 0 ] - > EId = CEntityId : : Unknown ;
// ok, we have the phrase and a list of typed param, we can search for the good clause.
found = false ;
for ( i = 0 ; i < phrase . Clauses . size ( ) ; + + i )
{
// if the first clause as no condition, consider it as a fallback clause.
if ( i = = 0 & & phrase . Clauses [ i ] . Conditions . empty ( ) )
{
// skip it, it will be used only for fallback when no clause match
continue ;
}
if ( phrase . Clauses [ i ] . eval ( lang , charInfo , & phrase ) )
{
found = true ;
break ;
}
}
if ( ! found )
{
// force the use of the first clause.
// NB : this is a 'best effort' fallback. Either the first clause has
// no condition, so it is designed to be the fallback one, or
// the first clause has some condition not valid, but we use it.
i = 0 ;
}
CClause & clause = phrase . Clauses [ i ] ;
// now, build the message for the client.
GenericXmlMsgHeaderMngr . pushNameToStream ( " STRING_MANAGER:PHRASE_SEND " , bmsOut ) ;
bmsOut . serial ( seqNum ) ;
bmsOut . serial ( clause . ClientStringId ) ;
// for each replacement parameter in order...
for ( i = 0 ; i < clause . Replacements . size ( ) ; + + i )
{
TReplacement & rep = clause . Replacements [ i ] ;
CParameterTraits * param = phrase . Params [ clause . Replacements [ i ] . ParamIndex ] ;
param - > fillBitMemStream ( charInfo , lang , rep , bmsOut ) ;
}
LOG ( " Sending phrase [%s] content " , phraseName . c_str ( ) ) ;
return true ;
}
void CStringManager : : receiveUserPhrase ( NLNET : : CMessage & message , bool debug )
{
// extract the parameters
uint32 userId ;
uint32 seqNum ;
message . serial ( userId ) ;
message . serial ( seqNum ) ;
LOG ( " Receiving a phrase for user id %d " , userId ) ;
// get the user language
TUserLanguagesContainer : : const_iterator itUser = _UsersLanguages . find ( userId ) ;
if ( itUser = = _UsersLanguages . end ( ) )
{
// just extract the phrase name for more info in the warning
std : : string phraseName ;
message . serial ( phraseName ) ;
nlwarning ( " CStringManager::receiveUserPhrase '%s', unknown user %u " , phraseName . c_str ( ) , userId ) ;
return ;
}
// built the stream to be sent to the client
NLMISC : : CBitMemStream bmsOut ;
if ( buildPhraseStream ( NULL , seqNum , message , debug , ( * itUser ) . second . Language , bmsOut ) )
{
CMessage msgout ( " IMPULSION_UID " ) ;
msgout . serial ( userId ) ;
msgout . serialBufferWithSize ( ( uint8 * ) bmsOut . buffer ( ) , bmsOut . length ( ) ) ;
NLNET : : CUnifiedNetwork : : getInstance ( ) - > send ( ( * itUser ) . second . FrontEndId , msgout ) ;
nldebug ( " IOSSM: Sent IMPULSION_UID to %hu (PHRASE_SEND) " , ( * itUser ) . second . FrontEndId . get ( ) ) ;
}
}
void CStringManager : : receivePhrase ( NLNET : : CMessage & message , bool debug , const std : : string & serviceName )
{
// extract the parameters
// NLMISC::CEntityId dest;
TDataSetRow dest ;
uint32 seqNum ;
message . serial ( dest ) ;
LOG ( " Receiving a phrase for char %s:%x " ,
TheDataset . getEntityId ( dest ) . toString ( ) . c_str ( ) ,
dest . getIndex ( ) ) ;
message . serial ( seqNum ) ;
CEntityId destId = TheDataset . getEntityId ( dest ) ;
// retrieve the dest player infos.
CCharacterInfos * charInfo = IOS - > getCharInfos ( destId ) ;
if ( charInfo = = 0 )
{
nlwarning ( " CStringManager::receivePhrase unknown eid %s:%x " ,
TheDataset . getEntityId ( dest ) . toString ( ) . c_str ( ) ,
dest . getIndex ( ) ) ;
return ;
}
if ( destId . getType ( ) = = RYZOMID : : npc | | destId . getType ( ) = = RYZOMID : : creature )
{
// read the phrase name
std : : string phraseName ;
message . serial ( phraseName ) ;
nlwarning ( " Service '%s' is trying to send phrase '%s' to a AIS entity ! " ,
serviceName . c_str ( ) ,
phraseName . c_str ( ) ) ;
return ;
}
// built the stream to be sent to the client
NLMISC : : CBitMemStream bmsOut ;
if ( buildPhraseStream ( charInfo , seqNum , message , debug , charInfo - > Language , bmsOut ) )
{
NLNET : : CMessage msgout ( " IMPULS_CH_ID " ) ;
NLMISC : : CEntityId destId = charInfo - > EntityId ;
uint8 channel = 1 ;
msgout . serial ( destId ) ;
msgout . serial ( channel ) ;
msgout . serialBufferWithSize ( ( uint8 * ) bmsOut . buffer ( ) , bmsOut . length ( ) ) ;
NLNET : : CUnifiedNetwork : : getInstance ( ) - > send ( TServiceId ( charInfo - > EntityId . getDynamicId ( ) ) , msgout ) ;
}
}
void CStringManager : : broadcastSystemMessage ( NLNET : : CMessage & message , bool debug )
{
TDataSetRow client ;
vector < CEntityId > temp ;
set < CEntityId > excluded ;
CChatGroup : : TGroupType audience = CChatGroup : : nbChatMode ;
uint32 seqNum ;
message . serial ( client ) ;
message . serialCont ( temp ) ;
excluded . insert ( temp . begin ( ) , temp . end ( ) ) ;
message . serialEnum ( audience ) ;
message . serial ( seqNum ) ;
if ( ! IOS - > getChatManager ( ) . checkClient ( client ) )
{
nlwarning ( " broadcastSystemMessage : can't find client %s:%x in chat client " ,
TheDataset . getEntityId ( client ) . toString ( ) . c_str ( ) ,
client . getIndex ( ) ) ;
return ;
}
try
{
// can thow CChatManager::EChatClient exception
CChatClient & chatClient = IOS - > getChatManager ( ) . getClient ( client ) ;
TGroupId groupId ;
switch ( audience )
{
case CChatGroup : : say :
// force an update of the say audience
chatClient . getSayAudience ( true ) ;
groupId = chatClient . getSayAudienceId ( ) ;
break ;
case CChatGroup : : team :
groupId = chatClient . getTeamChatGroup ( ) ;
break ;
case CChatGroup : : guild :
groupId = chatClient . getGuildChatGroup ( ) ;
break ;
case CChatGroup : : shout :
// force an update of the shout audience
chatClient . getShoutAudience ( true ) ;
groupId = chatClient . getShoutAudienceId ( ) ;
break ;
default :
nlwarning ( " broadcastSystemMessage : unsupported chat mode '%s' for broadcast " , CChatGroup : : groupTypeToString ( audience ) . c_str ( ) ) ;
return ;
}
// can throw EChatGroup
CChatGroup & chatGroup = IOS - > getChatManager ( ) . getGroup ( groupId ) ;
// generate a unique string seq number
seqNum = STRING_MANAGER : : pickStringSerialNumber ( ) ;
// and send to all the client in audience.
CChatGroup : : TMemberCont : : iterator first ( chatGroup . Members . begin ( ) ) , last ( chatGroup . Members . end ( ) ) ;
for ( ; first ! = last ; + + first )
{
TDataSetRow dsr = * first ;
// send to all client except the sender
if ( dsr ! = client )
{
CEntityId eid = TheDataset . getEntityId ( dsr ) ;
if ( excluded . find ( eid ) ! = excluded . end ( ) )
continue ;
CCharacterInfos * charInfo = IOS - > getCharInfos ( eid ) ;
if ( charInfo = = NULL )
continue ;
// copy the message
CMessage msg ;
msg . assignFromSubMessage ( message ) ;
// built the stream to be sent to the client
NLMISC : : CBitMemStream bmsOut ;
if ( buildPhraseStream ( charInfo , seqNum , msg , debug , charInfo - > Language , bmsOut ) )
{
NLNET : : CMessage msgout ( " IMPULS_CH_ID " ) ;
NLMISC : : CEntityId destId = charInfo - > EntityId ;
uint8 channel = 1 ;
msgout . serial ( destId ) ;
msgout . serial ( channel ) ;
msgout . serialBufferWithSize ( ( uint8 * ) bmsOut . buffer ( ) , bmsOut . length ( ) ) ;
NLNET : : CUnifiedNetwork : : getInstance ( ) - > send ( TServiceId ( charInfo - > EntityId . getDynamicId ( ) ) , msgout ) ;
// inform the client to display the dyn string in system info
{
CMessage msgout ( " IMPULSION_ID " ) ;
msgout . serial ( const_cast < CEntityId & > ( eid ) ) ;
CBitMemStream bms ;
if ( ! GenericXmlMsgHeaderMngr . pushNameToStream ( " STRING:DYN_STRING " , bms ) )
{
nlwarning ( " <sendDynamicSystemMessage> Msg name CHAT:DYN_STRING not found " ) ;
}
else
{
bms . serial ( seqNum ) ;
msgout . serialBufferWithSize ( ( uint8 * ) bms . buffer ( ) , bms . length ( ) ) ;
CUnifiedNetwork : : getInstance ( ) - > send ( TServiceId ( eid . getDynamicId ( ) ) , msgout ) ;
}
}
}
}
}
}
catch ( const CChatManager : : EChatClient & e )
{
nlwarning ( " %s " , e . what ( ) ) ;
}
catch ( . . . )
{
}
}
//void CStringManager::requestString(const NLMISC::CEntityId &client, uint32 stringId)
//{
// ucstring str = getString(stringId);
//
// CCharacterInfos *charInfo = IOS->getCharInfos(client);
// if (charInfo == 0)
// {
// if (IsRingShard.get())
// {
// // In ring shard, it is possible that the client autologin and
// // autochoose the character rapidly, and if a string need to be resolved
2013-02-12 21:48:37 +00:00
// // to display the char summary, it is possible client receive the
2012-05-29 13:31:11 +00:00
// // dynamic string from IOS after the EGS has passed the frontend in
// // entityId mode.
// // So, the IOS receive a stringId request with a Eid not registered yet.
// // Look in the user table, if we have the user, use the UID version
// uint32 userId = client.getShortId()>>4;
// TUserLanguagesContainer::const_iterator itUser = _UsersLanguages.find( userId );
// if ( itUser != _UsersLanguages.end() )
// {
// requestString(userId, stringId);
// return;
// }
// }
// nlwarning("requestString : Client with eid %s is unknown (requesting string %u as [%s])",
// client.toString().c_str(),
// stringId,
// str.toString().c_str());
// return;
// }
//
//
// LOG("Sending string %u as [%s] to client %s", stringId, str.toString().c_str(), client.toString().c_str());
//
// // build the response message
// NLMISC::CBitMemStream bmsOut;
// GenericXmlMsgHeaderMngr.pushNameToStream( "STRING_MANAGER:STRING_RESP", bmsOut);
// bmsOut.serial(stringId);
// // Send in utf8 format to save bandwith
// string strUtf8= str.toUtf8();
// bmsOut.serial(strUtf8);
//
// // send the message to Front End
// NLNET::CMessage msgout( "IMPULS_CH_ID" );
// NLMISC::CEntityId destId = client;
// uint8 channel = 1;
// msgout.serial( destId );
// msgout.serial( channel );
//
// msgout.serialBufferWithSize((uint8*)bmsOut.buffer(), bmsOut.length());
// NLNET::CUnifiedNetwork::getInstance()->send(TServiceId(charInfo->EntityId.getDynamicId()), msgout);
//}
void CStringManager : : requestString ( uint32 userId , uint32 stringId )
{
TServiceId frontendId ;
TUserLanguagesContainer : : const_iterator itUser = _UsersLanguages . find ( userId ) ;
if ( itUser = = _UsersLanguages . end ( ) )
{
// no user language, try to find a character
breakable
{
const CInputOutputService : : TIdToInfos & chars = IOS - > getCharInfosCont ( ) ;
CInputOutputService : : TIdToInfos : : const_iterator it ( chars . lower_bound ( CEntityId ( RYZOMID : : player , userId < < 4 ) ) ) ;
if ( it ! = chars . end ( ) )
{
CEntityId eid = it - > first ;
if ( eid . getShortId ( ) > > 4 = = userId )
{
// we found a character for this user, use it
frontendId = TServiceId ( eid . getDynamicId ( ) ) ;
break ;
}
}
// if we are here, we did not found a character for this user
nlwarning ( " <CStringManager requestString> Invalid user id %u " , userId ) ;
return ;
}
}
else
{
// we know the user
frontendId = itUser - > second . FrontEndId ;
}
ucstring str = getString ( stringId ) ;
LOG ( " Sending string %u as [%s] to user %u " , stringId , str . toString ( ) . c_str ( ) , userId ) ;
// build the response message
NLMISC : : CBitMemStream bmsOut ;
GenericXmlMsgHeaderMngr . pushNameToStream ( " STRING_MANAGER:STRING_RESP " , bmsOut ) ;
bmsOut . serial ( stringId ) ;
// Send in utf8 format to save bandwidth
string strUtf8 = str . toUtf8 ( ) ;
bmsOut . serial ( strUtf8 ) ;
// send the message to Front End
CMessage msgout ( " IMPULSION_UID " ) ;
msgout . serial ( userId ) ;
msgout . serialBufferWithSize ( ( uint8 * ) bmsOut . buffer ( ) , bmsOut . length ( ) ) ;
NLNET : : CUnifiedNetwork : : getInstance ( ) - > send ( frontendId , msgout ) ;
nldebug ( " IOSSM: Sent IMPULSION_UID to %hu (STRING_RESP) " , frontendId . get ( ) ) ;
}
void CStringManager : : reload ( NLMISC : : CLog * log )
{
uint i ;
for ( i = 0 ; i < NB_LANGUAGES ; + + i )
{
uint j ;
// need to manualy delete param object.
while ( ! _AllPhrases [ i ] . empty ( ) )
{
CPhrase & phrase = _AllPhrases [ i ] . begin ( ) - > second ;
while ( ! phrase . Params . empty ( ) )
{
delete phrase . Params . back ( ) ;
phrase . Params . pop_back ( ) ;
}
_AllPhrases [ i ] . erase ( _AllPhrases [ i ] . begin ( ) ) ;
}
_AllPhrases [ i ] . clear ( ) ;
for ( j = 0 ; j < STRING_MANAGER : : NB_PARAM_TYPES ; + + j )
{
CEntityWords & ew = _AllEntityWords [ i ] [ j ] ;
ew . _ColumnInfo . clear ( ) ;
ew . _NbColums = 0 ;
ew . _RowInfo . clear ( ) ;
delete [ ] ew . _Data ;
}
}
init ( log ) ;
}
CStringManager : : TLanguages CStringManager : : checkLanguageCode ( const std : : string & languageCode )
{
for ( uint i = 0 ; i < NB_LANGUAGES ; + + i )
{
if ( _LanguageCode [ i ] = = languageCode )
return TLanguages ( i ) ;
}
nlwarning ( " Unrecognized language code %s, default to english " , languageCode . c_str ( ) ) ;
// default to english.
return english ;
}
const std : : string & CStringManager : : getLanguageCodeString ( TLanguages language )
{
if ( language < NB_LANGUAGES )
return _LanguageCode [ language ] ;
nlwarning ( " Language number %u is out of range, returning english " , language ) ;
nlassert ( english < NB_LANGUAGES ) ; // just to avoid oopsie
return _LanguageCode [ english ] ;
}
uint32 CStringManager : : storeString ( const ucstring & str )
{
// TMappedUStringContainer _StringIdx;
// TUStringContainer _StringBase;
TMappedUStringContainer : : iterator it ( _StringIdx . find ( str ) ) ;
if ( it ! = _StringIdx . end ( ) )
{
// the string already in base, just return the index.
return it - > second ;
}
else
{
// create a new entry
std : : pair < TMappedUStringContainer : : iterator , bool > ret ;
ret = _StringIdx . insert ( std : : make_pair ( str , ( uint32 ) _StringBase . size ( ) ) ) ;
nlassert ( ret . second ) ;
_StringBase . push_back ( str ) ;
if ( ! _TestOnly )
{
// add the string in the cache file
NLMISC : : COFile file ( _CacheFilename , true ) ;
LOGPARSE ( " Writing to cache [%u][%s] " , ret . first - > second , ret . first - > first . toString ( ) . c_str ( ) ) ;
file . serial ( ret . first - > second ) ;
ucstring temp = ret . first - > first ;
file . serial ( temp ) ;
}
return ret . first - > second ;
}
}
const ucstring & CStringManager : : getString ( uint32 stringId )
{
if ( stringId < _StringBase . size ( ) )
return _StringBase [ stringId ] ;
else
return _StringBase . front ( ) ;
}
uint32 CStringManager : : translateShortName ( uint32 shortNameIndex )
{
// No bot name translation on ring shards
if ( IsRingShard )
return shortNameIndex ;
std : : map < uint32 , uint32 > : : iterator it ( _BotNameTranslation . find ( shortNameIndex ) ) ;
if ( it ! = _BotNameTranslation . end ( ) )
{
if ( it - > second ! = 0 )
// yeaa, we found a translation with a non empty short name
return it - > second ;
else
// the translated name is empty, ignore the translated name
return shortNameIndex ;
}
else
// no translation, return the same index.
return shortNameIndex ;
return 0 ;
}
uint32 CStringManager : : translateShortName ( const ucstring & shortName )
{
//
return translateShortName ( storeString ( shortName ) ) ;
}
uint32 CStringManager : : translateTitle ( const std : : string & title , TLanguages language )
{
const std : : string colName ( " name " ) ;
const CStringManager : : CEntityWords & ew = getEntityWords ( language , STRING_MANAGER : : title ) ;
2013-11-13 20:33:29 +00:00
std : : string rowName = NLMISC : : toLower ( title ) ;
2012-05-29 13:31:11 +00:00
uint32 stringId ;
stringId = ew . getStringId ( rowName , colName ) ;
return stringId ;
}
uint32 CStringManager : : translateEventFaction ( uint32 eventFactionId )
{
if ( VerboseStringManager )
nlinfo ( " Event faction translation asked for : '%s' (%u) " , getString ( eventFactionId ) . toString ( ) . c_str ( ) , eventFactionId ) ;
if ( eventFactionId = = 0 )
return 0 ;
std : : map < uint32 , uint32 > : : iterator it = _EventFactionTranslation . find ( eventFactionId ) ;
if ( it ! = _EventFactionTranslation . end ( ) )
{
if ( VerboseStringManager )
nlinfo ( " Found event faction translation : '%s' (%u) " , getString ( it - > second ) . toString ( ) . c_str ( ) , it - > second ) ;
return it - > second ;
}
return 0 ;
}
uint32 CStringManager : : translateEventFaction ( const ucstring & eventFaction )
{
if ( eventFaction . empty ( ) )
return 0 ;
return translateEventFaction ( storeString ( eventFaction ) ) ;
}
/*
* Send the requested string
*/
void CStringManager : : sendString ( uint32 nameIndex , TServiceId serviceId )
{
CMessage msgout ( " RECV_STRING " ) ;
msgout . serial ( nameIndex ) ;
const ucstring & ucs = getString ( nameIndex ) ;
msgout . serial ( const_cast < ucstring & > ( ucs ) ) ;
CUnifiedNetwork : : getInstance ( ) - > send ( serviceId , msgout ) ; // reply => not via mirror
}
/*
* Send the names of all online entities
*/
void CStringManager : : retrieveEntityNames ( TServiceId serviceId )
{
vector < pair < TDataSetRow , string > > names ;
TEntityIdToEntityIndexMap : : const_iterator itEntityIndex ;
for ( itEntityIndex = TheDataset . entityBegin ( ) ; itEntityIndex ! = TheDataset . entityEnd ( ) ; + + itEntityIndex )
{
TDataSetRow entityIndex = TheDataset . getCurrentDataSetRow ( GET_ENTITY_INDEX ( itEntityIndex ) ) ;
if ( entityIndex . isValid ( ) )
{
CMirrorPropValueRO < TYPE_NAME_STRING_ID > nameIndex ( TheDataset , entityIndex , DSPropertyNAME_STRING_ID ) ;
if ( nameIndex ( ) ! = 0 )
{
names . push_back ( make_pair ( entityIndex , getString ( nameIndex ) . toString ( ) ) ) ;
}
}
}
NLNET : : CMessage msgout ( " ENTITY_NAMES " ) ;
uint32 len = ( uint32 ) names . size ( ) ;
msgout . serial ( len ) ;
vector < pair < TDataSetRow , string > > : : const_iterator itn ;
for ( itn = names . begin ( ) ; itn ! = names . end ( ) ; + + itn )
{
msgout . serial ( const_cast < TDataSetRow & > ( ( * itn ) . first ) ) ;
msgout . serial ( const_cast < string & > ( ( * itn ) . second ) ) ;
}
NLNET : : CUnifiedNetwork : : getInstance ( ) - > send ( serviceId , msgout ) ;
nldebug ( " IOSSM: Sent %u names to service %hu " , names . size ( ) , serviceId . get ( ) ) ;
}
void CStringManager : : updateUserLanguage ( uint32 userId , TServiceId frontEndId , const std : : string & lang )
{
CStringManager : : TLanguages language = checkLanguageCode ( lang ) ;
SUserLanguageEntry entry ( frontEndId , language ) ;
TUserLanguagesContainer : : iterator it = _UsersLanguages . find ( userId ) ;
if ( it = = _UsersLanguages . end ( ) )
{
_UsersLanguages . insert ( make_pair ( userId , entry ) ) ;
}
else
{
it - > second . FrontEndId = frontEndId ;
it - > second . Language = language ;
}
// TODO : send cache time stamp to client.
nldebug ( " IOSSM: updateUserLanguage : set userId %u to front end %u using language code '%s' " ,
userId ,
frontEndId . get ( ) ,
SM - > getLanguageCodeString ( language ) . c_str ( ) ) ;
// send back the cache time stamp info
uint32 timestamp = SM - > getCacheTimestamp ( ) ;
// now, build the message for the client.
NLMISC : : CBitMemStream bmsOut ;
GenericXmlMsgHeaderMngr . pushNameToStream ( " STRING_MANAGER:RELOAD_CACHE " , bmsOut ) ;
bmsOut . serial ( timestamp ) ;
// send the message to Front End
NLNET : : CMessage msgout ( " IMPULSION_UID " ) ;
msgout . serial ( userId ) ;
msgout . serialBufferWithSize ( ( uint8 * ) bmsOut . buffer ( ) , bmsOut . length ( ) ) ;
try
{
CUnifiedNetwork : : getInstance ( ) - > send ( frontEndId , msgout ) ;
}
catch ( const Exception & e )
{
nlwarning ( " CStringManager::updateUserLanguage : Error : %s " , e . what ( ) ) ;
}
}
/*
* Replace a phrase in default language ( s ) ( message handler )
*/
void CStringManager : : setPhrase ( NLNET : : CMessage & message )
{
std : : string phraseName ;
ucstring phraseContent ;
try
{
message . serial ( phraseName ) ;
message . serial ( phraseContent ) ;
}
catch ( const Exception & e )
{
nlwarning ( " <setPhrase> %s " , e . what ( ) ) ;
return ;
}
setPhrase ( phraseName , phraseContent ) ;
}
/*
* Replace a phrase in specified language ( message handler )
*/
void CStringManager : : setPhraseLang ( NLNET : : CMessage & message )
{
std : : string phraseName ;
ucstring phraseContent ;
std : : string langString ;
try
{
message . serial ( phraseName ) ;
message . serial ( phraseContent ) ;
message . serial ( langString ) ;
}
catch ( Exception & e )
{
nlwarning ( " <setPhrase> %s " , e . what ( ) ) ;
return ;
}
TLanguages lang = checkLanguageCode ( langString ) ;
setPhrase ( phraseName , phraseContent , lang ) ;
}
/*
* Replace a phrase in default language ( s )
*/
void CStringManager : : setPhrase ( std : : string const & phraseName , ucstring const & phraseContent )
{
if ( _DefaultSetPhraseLanguage = = NB_LANGUAGES )
for ( int i = 0 ; i < NB_LANGUAGES ; + + i )
setPhrase ( phraseName , phraseContent , ( TLanguages ) i ) ;
else
setPhrase ( phraseName , phraseContent , _DefaultSetPhraseLanguage ) ;
}
/// Store a set of user named item associated with an AIInstance
void CStringManager : : storeItemNamesForAIInstance ( uint32 aiInstance , const std : : vector < R2 : : TCharMappedInfo > & itemInfos )
{
// first, parse all the container to remove any previously user item with this aiInstance
TRingUserItemInfos : : iterator first ( _RingUserItemInfos . begin ( ) ) , last ( _RingUserItemInfos . end ( ) ) ;
// for each item having one or more translation...
for ( ; first ! = last ; + + first )
{
std : : vector < TRingUserItemInfo > & items = first - > second ;
// for each translation of this item...
for ( uint i = 0 ; i < items . size ( ) ; + + i )
{
if ( items [ i ] . AIInstance = = aiInstance )
{
// remove this one
items . erase ( items . begin ( ) + i ) ;
- - i ;
// NB : item with 0 translation are keept in the table because
// the set of user item in the ring is closed and limited.
}
}
}
// insert the new items definition
for ( uint i = 0 ; i < itemInfos . size ( ) ; + + i )
{
const R2 : : TCharMappedInfo & itemInfo = itemInfos [ i ] ;
TRingUserItemInfo ruii ;
ruii . AIInstance = aiInstance ;
ruii . ItemNameId = storeString ( itemInfo . getName ( ) ) ;
_RingUserItemInfos [ itemInfo . getItemSheet ( ) ] . push_back ( ruii ) ;
}
}
/*
NLMISC_COMMAND ( verboseStringManager , " Turn on or off or check the state of verbose string manager logging " , " " )
{
if ( args . size ( ) > 1 )
return false ;
if ( args . size ( ) = = 1 )
{
if ( args [ 0 ] = = string ( " on " ) | | args [ 0 ] = = string ( " ON " ) | | args [ 0 ] = = string ( " true " ) | | args [ 0 ] = = string ( " TRUE " ) | | args [ 0 ] = = string ( " 1 " ) )
VerboseLog = true ;
if ( args [ 0 ] = = string ( " off " ) | | args [ 0 ] = = string ( " OFF " ) | | args [ 0 ] = = string ( " false " ) | | args [ 0 ] = = string ( " FALSE " ) | | args [ 0 ] = = string ( " 0 " ) )
VerboseLog = false ;
}
nlinfo ( " VerboseLogging is %s " , VerboseLog ? " ON " : " OFF " ) ;
return true ;
}
*/
NLMISC_CATEGORISED_COMMAND ( stringmanager , loadPhraseFile , " Merge a phrase file into string manager " , " <language code> <directory>[/file] " )
{
if ( args . size ( ) ! = 2 )
return false ;
std : : string lang = args [ 0 ] ;
std : : string file = args [ 1 ] ;
CStringManager : : TLanguages language = SM - > checkLanguageCode ( lang ) ;
if ( SM - > getLanguageCodeString ( language ) ! = lang )
{
log . displayNL ( " Failed, language '%s' is not a valid language " , lang . c_str ( ) ) ;
return false ;
}
if ( ! CFile : : fileExists ( file ) )
{
if ( ! CFile : : isDirectory ( file ) )
{
log . displayNL ( " Failed, path '%s' is not a valid file nor directory " , file . c_str ( ) ) ;
return false ;
}
file = CPath : : standardizePath ( file ) + " phrase_ " + lang + " .txt " ;
if ( ! CFile : : fileExists ( file ) )
{
log . displayNL ( " Failed to locate default phrase file '%s' " , file . c_str ( ) ) ;
return false ;
}
}
SM - > loadPhraseFile ( file , language , " " , & log ) ;
return true ;
}
NLMISC_CATEGORISED_COMMAND ( stringmanager , mergeWordFile , " Merge a word file into string manager " , " <language code> <word type> <directory>[/file] " )
{
if ( args . size ( ) ! = 3 )
return false ;
std : : string lang = args [ 0 ] ;
std : : string word = toLower ( args [ 1 ] ) ;
std : : string file = args [ 2 ] ;
// get language
CStringManager : : TLanguages language = SM - > checkLanguageCode ( lang ) ;
if ( SM - > getLanguageCodeString ( language ) ! = lang )
{
log . displayNL ( " Failed, language '%s' is not a valid language " , lang . c_str ( ) ) ;
return false ;
}
// get word type
CStringManager : : TParameterTraitList typeNames = CStringManager : : CParameterTraits : : getParameterTraitsNames ( ) ;
STRING_MANAGER : : TParamType wordType ;
uint i ;
for ( i = 0 ; i < typeNames . size ( ) ; + + i )
{
if ( toLower ( typeNames [ i ] . second ) = = word )
{
wordType = typeNames [ i ] . first ;
break ;
}
}
if ( i = = typeNames . size ( ) )
{
log . displayNL ( " Failed, word type '%s' is not valid " , word . c_str ( ) ) ;
return false ;
}
//
if ( ! CFile : : fileExists ( file ) )
{
if ( ! CFile : : isDirectory ( file ) )
{
log . displayNL ( " Failed, path '%s' is not a valid file nor directory " , file . c_str ( ) ) ;
return false ;
}
file = CPath : : standardizePath ( file ) + word + " _words_ " + lang + " .txt " ;
if ( ! CFile : : fileExists ( file ) )
{
log . displayNL ( " Failed to locate default word file '%s' " , file . c_str ( ) ) ;
return false ;
}
}
bool oldMode = SM - > ReadTranslationWork ;
// We don't want diff with work file now
SM - > ReadTranslationWork = false ;
SM - > mergeEntityWordsFile ( file , language , wordType ) ;
SM - > ReadTranslationWork = oldMode ;
return true ;
}
NLMISC_CATEGORISED_COMMAND ( stringmanager , displayEntityWords , " display entity words for a language and type " , " <language code> <word type> [wordwildcard] " )
{
if ( args . size ( ) < 2 | | args . size ( ) > 3 )
return false ;
std : : string lang = args [ 0 ] ;
std : : string word = toLower ( args [ 1 ] ) ;
std : : string wc ;
if ( args . size ( ) = = 3 )
wc = args [ 2 ] ;
// get language
CStringManager : : TLanguages language = SM - > checkLanguageCode ( lang ) ;
if ( SM - > getLanguageCodeString ( language ) ! = lang )
{
log . displayNL ( " Failed, language '%s' is not a valid language " , lang . c_str ( ) ) ;
return false ;
}
// get word type
CStringManager : : TParameterTraitList typeNames = CStringManager : : CParameterTraits : : getParameterTraitsNames ( ) ;
STRING_MANAGER : : TParamType wordType ;
uint i ;
for ( i = 0 ; i < typeNames . size ( ) ; + + i )
{
if ( toLower ( typeNames [ i ] . second ) = = word )
{
wordType = typeNames [ i ] . first ;
break ;
}
}
if ( i = = typeNames . size ( ) )
{
log . displayNL ( " Failed, word type '%s' is not valid " , word . c_str ( ) ) ;
return false ;
}
SM - > displayEntityWords ( language , wordType , wc , & log ) ;
return true ;
}
NLMISC_CATEGORISED_COMMAND ( stringmanager , setEntityWord , " set a word value " , " <language code>.<word type>.<word>.<determinant> <value> " )
{
if ( args . size ( ) < 2 | | args . size ( ) > 5 )
return false ;
std : : string path = args [ 0 ] ;
uint wi = 1 ;
while ( wi < args . size ( ) - 1 )
path + = " . " + args [ wi + + ] ;
ucstring word ( args [ wi ] ) ;
// get language
SM - > setEntityWord ( path , word ) ;
return true ;
}
NLMISC_CATEGORISED_COMMAND ( stringmanager , loadBotNames , " load a bot names file " , " [bot names file] [reset bot names (0|1)] " )
{
if ( args . size ( ) > 2 )
return false ;
std : : string filename = " bot_names.txt " ;
bool resetBotnames = false ;
if ( args . size ( ) > 0 )
filename = args [ 0 ] ;
if ( args . size ( ) > 1 )
NLMISC : : fromString ( args [ 1 ] , resetBotnames ) ;
SM - > loadBotNames ( filename , resetBotnames , & log ) ;
return true ;
}
NLMISC_CATEGORISED_COMMAND ( stringmanager , setBotName , " set a bot name " , " <bot name (utf8)> <translation (utf8)> " )
{
if ( args . size ( ) ! = 2 )
return false ;
ucstring botname , translation ;
botname . fromUtf8 ( args [ 0 ] ) ;
translation . fromUtf8 ( args [ 1 ] ) ;
SM - > setBotName ( botname , translation ) ;
return true ;
}
NLMISC_CATEGORISED_COMMAND ( stringmanager , readStringManagerRepository , " parse a whole repository with phrases and words (language is optional, none will load rep for all languages) " , " <directory> [language code] " )
{
if ( args . size ( ) < 1 | | args . size ( ) > 2 )
return false ;
string path = args [ 0 ] ;
if ( args . size ( ) = = 2 )
{
string lang = args [ 1 ] ;
CStringManager : : TLanguages language = SM - > checkLanguageCode ( lang ) ;
if ( SM - > getLanguageCodeString ( language ) ! = lang )
{
log . displayNL ( " Failed, language '%s' is not a valid language " , lang . c_str ( ) ) ;
return false ;
}
log . displayNL ( " Reading text repository '%s' for language '%s' " ,
path . c_str ( ) ,
lang . c_str ( ) ) ;
SM - > readRepository ( path , language , & log ) ;
}
else
{
log . displayNL ( " Reading text repository '%s' for all language " ,
path . c_str ( ) ) ;
uint i ;
for ( i = 1 ; i < CStringManager : : NB_LANGUAGES ; + + i )
{
SM - > readRepository ( path , ( CStringManager : : TLanguages ) i , & log ) ;
}
}
return true ;
}
NLMISC_CATEGORISED_COMMAND ( stringmanager , defaultSetPhraseLanguage , " Selects the language overriden by AIS messages " , " <language code> " )
{
if ( args . size ( ) < 0 | | args . size ( ) > 1 )
return false ;
if ( args . size ( ) ! = 0 )
{
CStringManager : : TLanguages language = CStringManager : : english ;
if ( args [ 0 ] = = " all " )
{
language = CStringManager : : NB_LANGUAGES ;
}
else
{
language = SM - > checkLanguageCode ( args [ 0 ] ) ;
}
SM - > setDefaultSetPhraseLanguage ( language ) ;
}
CStringManager : : TLanguages language = SM - > getDefaultSetPhraseLanguage ( ) ;
std : : string languageCode ;
if ( language = = CStringManager : : NB_LANGUAGES )
languageCode = " all " ;
else
languageCode = SM - > getLanguageCodeString ( language ) ;
log . displayNL ( " Language overriden by AIS messages is %s " , languageCode . c_str ( ) ) ;
return true ;
}