// Ryzom - MMORPG Framework // Copyright (C) 2010-2017 Winch Gate Property Limited // // This source file has been modified by the following contributors: // Copyright (C) 2020 Jan BOON (Kaetemi) // // 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) && CFile::getFileSize(_CacheFilename)) { // there is a cache file, check date reset it if needed { NLMISC::CIFile file(_CacheFilename); file.setVersionException(false, false); file.serialVersion(currentVersion); if (file.getPos() + sizeof(_Timestamp) > file.getFileSize()) _Timestamp = ~timestamp; else 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); } // if the string contains a special rename of creature, remove it if (str.size() > 2 && str[0] == '<' && str[1] == '#') { str = toUpper(str[2])+str.substr(3); } // 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) // TODO: UTF-8 (serial) { 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; // TODO: UTF-8 (serial) } 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; // TODO: UTF-8 (serial) } 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) // TODO: UTF-8 (serial) { 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); // TODO: UTF-8 (serial) _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); // TODO: UTF-8 (serial) _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); // TODO: UTF-8 (serial) _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 = toLowerAscii(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 = toLowerAscii(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 = toLowerAscii(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 char *CStringManagerClient::getLocalizedName(const string &uctext) { string text = uctext; if (text[0] == '[') { vector textLocalizations; static string 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 = toLowerAscii(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