// Ryzom - MMORPG Framework
// Copyright (C) 2010-2017 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 .
#include "stdpch.h"
#include "string_manager_client.h"
#include "nel/misc/file.h"
#include "client_cfg.h"
#include "net_manager.h"
#include "connection.h"
#include "nel/misc/hierarchical_timer.h"
#include "nel/misc/algo.h"
#include "misc.h"
#include "entity_cl.h"
#ifdef DEBUG_NEW
#define new DEBUG_NEW
#endif
using namespace std;
using namespace NLMISC;
namespace STRING_MANAGER
{
// ***************************************************************************
map CStringManagerClient::_SpecItem_TempMap;
map CStringManagerClient::_DynStrings;
vector CStringManagerClient::_TitleWords;
bool CStringManagerClient::_SpecItem_MemoryCompressed = false;
char *CStringManagerClient::_SpecItem_Labels = NULL;
char *CStringManagerClient::_SpecItem_NameDesc = NULL;
vector CStringManagerClient::_SpecItems;
bool MustReleaseStaticArrays = true;
CStringManagerClient *CStringManagerClient::_Instance= NULL;
string CStringManagerClient::_WaitString("???");
CStringManagerClient::CStringManagerClient()
{
_CacheInited = false;
_CacheLoaded = false;
// insert the empty string.
_ReceivedStrings.insert(make_pair((uint)EmptyStringId, string()));
// reserve some place to avoid reallocation as possible
_CacheStringToSave.reserve(1024);
}
// destructor.
CStringManagerClient::~CStringManagerClient()
{
if (MustReleaseStaticArrays)
{
if (_SpecItem_MemoryCompressed)
{
delete [] _SpecItem_Labels;
_SpecItem_Labels = NULL;
delete [] _SpecItem_NameDesc;
_SpecItem_NameDesc = NULL;
_SpecItems.clear();
_SpecItem_MemoryCompressed = false;
}
}
}
CStringManagerClient *CStringManagerClient::instance()
{
if (_Instance == 0)
_Instance = new CStringManagerClient();
return _Instance;
}
void CStringManagerClient::release(bool mustReleaseStaticArrays)
{
if (_Instance != 0)
{
bool prev = MustReleaseStaticArrays;
MustReleaseStaticArrays = mustReleaseStaticArrays;
delete _Instance;
MustReleaseStaticArrays = prev;
_Instance =0;
}
}
void CStringManagerClient::initCache(const std::string &shardId, const std::string &languageCode)
{
H_AUTO( CStringManagerClient_initCache )
_ShardId = shardId;
_LanguageCode = languageCode;
// to be inited, shard id and language code must be filled
if (!_ShardId.empty() && !_LanguageCode.empty())
_CacheInited = true;
else
_CacheInited = false;
}
void CStringManagerClient::loadCache(uint32 timestamp)
{
H_AUTO( CStringManagerClient_loadCache )
if (_CacheInited)
{
try
{
const uint currentVersion = 1;
_CacheFilename = std::string("save/") + _ShardId.substr(0, _ShardId.find(":")) + ".string_cache";
nlinfo("SM : Try to open the string cache : %s", _CacheFilename.c_str());
if (CFile::fileExists(_CacheFilename))
{
// there is a cache file, check date reset it if needed
{
NLMISC::CIFile file(_CacheFilename);
file.setVersionException(false, false);
file.serialVersion(currentVersion);
file.serial(_Timestamp);
}
if (_Timestamp != timestamp)
{
nlinfo("SM: Clearing string cache : outofdate");
// the cache is not sync, reset it
NLMISC::COFile file(_CacheFilename);
file.serialVersion(currentVersion);
file.serial(timestamp);
}
else
{
nlinfo("SM : string cache in sync. cool");
}
}
else
{
nlinfo("SM: Creating string cache");
// cache file don't exist, create it with the timestamp
NLMISC::COFile file(_CacheFilename);
file.serialVersion(currentVersion);
file.serial(timestamp);
}
// clear all current data.
_ReceivedStrings.clear();
_ReceivedDynStrings.clear();
// NB : we keep the waiting strings and dyn strings
// insert the empty string.
_ReceivedStrings.insert(make_pair((uint)EmptyStringId, string()));
// load the cache file
NLMISC::CIFile file(_CacheFilename);
int version = file.serialVersion(currentVersion);
file.serial(_Timestamp);
nlassert(_Timestamp == timestamp);
nlassert(version >= 1); // Initial version
while (!file.eof())
{
uint32 id;
string str;
file.serial(id);
file.serial(str);
//nldebug("SM : loading string [%6u] as [%s] in cache", id, str.toString().c_str());
_ReceivedStrings.insert(std::make_pair(id, str));
}
_CacheLoaded = true;
}
catch(const NLMISC::Exception &e)
{
nlinfo("SM : loadCache failed, exception : %s", e.what());
nlinfo("SM : cache deactivated");
// unactivated cache.
_CacheFilename.erase();
}
}
}
void CStringManagerClient::waitString(uint32 stringId, const IStringWaiterRemover *premover, string *result)
{
H_AUTO( CStringManagerClient_waitString )
nlassert(premover && result);
string value;
if (getString(stringId, value))
*result = value;
else
{
// wait for the string
TStringWaiter sw;
sw.Result = result;
sw.Remover = premover;
_StringsWaiters.insert(std::make_pair(stringId, sw));
}
}
void CStringManagerClient::waitString(uint32 stringId, IStringWaitCallback *pcallback)
{
H_AUTO( CStringManagerClient_waitString2 )
nlassert(pcallback != 0);
string value;
if (getString(stringId, value))
{
pcallback->onStringAvailable(stringId, value);
}
else
{
// wait for the string
_StringsCallbacks.insert(std::make_pair(stringId, pcallback));
}
}
void CStringManagerClient::waitDynString(uint32 stringId, const IStringWaiterRemover *premover, string *result)
{
H_AUTO( CStringManagerClient_waitDynString )
nlassert(premover && result);
string value;
if (getDynString(stringId, value))
*result = value;
else
{
// wait for the string
TStringWaiter sw;
sw.Result = result;
sw.Remover = premover;
_DynStringsWaiters.insert(std::make_pair(stringId, sw));
}
}
void CStringManagerClient::waitDynString(uint32 stringId, IStringWaitCallback *pcallback)
{
H_AUTO( CStringManagerClient_waitDynString2 )
nlassert(pcallback != 0);
string value;
if (getDynString(stringId, value))
{
pcallback->onDynStringAvailable(stringId, value);
}
else
{
_DynStringsCallbacks.insert(std::make_pair(stringId, pcallback));
}
}
void CStringManagerClient::removeStringWaiter(const IStringWaiterRemover *remover)
{
H_AUTO( CStringManagerClient_removeStringWaiter )
// search in waiting string
{
restartLoop1:
TStringWaitersContainer::iterator first(_StringsWaiters.begin()), last(_StringsWaiters.end());
for (; first != last; ++first)
{
if (first->second.Remover == remover)
{
_StringsWaiters.erase(first);
goto restartLoop1;
}
}
}
// search in waiting dyn string
{
restartLoop2:
TStringWaitersContainer::iterator first(_DynStringsWaiters.begin()), last(_DynStringsWaiters.end());
for (; first != last; ++first)
{
if (first->second.Remover == remover)
{
_DynStringsWaiters.erase(first);
goto restartLoop2;
}
}
}
}
void CStringManagerClient::removeStringWaiter(const IStringWaitCallback *callback)
{
// H_AUTO( CStringManagerClient_removeStringWaiter2 )
// search in waiting string
{
restartLoop3:
TStringCallbacksContainer::iterator first(_StringsCallbacks.begin()), last(_StringsCallbacks.end());
for (; first != last; ++first)
{
if (first->second == callback)
{
_StringsCallbacks.erase(first);
goto restartLoop3;
}
}
}
// search in waiting dyn string
{
restartLoop4:
TStringCallbacksContainer::iterator first(_DynStringsCallbacks.begin()), last(_DynStringsCallbacks.end());
for (; first != last; ++first)
{
if (first->second == callback)
{
_DynStringsCallbacks.erase(first);
goto restartLoop4;
}
}
}
}
bool CStringManagerClient::getString(uint32 stringId, string &result)
{
H_AUTO( CStringManagerClient_getString )
if (ClientCfg.Local)
{
TStringsContainer::iterator it(_ReceivedStrings.find(stringId));
if (it != _ReceivedStrings.end())
{
result = it->second;
}
else
{
result = "StringID = " + toString(stringId);
}
}
else
{
TStringsContainer::iterator it(_ReceivedStrings.find(stringId));
if (it == _ReceivedStrings.end())
{
CHashSet::iterator it(_WaitingStrings.find(stringId));
if (it == _WaitingStrings.end())
{
_WaitingStrings.insert(stringId);
// need to ask for this string.
NLMISC::CBitMemStream bms;
static const string msgType = "STRING_MANAGER:STRING_RQ";
if( GenericMsgHeaderMngr.pushNameToStream(msgType,bms) )
{
bms.serial( stringId );
NetMngr.push( bms );
//nldebug(" sending 'STRING_MANAGER:STRING_RQ' message to server");
}
else
{
nldebug(" unknown message name 'STRING_MANAGER:STRING_RQ'");
}
}
if (ClientCfg.DebugStringManager)
{
char tmp[1024];
sprintf(tmp, "", stringId);
result = tmp;
}
else
result.erase(); // = _WaitString;
return false;
}
if (ClientCfg.DebugStringManager)
{
char tmp[1024];
sprintf(tmp, "", stringId);
result = tmp + it->second;
}
else
{
result = it->second;
if (result.size() > 9 && result.substr(0, 9) == "::iterator itds = _DynStrings.find(result.substr(9, result.size()-10));
if (itds != _DynStrings.end())
result = itds->second;
}
}
}
return true;
}
void CStringManagerClient::receiveString(uint32 stringId, const string &str)
{
H_AUTO( CStringManagerClient_receiveString )
//nlinfo("String %u available : [%s]", stringId, str.toString().c_str());
CHashSet::iterator it(_WaitingStrings.find(stringId));
if (it != _WaitingStrings.end())
{
_WaitingStrings.erase(it);
}
bool updateCache = true;
if (_ReceivedStrings.find(stringId) != _ReceivedStrings.end())
{
TStringsContainer::iterator it(_ReceivedStrings.find(stringId));
nlwarning("Receiving stringID %u (%s), already in received string (%s), replacing with new value.",
stringId,
str.c_str(),
it->second.c_str());
if (it->second != str)
it->second = str;
else
updateCache = false;
}
else
{
_ReceivedStrings.insert(std::make_pair(stringId, str));
}
if (updateCache)
{
// update the string cache. DON'T SAVE now cause
if (_CacheInited && !_CacheFilename.empty())
{
CCacheString cs;
cs.StringId= stringId;
cs.String= str;
_CacheStringToSave.push_back(cs);
}
}
// update the waiting strings
{
std::pair range =
_StringsWaiters.equal_range(stringId);
if (range.first != range.second)
{
for (; range.first != range.second; ++range.first)
{
TStringWaiter &sw = range.first->second;
*(sw.Result) = str;
}
_StringsWaiters.erase(stringId);
}
}
// callback the waiter
{
std::pair range =
_StringsCallbacks.equal_range(stringId);
if (range.first != range.second)
{
for (; range.first != range.second; ++range.first)
{
range.first->second->onStringAvailable(stringId, str);
}
_StringsCallbacks.erase(stringId);
}
}
// try to complete any pending dyn string
{
TDynStringsContainer::iterator first, last;
restartLoop:
first = _WaitingDynStrings.begin();
last = _WaitingDynStrings.end();
for (; first != last; ++first)
{
string value;
uint number = first->first;
/// Warning: if getDynString() return true, 'first' is erased => don't use it after in this loop
if (getDynString(number, value))
{
//nlinfo("DynString %u available : [%s]", number, value.toString().c_str());
// this dyn string is now complete !
// update the waiting dyn strings
{
std::pair range =
_DynStringsWaiters.equal_range(number);
if (range.first != range.second)
{
for (; range.first != range.second; ++range.first)
{
TStringWaiter &sw = range.first->second;
*(sw.Result) = str;
}
_DynStringsWaiters.erase(number);
}
}
// callback the waiting dyn strings
{
std::pair range =
_DynStringsCallbacks.equal_range(number);
if (range.first != range.second)
{
for (; range.first != range.second; ++range.first)
{
range.first->second->onDynStringAvailable(number, value);
}
_DynStringsCallbacks.erase(number);
}
}
goto restartLoop;
}
}
}
}
void CStringManagerClient::flushStringCache()
{
if(!_CacheStringToSave.empty())
{
NLMISC::COFile file(_CacheFilename, true);
for(uint i=0;i<_CacheStringToSave.size();i++)
{
file.serial(_CacheStringToSave[i].StringId);
file.serial(_CacheStringToSave[i].String);
}
_CacheStringToSave.clear();
}
}
void CStringManagerClient::receiveDynString(NLMISC::CBitMemStream &bms)
{
H_AUTO( CStringManagerClient_receiveDynString )
TDynStringInfo dynInfo;
dynInfo.Status = TDynStringInfo::received;
// read the dynamic string Id
uint32 dynId;
bms.serial(dynId);
/// read the base string Id
bms.serial(dynInfo.StringId);
// try to build the string
dynInfo.Message = bms;
buildDynString(dynInfo);
if (dynInfo.Status == TDynStringInfo::complete)
{
if (!ClientCfg.Light)
{
//nlinfo("DynString %u available : [%s]", dynId, dynInfo.String.toString().c_str());
}
_ReceivedDynStrings.insert(std::make_pair(dynId, dynInfo));
// security, if dynstring Message received twice, it is possible that the dynstring is still in waiting list
_WaitingDynStrings.erase(dynId);
// update the waiting dyn strings
{
std::pair range =
_DynStringsWaiters.equal_range(dynId);
if (range.first != range.second)
{
for (; range.first != range.second; ++range.first)
{
TStringWaiter &sw = range.first->second;
*(sw.Result) = dynInfo.String;
}
_DynStringsWaiters.erase(dynId);
}
}
// callback the waiting dyn strings
{
std::pair range =
_DynStringsCallbacks.equal_range(dynId);
if (range.first != range.second)
{
for (; range.first != range.second; ++range.first)
{
range.first->second->onDynStringAvailable(dynId, dynInfo.String);
}
_DynStringsCallbacks.erase(dynId);
}
}
}
else
_WaitingDynStrings.insert(std::make_pair(dynId, dynInfo));
}
bool CStringManagerClient::buildDynString(TDynStringInfo &dynInfo)
{
H_AUTO( CStringManagerClient_buildDynString )
if (dynInfo.Status == TDynStringInfo::received)
{
if (!getString(dynInfo.StringId, dynInfo.String))
{
// can't continue now, need the base string.
return false;
}
// ok, we have the base string, we can serial the parameters
string::iterator first(dynInfo.String.begin()), last(dynInfo.String.end());
for (; first != last; ++first)
{
if (*first == '%')
{
first ++;
if (first != last && *first != '%')
{
// we have a replacement point.
TParamValue param;
param.ReplacementPoint = (first-1) - dynInfo.String.begin();
switch(*first)
{
case 's':
param.Type = string_id;
try
{
dynInfo.Message.serial(param.StringId);
}
catch(const Exception &)
{
param.StringId = EmptyStringId;
}
break;
case 'i':
param.Type = integer;
try
{
dynInfo.Message.serial(param.Integer);
}
catch(const Exception &)
{
param.Integer= 0;
}
break;
case 't':
param.Type = time;
try
{
dynInfo.Message.serial(param.Time);
}
catch(const Exception &)
{
param.Time= 0;
}
break;
case '$':
param.Type = money;
try
{
dynInfo.Message.serial(param.Money);
}
catch(const Exception &)
{
param.Money= 0;
}
break;
case 'm':
param.Type = dyn_string_id;
try
{
dynInfo.Message.serial(param.DynStringId);
}
catch(const Exception &)
{
param.DynStringId= EmptyDynStringId;
}
break;
default:
nlwarning("Error: unknown replacement tag %%%c", (char)*first);
return false;
}
dynInfo.Params.push_back(param);
}
}
}
dynInfo.Status = TDynStringInfo::serialized;
}
if (dynInfo.Status == TDynStringInfo::serialized)
{
// try to retreive all string parameter to build the string.
string temp;
temp.reserve(dynInfo.String.size() * 2);
string::iterator src(dynInfo.String.begin());
string::iterator move = src;
std::vector::iterator first(dynInfo.Params.begin()), last(dynInfo.Params.end());
for (; first != last; ++first)
{
TParamValue ¶m = *first;
switch(param.Type)
{
case string_id:
{
string str;
if (!getString(param.StringId, str))
return false;
ucstring::size_type p1 = str.find('[');
if (p1 != ucstring::npos)
{
str = str.substr(0, p1)+STRING_MANAGER::CStringManagerClient::getLocalizedName(str.substr(p1));
}
// If the string is a player name, we may have to remove the shard name (if the string looks like a player name)
if(!str.empty() && !PlayerSelectedHomeShardNameWithParenthesis.empty())
{
// fast pre-test
if( str[str.size()-1]==')' )
{
// the player name must be at least bigger than the string with ()
if(str.size()>PlayerSelectedHomeShardNameWithParenthesis.size())
{
// If the shard name is the same as the player home shard name, remove it
uint len= (uint)PlayerSelectedHomeShardNameWithParenthesis.size();
uint start= (uint)str.size()-len;
if(ucstrnicmp(str, start, len, PlayerSelectedHomeShardNameWithParenthesis)==0) // TODO: NLMISC::compareCaseInsensitive
str.resize(start);
}
}
}
// If the string contains a title, then remove it
string::size_type pos = str.find('$');
if ( ! str.empty() && pos != string::npos)
{
str = CEntityCL::removeTitleFromName(str);
}
// append this string
temp.append(move, src+param.ReplacementPoint);
temp += str;
move = dynInfo.String.begin()+param.ReplacementPoint+2;
}
break;
case integer:
{
char value[1024];
sprintf(value, "%d", param.Integer);
temp.append(move, src+param.ReplacementPoint);
temp += value;
move = dynInfo.String.begin()+param.ReplacementPoint+2;
}
break;
case time:
{
string value;
uint32 time = (uint32)param.Time;
if( time >= (10*60*60) )
{
uint32 nbHours = time / (10*60*60);
time -= nbHours * 10 * 60 * 60;
value = toString("%d ", nbHours) + CI18N::get("uiMissionTimerHour") + " ";
uint32 nbMinutes = time / (10*60);
time -= nbMinutes * 10 * 60;
value = value + toString("%d ", nbMinutes) + CI18N::get("uiMissionTimerMinute") + " ";
}
else if( time >= (10*60) )
{
uint32 nbMinutes = time / (10*60);
time -= nbMinutes * 10 * 60;
value = value + toString("%d ", nbMinutes) + CI18N::get("uiMissionTimerMinute") + " ";
}
uint32 nbSeconds = time / 10;
value = value + toString("%d", nbSeconds) + CI18N::get("uiMissionTimerSecond");
temp.append(move, src+param.ReplacementPoint);
temp+=value;
move = dynInfo.String.begin()+param.ReplacementPoint+2;
}
break;
case money:
///\todo nicoB/Boris : this is a temp patch that display money as integers
{
char value[1024];
sprintf(value, "%u", (uint32)param.Money);
temp.append(move, src+param.ReplacementPoint);
temp += value;
move = dynInfo.String.begin()+param.ReplacementPoint+2;
}
// TODO
// temp.append(move, src+param.ReplacementPoint);
// move = dynInfo.String.begin()+param.ReplacementPoint+2;
break;
case dyn_string_id:
{
string dynStr;
if (!getDynString(param.DynStringId, dynStr))
return false;
temp.append(move, src+param.ReplacementPoint);
temp += dynStr;
move = dynInfo.String.begin()+param.ReplacementPoint+2;
}
break;
default:
nlwarning("Unknown parameter type.");
break;
}
}
// append the rest of the string
temp.append(move, dynInfo.String.end());
// apply any 'delete' character in the string and replace double '%'
{
ptrdiff_t i =0;
while (i < (ptrdiff_t)temp.size())
{
if (temp[i] == 8)
{
// remove the 'delete' char AND the next char
temp.erase(i, 2);
}
else if (temp[i] == '%' && i < temp.size()-1 && temp[i+1] == '%')
{
temp.erase(i, 1);
}
else
++i;
}
}
dynInfo.Status = TDynStringInfo::complete;
dynInfo.Message.clear();
dynInfo.String = temp;
return true;
}
if (dynInfo.Status == TDynStringInfo::complete)
return true;
nlwarning("Inconsistent dyn string status : %u", dynInfo.Status);
return false;
}
bool CStringManagerClient::getDynString(uint32 dynStringId, std::string &result)
{
H_AUTO( CStringManagerClient_getDynString )
if (dynStringId == EmptyDynStringId)
return true;
if (ClientCfg.Local)
{
TDynStringsContainer::iterator it(_ReceivedDynStrings.find(dynStringId));
if (it != _ReceivedDynStrings.end())
{
result = it->second.String;
}
else
{
result = "DYNSTR = " + toString(dynStringId);
}
return true;
}
else
{
TDynStringsContainer::iterator it(_ReceivedDynStrings.find(dynStringId));
if (it != _ReceivedDynStrings.end())
{
// ok, we have the string with all the parts.
if (ClientCfg.DebugStringManager)
{
char tmp[1024];
sprintf(tmp, "", dynStringId);
result = tmp + it->second.String;
}
else
result = it->second.String;
// security/antiloop checking
it = _WaitingDynStrings.find(dynStringId);
if (it != _WaitingDynStrings.end())
{
nlwarning("CStringManager::getDynString : the string %u is received but still in _WaintingDynStrings !", dynStringId);
_WaitingDynStrings.erase(it);
}
return true;
}
else
{
// check to see if the string is available now.
it = _WaitingDynStrings.find(dynStringId);
if (it == _WaitingDynStrings.end())
{
if (ClientCfg.DebugStringManager)
{
nlwarning("DynStringID %u is unknown !", dynStringId);
char tmp[1024];
sprintf(tmp, "", dynStringId);
result = tmp;
}
else
result.erase(); //_WaitString;
return false;
}
if (buildDynString(it->second))
{
if (ClientCfg.DebugStringManager)
{
char tmp[1024];
sprintf(tmp, "", dynStringId);
result = tmp + it->second.String;
}
else
result = it->second.String;
_ReceivedDynStrings.insert(std::make_pair(dynStringId, it->second));
_WaitingDynStrings.erase(it);
return true;
}
if (ClientCfg.DebugStringManager)
{
char tmp[1024];
sprintf(tmp, "", dynStringId);
result = tmp;
}
else
result.erase(); // = _WaitString;
return false;
}
}
}
// Tool fct to lookup a reference file
static string lookupReferenceFile(const string &fileName)
{
string referenceFile;
// special location for the "wk" language
if(ClientCfg.LanguageCode=="wk")
{
referenceFile = "./translation/translated/"+CFile::getFilename(fileName);
if (!CFile::fileExists(referenceFile))
{
nlwarning("Translation file: %s not found", referenceFile.c_str());
referenceFile.clear();
}
}
else
{
referenceFile = CPath::lookup(fileName, false);
}
return referenceFile;
}
void CLoadProxy::loadStringFile(const string &filename, ucstring &text)
{
vector reference;
vector addition;
vector diff;
// get the correct path name of the ref file
string referenceFile= lookupReferenceFile(filename);
// load the reference file
if (!referenceFile.empty())
{
STRING_MANAGER::loadStringFile(referenceFile, reference, false);
}
// try to find the working file
string workingFile("./translation/work/"+CFile::getFilename(filename));
if (CFile::fileExists(workingFile))
{
STRING_MANAGER::loadStringFile(workingFile, addition, false);
}
else
{
// no addition file ? just copy the reference into addition
addition = reference;
}
TStringDiffContext context(addition, reference, diff);
TStringDiff differ;
differ.makeDiff(this, context);
text.erase();
text = prepareStringFile(context.Diff, true);
}
void CLoadProxy::onEquivalent(uint addIndex, uint /* refIndex */, TStringDiffContext &context)
{
context.Diff.push_back(context.Addition[addIndex]);
}
void CLoadProxy::onAdd(uint addIndex, uint /* refIndex */, TStringDiffContext &context)
{
context.Diff.push_back(context.Addition[addIndex]);
//nldebug("Adding new string '%s' in CI18N", context.Addition[addIndex].Identifier.c_str());
if (ClientCfg.DebugStringManager)
context.Diff.back().Text = ucstring("")+context.Diff.back().Text;
}
void CLoadProxy::onRemove(uint /* addIndex */, uint /* refIndex */, TStringDiffContext &/* context */)
{
// nothing to do because we don't insert bad value
}
void CLoadProxy::onChanged(uint addIndex, uint /* refIndex */, TStringDiffContext &context)
{
// we use the addition value in this case
context.Diff.push_back(context.Addition[addIndex]);
//nldebug("Using changed string '%s' in CI18N", context.Addition[addIndex].Identifier.c_str());
if (ClientCfg.DebugStringManager)
context.Diff.back().Text = ucstring("")+context.Diff.back().Text;
}
void CLoadProxy::onSwap(uint /* newIndex */, uint /* refIndex */, TStringDiffContext &/* context */)
{
// don't swap.
}
// ***************************************************************************
/*
The readed for skill_word_en.txt for example. Do all the job of Diffs with work and translated dirs.
*/
class CReadWorkSheetFile : public TWorkSheetDiff::IDiffCallback
{
public:
void readWorkSheetFile(const string &filename, ucstring &text)
{
TWorksheet addition;
TWorksheet reference;
TWorksheet diff;
// get the correct path name of the ref file
string referenceFile= lookupReferenceFile(filename);
// load the reference file
if (!referenceFile.empty())
{
STRING_MANAGER::loadExcelSheet(referenceFile, reference);
STRING_MANAGER::makeHashCode(reference, false);
}
// try to find the working file
string workingFile("./translation/work/"+CFile::getFilename(filename));
if (CFile::fileExists(workingFile))
{
STRING_MANAGER::loadExcelSheet(workingFile, addition);
STRING_MANAGER::makeHashCode(addition, true);
}
else
{
text = prepareExcelSheet(reference);
return;
}
if (addition.size() == 0)
return;
// check column consistency.
bool doDiff = true;
uint i;
if (reference.ColCount != addition.ColCount)
{
nlwarning("Can't check for difference for file %s, column number is not the same!", filename.c_str());
doDiff = false;
}
if (doDiff)
{
// check eachg column name
for (i=0; i &fileChecks, const std::vector &fileNames, const string &languageCode)
{
fileChecks.resize(fileNames.size());
// **** First get the date of all the .txt files.
for(uint i=0;i fileNames;
fileNames.resize(numSpecialWords);
for(uint i=0;i fileChecks;
bool mustRebuild= !checkWordFileDates(fileChecks, fileNames, languageCode);
// **** rebuild or load?
if(mustRebuild)
{
for(uint i=0;i::max();
if( !ws.findCol(womenNameColIdent, womenNameColIndex) )
womenNameColIndex= std::numeric_limits::max();
// Get the description index if possible.
uint descColIndex = std::numeric_limits::max();
if( !ws.findCol(descColIdent, descColIndex) )
descColIndex= std::numeric_limits::max();
uint descColIndex2 = std::numeric_limits::max();
if( !ws.findCol(descColIdent2, descColIndex2) )
descColIndex2= std::numeric_limits::max();
// For all rows minus the first header one.
for(uint j=1;j::iterator it;
it= _SpecItem_TempMap.find( keyStr );
if ( it!=_SpecItem_TempMap.end() )
{
nlwarning("Error in initI18NSpecialWords(), %s already exist (not replaced)!", keyStr.c_str());
}
else
{
_SpecItem_TempMap[keyStr].Name= name;
// replace all \n in the desc with true \n
while(strFindReplace(_SpecItem_TempMap[keyStr].Name, "\\n", "\n"));
// insert in map of Women Name if OK.
if(womenNameColIndex!=std::numeric_limits::max())
{
const ucstring &womenName= ws.getData(j, womenNameColIndex);
_SpecItem_TempMap[keyStr].WomenName= womenName.toUtf8();
// replace all \n in the women name with true \n
while(strFindReplace(_SpecItem_TempMap[keyStr].WomenName, "\\n", "\n"));
}
// insert in map of Description if OK.
if(descColIndex!=std::numeric_limits::max())
{
const ucstring &desc= ws.getData(j, descColIndex);
_SpecItem_TempMap[keyStr].Desc= desc.toUtf8();
// replace all \n in the desc with true \n
while(strFindReplace(_SpecItem_TempMap[keyStr].Desc, "\\n", "\n"));
}
// insert in map of Description2 if OK.
if(descColIndex2!=std::numeric_limits::max())
{
const ucstring &desc= ws.getData(j, descColIndex2);
_SpecItem_TempMap[keyStr].Desc2= desc.toUtf8();
// replace all \n in the desc with true \n
while(strFindReplace(_SpecItem_TempMap[keyStr].Desc2, "\\n", "\n"));
}
}
}
nlinfo ("%d seconds for I18N words: Total: %s", (uint32)(ryzomGetLocalTime()-profile0+500)/1000, fileName.c_str());
}
}
else
{
CIFile packFile;
nlverify(packFile.open(StringClientPackedFileName));
// Serial the header
CPackHeader packHeader;
packFile.serial(packHeader);
// Serial the map
packFile.serialCont(_SpecItem_TempMap);
}
// **** Save if rebuilt
if(mustRebuild)
{
COFile packFile;
if(packFile.open(StringClientPackedFileName))
{
// Write the header
CPackHeader packHeader;
packHeader.LanguageCode= languageCode;
packHeader.FileChecks= fileChecks;
packHeader.PackedVersion= StringClientPackedVersion;
packFile.serial(packHeader);
// Serial the map
packFile.serialCont(_SpecItem_TempMap);
}
}
}
// ***************************************************************************
void CStringManagerClient::specialWordsMemoryCompress()
{
// Convert map to the light structure
// Count
uint32 nNbEntries = 0, nLabelSize = 0, nNameDescSize = 0;
map::iterator it = _SpecItem_TempMap.begin();
while (it != _SpecItem_TempMap.end())
{
nNbEntries++;
nLabelSize += (uint32)it->first.size() + 1;
nNameDescSize += (uint32)it->second.Name.size() + 1;
nNameDescSize += (uint32)it->second.WomenName.size() + 1;
nNameDescSize += (uint32)it->second.Desc.size() + 1;
nNameDescSize += (uint32)it->second.Desc2.size() + 1;
it++;
}
// Make big strings
_SpecItems.resize(nNbEntries);
_SpecItem_Labels = new char[nLabelSize];
_SpecItem_NameDesc = new char[nNameDescSize];
nNbEntries = 0;
nLabelSize = 0;
nNameDescSize = 0;
it = _SpecItem_TempMap.begin();
while (it != _SpecItem_TempMap.end())
{
if (NLMISC::startsWith(it->first.c_str(), "bf"))
{
uint nDbg = 0;
nDbg++;
}
_SpecItems[nNbEntries].Label = _SpecItem_Labels+nLabelSize;
strcpy(_SpecItem_Labels+nLabelSize, it->first.c_str());
nLabelSize += (uint32)it->first.size() + 1;
_SpecItems[nNbEntries].Name = _SpecItem_NameDesc+nNameDescSize;
strcpy(_SpecItem_NameDesc+nNameDescSize, it->second.Name.c_str());
nNameDescSize += (uint32)it->second.Name.size() + 1;
_SpecItems[nNbEntries].WomenName = _SpecItem_NameDesc+nNameDescSize;
strcpy(_SpecItem_NameDesc+nNameDescSize, it->second.WomenName.c_str());
nNameDescSize += (uint32)it->second.WomenName.size() + 1;
_SpecItems[nNbEntries].Desc = _SpecItem_NameDesc+nNameDescSize;
strcpy(_SpecItem_NameDesc+nNameDescSize, it->second.Desc.c_str());
nNameDescSize += (uint32)it->second.Desc.size() + 1;
_SpecItems[nNbEntries].Desc2 = _SpecItem_NameDesc+nNameDescSize;
strcpy(_SpecItem_NameDesc+nNameDescSize, it->second.Desc2.c_str());
nNameDescSize += (uint32)it->second.Desc2.size() + 1;
nNbEntries++;
it++;
}
// No need to sort the vector because the map was already sorted in the good order
contReset(_SpecItem_TempMap);
_SpecItem_MemoryCompressed = true;
}
// ***************************************************************************
const char *CStringManagerClient::getSpecialWord(const string &label, bool women)
{
if (label.empty())
{
static string emptyString;
return emptyString.c_str();
}
if (label[0] == '#')
{
return getLocalizedName(label.substr(1, label.size()-1));
}
// avoid case problems
static string lwrLabel;
lwrLabel = toLower(label);
if (_SpecItem_MemoryCompressed)
{
CItemLight tmp;
tmp.Label = (char*)lwrLabel.c_str();
vector::iterator it = lower_bound(_SpecItems.begin(), _SpecItems.end(), tmp, CItemLightComp());
if (it != _SpecItems.end())
{
if (strcmp(it->Label, lwrLabel.c_str()) == 0)
{
if( UseFemaleTitles && women )
{
if( !it->WomenName[0] )
return it->WomenName;
}
return it->Name;
}
}
}
else
{
map::iterator it = _SpecItem_TempMap.find(lwrLabel);
if (it != _SpecItem_TempMap.end())
{
if( UseFemaleTitles && women )
if (!it->second.WomenName.empty())
return it->second.WomenName.c_str();
return it->second.Name.c_str();
}
}
static string badString;
badString = "";
return badString.c_str();
}
// ***************************************************************************
const char *CStringManagerClient::getSpecialDesc(const string &label)
{
static string emptyString;
if (label.empty())
return emptyString.c_str();
// avoid case problems
static string lwrLabel;
lwrLabel = toLower(label);
if (_SpecItem_MemoryCompressed)
{
CItemLight tmp;
tmp.Label = lwrLabel.c_str();
vector::iterator it = lower_bound(_SpecItems.begin(), _SpecItems.end(), tmp, CItemLightComp());
if (it != _SpecItems.end())
{
if (strcmp(it->Label, lwrLabel.c_str()) == 0)
return it->Desc;
}
}
else
{
map::iterator it = _SpecItem_TempMap.find(lwrLabel);
if (it != _SpecItem_TempMap.end())
return it->second.Desc.c_str();
}
return emptyString.c_str();
}
// ***************************************************************************
const char *CStringManagerClient::getSpecialDesc2(const string &label)
{
static string emptyString;
if (label.empty())
return emptyString.c_str();
// avoid case problems
static string lwrLabel;
lwrLabel = toLower(label);
if (_SpecItem_MemoryCompressed)
{
CItemLight tmp;
tmp.Label = lwrLabel.c_str();
vector::iterator it = lower_bound(_SpecItems.begin(), _SpecItems.end(), tmp, CItemLightComp());
if (it != _SpecItems.end())
{
if (strcmp(it->Label, lwrLabel.c_str()) == 0)
return it->Desc2;
}
}
else
{
map::iterator it = _SpecItem_TempMap.find(lwrLabel);
if (it != _SpecItem_TempMap.end())
{
return it->second.Desc2.c_str();
}
}
return emptyString.c_str();
}
// ***************************************************************************
/*const ucchar *CStringManagerClient::getBrickLocalizedName(BRICK_FAMILIES::TBrickFamily e)
{
return getSpecialWord(BRICK_FAMILIES::toString(e));
}
*/
// ***************************************************************************
const char *CStringManagerClient::getPlaceLocalizedName(const string &placeNameID)
{
return getSpecialWord(placeNameID);
}
// ***************************************************************************
const char *CStringManagerClient::getFactionLocalizedName(const string &factionNameID)
{
return getSpecialWord(factionNameID);
}
// ***************************************************************************
const char *CStringManagerClient::getSkillLocalizedName(SKILLS::ESkills e)
{
return getSpecialWord(SKILLS::toString(e));
}
// ***************************************************************************
const char *CStringManagerClient::getItemLocalizedName(CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getCreatureLocalizedName(NLMISC::CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSBrickLocalizedName(NLMISC::CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSPhraseLocalizedName(NLMISC::CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
/*const char *CStringManagerClient::getBrickLocalizedDescription(BRICK_FAMILIES::TBrickFamily e)
{
return getSpecialDesc(BRICK_FAMILIES::toString(e));
}
*/
// ***************************************************************************
const char *CStringManagerClient::getSkillLocalizedDescription(SKILLS::ESkills e)
{
return getSpecialDesc(SKILLS::toString(e));
}
// ***************************************************************************
const char *CStringManagerClient::getItemLocalizedDescription(CSheetId id)
{
return getSpecialDesc(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSBrickLocalizedDescription(NLMISC::CSheetId id)
{
return getSpecialDesc(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSBrickLocalizedCompositionDescription(NLMISC::CSheetId id)
{
return getSpecialDesc2(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSPhraseLocalizedDescription(NLMISC::CSheetId id)
{
return getSpecialDesc(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getTitleLocalizedName(const string &titleId, bool women)
{
vector listInfos = getTitleInfos(titleId, women);
if (!listInfos.empty())
{
_TitleWords.push_back(listInfos[0]);
return getLocalizedName(_TitleWords.back());
}
return getLocalizedName(titleId);
}
const ucchar *CStringManagerClient::getLocalizedName(const ucstring &uctext)
{
string text = uctext.toUtf8();
if (text[0] == '[')
{
vector textLocalizations;
static ucstring defaultText;
splitString(text.substr(1), "[", textLocalizations);
if (!textLocalizations.empty())
{
for(uint i = 0; i CStringManagerClient::getTitleInfos(const string &titleId, bool women)
{
vector listInfos;
splitString(titleId, string("#"), listInfos);
if (!listInfos.empty())
{
if (titleId[0] != '#')
{
listInfos[0] = getSpecialWord(listInfos[0], women);
}
}
return listInfos;
}
// ***************************************************************************
const char *CStringManagerClient::getClassificationTypeLocalizedName(EGSPD::CClassificationType::TClassificationType type)
{
return getSpecialDesc(EGSPD::CClassificationType::toString(type));
}
// ***************************************************************************
const char *CStringManagerClient::getOutpostLocalizedName(NLMISC::CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getOutpostLocalizedDescription(NLMISC::CSheetId id)
{
return getSpecialDesc(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getOutpostBuildingLocalizedName(NLMISC::CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getOutpostBuildingLocalizedDescription(NLMISC::CSheetId id)
{
return getSpecialDesc(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSquadLocalizedName(NLMISC::CSheetId id)
{
return getSpecialWord(id.toString());
}
// ***************************************************************************
const char *CStringManagerClient::getSquadLocalizedDescription(NLMISC::CSheetId id)
{
return getSpecialDesc(id.toString());
}
// ***************************************************************************
void CStringManagerClient::replaceDynString(const std::string &name, const std::string &text)
{
_DynStrings[name] = text;
}
// ***************************************************************************
void CStringManagerClient::replaceSBrickName(NLMISC::CSheetId id, const std::string &name, const std::string &desc, const std::string &desc2)
{
string label= id.toString();
if (label.empty())
{
return;
}
// avoid case problems
static string lwrLabel;
lwrLabel = toLower(label);
nlassert(!_SpecItem_MemoryCompressed); // Not allowed, strings are released!
if (_SpecItem_MemoryCompressed)
{
#if 0
const char *strName = name.c_str();
const char *strDesc = desc.c_str();
const char *strDesc2 = desc2.c_str();
CItemLight tmp;
tmp.Label = lwrLabel.c_str();
vector::iterator it = lower_bound(_SpecItems.begin(), _SpecItems.end(), tmp, CItemLightComp());
if (it != _SpecItems.end())
{
if (strcmp(it->Label, lwrLabel.c_str()) == 0)
{
it->Name = strName;
it->Desc = strDesc;
it->Desc2 = strDesc2;
}
else
{
it->Label = tmp.Label;
it->Name = strName;
it->Desc = strDesc;
it->Desc2 = strDesc2;
}
}
else
{
tmp.Name = strName;
tmp.Desc = strDesc;
tmp.Desc2 = strDesc2;
_SpecItems.push_back(tmp);
}
#endif
}
else
{
map::iterator it(_SpecItem_TempMap.find(lwrLabel));
if (it != _SpecItem_TempMap.end())
{
it->second.Name= name;
it->second.Desc= desc;
it->second.Desc2= desc2;
}
else
{
CItem newItem;
newItem.Name = name;
newItem.Desc = desc;
newItem.Desc2 = desc2;
_SpecItem_TempMap.insert(pair(lwrLabel,newItem));
}
}
}
} // namespace STRING_MANAGER