LocalizedStringManager.cpp 96 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "CrySystem_precompiled.h"
  9. #include "LocalizedStringManager.h"
  10. #if defined(AZ_RESTRICTED_PLATFORM)
  11. #undef AZ_RESTRICTED_SECTION
  12. #define LOCALIZEDSTRINGMANAGER_CPP_SECTION_1 1
  13. #endif
  14. #include <ISystem.h>
  15. #include "System.h" // to access InitLocalization()
  16. #include <CryPath.h>
  17. #include <IConsole.h>
  18. #include <IFont.h>
  19. #include <locale.h>
  20. #include <time.h>
  21. #include <AzCore/std/string/conversions.h>
  22. #include <AzFramework/StringFunc/StringFunc.h>
  23. #include <AzCore/std/string/conversions.h>
  24. #include <AzCore/Serialization/Locale.h>
  25. #include <AzCore/Math/Crc.h>
  26. #define MAX_CELL_COUNT 32
  27. // CVAR names
  28. #if !defined(_RELEASE)
  29. const char c_sys_localization_debug[] = "sys_localization_debug";
  30. const char c_sys_localization_encode[] = "sys_localization_encode";
  31. #endif // !defined(_RELEASE)
  32. #define LOC_WINDOW "Localization"
  33. const char* c_sys_localization_format = "sys_localization_format";
  34. AZ_CVAR(int32_t, sys_localization_format, 0, nullptr, AZ::ConsoleFunctorFlags::Null,
  35. "Usage: sys_localization_format [0..1]\n"
  36. " 0: O3DE Legacy Localization (Excel 2003)\n"
  37. " 1: AGS XML\n"
  38. "Default is 1 (AGS Xml)");
  39. enum ELocalizedXmlColumns
  40. {
  41. ELOCALIZED_COLUMN_SKIP = 0,
  42. ELOCALIZED_COLUMN_KEY,
  43. ELOCALIZED_COLUMN_AUDIOFILE,
  44. ELOCALIZED_COLUMN_CHARACTER_NAME,
  45. ELOCALIZED_COLUMN_SUBTITLE_TEXT,
  46. ELOCALIZED_COLUMN_ACTOR_LINE,
  47. ELOCALIZED_COLUMN_USE_SUBTITLE,
  48. ELOCALIZED_COLUMN_VOLUME,
  49. ELOCALIZED_COLUMN_SOUNDEVENT,
  50. ELOCALIZED_COLUMN_RADIO_RATIO,
  51. ELOCALIZED_COLUMN_EVENTPARAMETER,
  52. ELOCALIZED_COLUMN_SOUNDMOOD,
  53. ELOCALIZED_COLUMN_IS_DIRECT_RADIO,
  54. // legacy names
  55. ELOCALIZED_COLUMN_LEGACY_PERSON,
  56. ELOCALIZED_COLUMN_LEGACY_CHARACTERNAME,
  57. ELOCALIZED_COLUMN_LEGACY_TRANSLATED_CHARACTERNAME,
  58. ELOCALIZED_COLUMN_LEGACY_ENGLISH_DIALOGUE,
  59. ELOCALIZED_COLUMN_LEGACY_TRANSLATION,
  60. ELOCALIZED_COLUMN_LEGACY_YOUR_TRANSLATION,
  61. ELOCALIZED_COLUMN_LEGACY_ENGLISH_SUBTITLE,
  62. ELOCALIZED_COLUMN_LEGACY_TRANSLATED_SUBTITLE,
  63. ELOCALIZED_COLUMN_LEGACY_ORIGINAL_CHARACTER_NAME,
  64. ELOCALIZED_COLUMN_LEGACY_TRANSLATED_CHARACTER_NAME,
  65. ELOCALIZED_COLUMN_LEGACY_ORIGINAL_TEXT,
  66. ELOCALIZED_COLUMN_LEGACY_TRANSLATED_TEXT,
  67. ELOCALIZED_COLUMN_LEGACY_ORIGINAL_ACTOR_LINE,
  68. ELOCALIZED_COLUMN_LEGACY_TRANSLATED_ACTOR_LINE,
  69. ELOCALIZED_COLUMN_LAST,
  70. };
  71. // The order must match to the order of the ELocalizedXmlColumns
  72. static const char* sLocalizedColumnNames[] =
  73. {
  74. // everyhing read by the file will be convert to lower cases
  75. "skip",
  76. "key",
  77. "audio_filename",
  78. "character name",
  79. "subtitle text",
  80. "actor line",
  81. "use subtitle",
  82. "volume",
  83. "prototype event",
  84. "radio ratio",
  85. "eventparameter",
  86. "soundmood",
  87. "is direct radio",
  88. // legacy names
  89. "person",
  90. "character name",
  91. "translated character name",
  92. "english dialogue",
  93. "translation",
  94. "your translation",
  95. "english subtitle",
  96. "translated subtitle",
  97. "original character name",
  98. "translated character name",
  99. "original text",
  100. "translated text",
  101. "original actor line",
  102. "translated actor line",
  103. };
  104. //Please ensure that this array matches the contents of EPlatformIndependentLanguageID in ILocalizationManager.h
  105. static const char* PLATFORM_INDEPENDENT_LANGUAGE_NAMES[ ILocalizationManager::ePILID_MAX_OR_INVALID ] =
  106. {
  107. "en-US", // English (USA)
  108. "en-GB", // English (UK)
  109. "de-DE", // German
  110. "ru-RU", // Russian (Russia)
  111. "pl-PL", // Polish
  112. "tr-TR", // Turkish
  113. "es-ES", // Spanish (Spain)
  114. "es-MX", // Spanish (Mexico)
  115. "fr-FR", // French (France)
  116. "fr-CA", // French (Canada)
  117. "it-IT", // Italian
  118. "pt-PT", // Portugese (Portugal)
  119. "pt-BR", // Portugese (Brazil)
  120. "ja-JP", // Japanese
  121. "ko-KR", // Korean
  122. "zh-CHT", // Traditional Chinese
  123. "zh-CHS", // Simplified Chinese
  124. "nl-NL", // Dutch (The Netherlands)
  125. "fi-FI", // Finnish
  126. "sv-SE", // Swedish
  127. "cs-CZ", // Czech
  128. "no-NO", // Norwegian
  129. "ar-SA", // Arabic (Saudi Arabia)
  130. "da-DK" // Danish (Denmark)
  131. };
  132. #if defined(WIN32) || defined(WIN64)
  133. namespace
  134. {
  135. #if defined(WIN32)
  136. time_t gmt_to_local_win32(void)
  137. {
  138. TIME_ZONE_INFORMATION tzinfo;
  139. DWORD dwStandardDaylight;
  140. long bias;
  141. dwStandardDaylight = GetTimeZoneInformation(&tzinfo);
  142. bias = tzinfo.Bias;
  143. if (dwStandardDaylight == TIME_ZONE_ID_STANDARD)
  144. {
  145. bias += tzinfo.StandardBias;
  146. }
  147. if (dwStandardDaylight == TIME_ZONE_ID_DAYLIGHT)
  148. {
  149. bias += tzinfo.DaylightBias;
  150. }
  151. return (-bias * 60);
  152. }
  153. #endif // #if defined(WIN32)
  154. time_t DateToSecondsUTC(struct tm& inDate)
  155. {
  156. #if defined(WIN32)
  157. return mktime(&inDate) + gmt_to_local_win32();
  158. #else
  159. return mktime(&inDate);
  160. #endif // #if defined(WIN32)
  161. }
  162. }
  163. #endif // #if defined(WIN32) || defined(WIN64)
  164. //////////////////////////////////////////////////////////////////////////
  165. #if !defined(_RELEASE)
  166. static void ReloadDialogData([[maybe_unused]] IConsoleCmdArgs* pArgs)
  167. {
  168. LocalizationManagerRequestBus::Broadcast(&LocalizationManagerRequestBus::Events::ReloadData);
  169. //CSystem *pSystem = (CSystem*) gEnv->pSystem;
  170. //pSystem->InitLocalization();
  171. //pSystem->OpenBasicPaks();
  172. }
  173. #endif //#if !defined(_RELEASE)
  174. //////////////////////////////////////////////////////////////////////////
  175. #if !defined(_RELEASE)
  176. static void TestFormatMessage ([[maybe_unused]] IConsoleCmdArgs* pArgs)
  177. {
  178. AZStd::string fmt1 ("abc %1 def % gh%2i %");
  179. AZStd::string fmt2 ("abc %[action:abc] %2 def % gh%1i %1");
  180. AZStd::string out1, out2;
  181. LocalizationManagerRequestBus::Broadcast(&LocalizationManagerRequestBus::Events::FormatStringMessage, out1, fmt1, "first", "second", "third", nullptr);
  182. CryLogAlways("%s", out1.c_str());
  183. LocalizationManagerRequestBus::Broadcast(&LocalizationManagerRequestBus::Events::FormatStringMessage, out2, fmt2, "second", nullptr, nullptr, nullptr);
  184. CryLogAlways("%s", out2.c_str());
  185. }
  186. #endif //#if !defined(_RELEASE)
  187. //////////////////////////////////////////////////////////////////////
  188. // Construction/Destruction
  189. //////////////////////////////////////////////////////////////////////
  190. //////////////////////////////////////////////////////////////////////
  191. CLocalizedStringsManager::CLocalizedStringsManager(ISystem* pSystem)
  192. : m_cvarLocalizationDebug(0)
  193. , m_cvarLocalizationEncode(1)
  194. , m_availableLocalizations(0)
  195. {
  196. m_pSystem = pSystem;
  197. m_pSystem->GetISystemEventDispatcher()->RegisterListener(this);
  198. m_languages.reserve(4);
  199. m_pLanguage = 0;
  200. #if !defined(_RELEASE)
  201. m_haveWarnedAboutAtLeastOneLabel = false;
  202. REGISTER_COMMAND("ReloadDialogData", ReloadDialogData, VF_NULL,
  203. "Reloads all localization dependent XML sheets for the currently set language.");
  204. REGISTER_COMMAND("_TestFormatMessage", TestFormatMessage, VF_NULL, "");
  205. REGISTER_CVAR2(c_sys_localization_debug, &m_cvarLocalizationDebug, m_cvarLocalizationDebug, VF_CHEAT,
  206. "Toggles debugging of the Localization Manager.\n"
  207. "Usage: sys_localization_debug [0..3]\n"
  208. "1: outputs warnings\n"
  209. "2: outputs extended information and warnings\n"
  210. "3: outputs CRC32 hashes and strings to help detect clashes\n"
  211. "Default is 0 (off).");
  212. REGISTER_CVAR2(c_sys_localization_encode, &m_cvarLocalizationEncode, m_cvarLocalizationEncode, VF_REQUIRE_APP_RESTART,
  213. "Toggles encoding of translated text to save memory. REQUIRES RESTART.\n"
  214. "Usage: sys_localization_encode [0..1]\n"
  215. "0: No encoding, store as wide strings\n"
  216. "1: Huffman encode translated text, saves approx 30% with a small runtime performance cost\n"
  217. "Default is 1.");
  218. #endif //#if !defined(_RELEASE)
  219. //Check that someone hasn't added a language ID without a language name
  220. assert(PLATFORM_INDEPENDENT_LANGUAGE_NAMES[ ILocalizationManager::ePILID_MAX_OR_INVALID - 1 ] != 0);
  221. // Populate available languages by scanning the localization directory for paks
  222. // Default to US English if language is not supported
  223. AZStd::string sPath;
  224. const AZStd::string sLocalizationFolder(PathUtil::GetLocalizationFolder());
  225. ILocalizationManager::TLocalizationBitfield availableLanguages = 0;
  226. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  227. // test language name against supported languages
  228. for (int i = 0; i < ILocalizationManager::ePILID_MAX_OR_INVALID; i++)
  229. {
  230. AZStd::string sCurrentLanguage = LangNameFromPILID((ILocalizationManager::EPlatformIndependentLanguageID)i);
  231. sPath = sLocalizationFolder.c_str() + sCurrentLanguage;
  232. AZStd::to_lower(sPath.begin(), sPath.end());
  233. if (fileIO && fileIO->IsDirectory(sPath.c_str()))
  234. {
  235. availableLanguages |= ILocalizationManager::LocalizationBitfieldFromPILID((ILocalizationManager::EPlatformIndependentLanguageID)i);
  236. if (m_cvarLocalizationDebug >= 2)
  237. {
  238. AZ_TracePrintf("Localization", "Detected language support for %s (id %d)", sCurrentLanguage.c_str(), i);
  239. }
  240. }
  241. }
  242. int32_t localizationFormat{};
  243. if (auto console = AZ::Interface<AZ::IConsole>::Get();
  244. console !=nullptr)
  245. {
  246. console->GetCvarValue(c_sys_localization_format, localizationFormat);
  247. }
  248. AZ_Warning("Localization", !(localizationFormat == 0 && availableLanguages == 0 && ProjectUsesLocalization()), "No localization files found!");
  249. SetAvailableLocalizationsBitfield(availableLanguages);
  250. LocalizationManagerRequestBus::Handler::BusConnect();
  251. }
  252. //////////////////////////////////////////////////////////////////////
  253. CLocalizedStringsManager::~CLocalizedStringsManager()
  254. {
  255. FreeData();
  256. LocalizationManagerRequestBus::Handler::BusDisconnect();
  257. }
  258. //////////////////////////////////////////////////////////////////////
  259. void CLocalizedStringsManager::GetLoadedTags(TLocalizationTagVec& tagVec)
  260. {
  261. TTagFileNames::const_iterator end = m_tagFileNames.end();
  262. for (TTagFileNames::const_iterator it = m_tagFileNames.begin(); it != end; ++it)
  263. {
  264. if (it->second.loaded)
  265. {
  266. tagVec.push_back(it->first);
  267. }
  268. }
  269. }
  270. //////////////////////////////////////////////////////////////////////
  271. void CLocalizedStringsManager::FreeLocalizationData()
  272. {
  273. AutoLock lock(m_cs); //Make sure to lock, as this is a modifying operation
  274. ListAndClearProblemLabels();
  275. for (uint32 i = 0; i < m_languages.size(); i++)
  276. {
  277. if (m_cvarLocalizationEncode == 1)
  278. {
  279. auto pLanguage = m_languages[i];
  280. for (uint8 iEncoder = 0; iEncoder < pLanguage->m_vEncoders.size(); iEncoder++)
  281. {
  282. SAFE_DELETE(pLanguage->m_vEncoders[iEncoder]);
  283. }
  284. }
  285. std::for_each(m_languages[i]->m_vLocalizedStrings.begin(), m_languages[i]->m_vLocalizedStrings.end(), stl::container_object_deleter());
  286. m_languages[i]->m_keysMap.clear();
  287. m_languages[i]->m_vLocalizedStrings.clear();
  288. }
  289. m_loadedTables.clear();
  290. }
  291. //////////////////////////////////////////////////////////////////////
  292. void CLocalizedStringsManager::FreeData()
  293. {
  294. FreeLocalizationData();
  295. for (uint32 i = 0; i < m_languages.size(); i++)
  296. {
  297. delete m_languages[i];
  298. }
  299. m_languages.resize(0);
  300. m_loadedTables.clear();
  301. m_pLanguage = 0;
  302. }
  303. //////////////////////////////////////////////////////////////////////////
  304. const char* CLocalizedStringsManager::LangNameFromPILID(const ILocalizationManager::EPlatformIndependentLanguageID id)
  305. {
  306. assert(id >= 0 && id < ILocalizationManager::ePILID_MAX_OR_INVALID);
  307. return PLATFORM_INDEPENDENT_LANGUAGE_NAMES[ id ];
  308. }
  309. //////////////////////////////////////////////////////////////////////////
  310. ILocalizationManager::EPlatformIndependentLanguageID CLocalizedStringsManager::PILIDFromLangName(AZStd::string langName)
  311. {
  312. for (int i = 0; i < ILocalizationManager::ePILID_MAX_OR_INVALID; i++)
  313. {
  314. if (!_stricmp(langName.c_str(), PLATFORM_INDEPENDENT_LANGUAGE_NAMES[i]))
  315. {
  316. return (ILocalizationManager::EPlatformIndependentLanguageID)i;
  317. }
  318. }
  319. return ILocalizationManager::ePILID_MAX_OR_INVALID;
  320. }
  321. #if defined(AZ_RESTRICTED_PLATFORM)
  322. #define AZ_RESTRICTED_SECTION LOCALIZEDSTRINGMANAGER_CPP_SECTION_1
  323. #include AZ_RESTRICTED_FILE(LocalizedStringManager_cpp)
  324. #if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED)
  325. #undef AZ_RESTRICTED_SECTION_IMPLEMENTED
  326. #endif // AZ_RESTRICTED_SECTION_IMPLEMENTED
  327. #else
  328. //////////////////////////////////////////////////////////////////////////
  329. ILocalizationManager::EPlatformIndependentLanguageID CLocalizedStringsManager::GetSystemLanguage()
  330. {
  331. return ILocalizationManager::EPlatformIndependentLanguageID::ePILID_English_US;
  332. }
  333. #endif // defined(AZ_RESTRICTED_PLATFORM)
  334. //Uses bitwise operations to compare the localizations we provide in this SKU and the languages that the platform supports.
  335. //Returns !0 if we provide more localizations than are available as system languages
  336. ILocalizationManager::TLocalizationBitfield CLocalizedStringsManager::MaskSystemLanguagesFromSupportedLocalizations(const ILocalizationManager::TLocalizationBitfield systemLanguages)
  337. {
  338. return (~systemLanguages) & m_availableLocalizations;
  339. }
  340. //Returns !0 if the language is supported.
  341. ILocalizationManager::TLocalizationBitfield CLocalizedStringsManager::IsLanguageSupported(const ILocalizationManager::EPlatformIndependentLanguageID id)
  342. {
  343. return m_availableLocalizations & (1 << id);
  344. }
  345. //////////////////////////////////////////////////////////////////////////
  346. void CLocalizedStringsManager::SetAvailableLocalizationsBitfield(const ILocalizationManager::TLocalizationBitfield availableLocalizations)
  347. {
  348. m_availableLocalizations = availableLocalizations;
  349. }
  350. //////////////////////////////////////////////////////////////////////
  351. const char* CLocalizedStringsManager::GetLanguage()
  352. {
  353. if (m_pLanguage == 0)
  354. {
  355. return "";
  356. }
  357. return m_pLanguage->sLanguage.c_str();
  358. }
  359. //////////////////////////////////////////////////////////////////////
  360. bool CLocalizedStringsManager::SetLanguage(const char* sLanguage)
  361. {
  362. if (m_cvarLocalizationDebug >= 2)
  363. {
  364. CryLog("<Localization> Set language to %s", sLanguage);
  365. }
  366. // Check if already language loaded.
  367. for (uint32 i = 0; i < m_languages.size(); i++)
  368. {
  369. if (_stricmp(sLanguage, m_languages[i]->sLanguage.c_str()) == 0)
  370. {
  371. InternalSetCurrentLanguage(m_languages[i]);
  372. return true;
  373. }
  374. }
  375. SLanguage* pLanguage = new SLanguage;
  376. m_languages.push_back(pLanguage);
  377. if (m_cvarLocalizationDebug >= 2)
  378. {
  379. CryLog("<Localization> Insert new language to %s", sLanguage);
  380. }
  381. pLanguage->sLanguage = sLanguage;
  382. InternalSetCurrentLanguage(pLanguage);
  383. //-------------------------------------------------------------------------------------------------
  384. // input localization
  385. //-------------------------------------------------------------------------------------------------
  386. // keyboard
  387. for (int i = 0; i <= 0x80; i++)
  388. {
  389. AddControl(i);
  390. }
  391. // mouse
  392. for (int i = 1; i <= 0x0f; i++)
  393. {
  394. AddControl(i * 0x10000);
  395. }
  396. return (true);
  397. }
  398. //////////////////////////////////////////////////////////////////////////
  399. int CLocalizedStringsManager::GetLocalizationFormat() const
  400. {
  401. int32_t localizationFormat{};
  402. if (auto console = AZ::Interface<AZ::IConsole>::Get();
  403. console !=nullptr)
  404. {
  405. console->GetCvarValue(c_sys_localization_format, localizationFormat);
  406. }
  407. return localizationFormat;
  408. }
  409. //////////////////////////////////////////////////////////////////////////
  410. AZStd::string CLocalizedStringsManager::GetLocalizedSubtitleFilePath(const AZStd::string& localVideoPath, const AZStd::string& subtitleFileExtension) const
  411. {
  412. AZStd::string sLocalizationFolder(PathUtil::GetLocalizationFolder().c_str());
  413. size_t backSlashIdx = sLocalizationFolder.find_first_of("\\", 0);
  414. if (backSlashIdx != AZStd::string::npos)
  415. {
  416. sLocalizationFolder.replace(backSlashIdx, 2, "/");
  417. }
  418. AZStd::string filePath(m_pLanguage->sLanguage.c_str());
  419. filePath = sLocalizationFolder.c_str() + filePath + "/" + localVideoPath;
  420. return filePath.substr(0, filePath.find_last_of('.')).append(subtitleFileExtension);
  421. }
  422. //////////////////////////////////////////////////////////////////////////
  423. AZStd::string CLocalizedStringsManager::GetLocalizedLocXMLFilePath(const AZStd::string & localXmlPath) const
  424. {
  425. AZStd::string sLocalizationFolder(PathUtil::GetLocalizationFolder().c_str());
  426. size_t backSlashIdx = sLocalizationFolder.find_first_of("\\", 0);
  427. if (backSlashIdx != AZStd::string::npos)
  428. {
  429. sLocalizationFolder.replace(backSlashIdx, 2, "/");
  430. }
  431. const AZStd::string& filePath = AZStd::string::format("%s%s/%s", sLocalizationFolder.c_str(), m_pLanguage->sLanguage.c_str(), localXmlPath.c_str());
  432. return filePath.substr(0, filePath.find_last_of('.')).append(".loc.xml");
  433. }
  434. //////////////////////////////////////////////////////////////////////////
  435. void CLocalizedStringsManager::AddControl([[maybe_unused]] int nKey)
  436. {
  437. }
  438. //////////////////////////////////////////////////////////////////////////
  439. void CLocalizedStringsManager::ParseFirstLine(IXmlTableReader* pXmlTableReader, char* nCellIndexToType, std::map<int, AZStd::string>& SoundMoodIndex, std::map<int, AZStd::string>& EventParameterIndex)
  440. {
  441. AZStd::string sCellContent;
  442. for (;; )
  443. {
  444. int nCellIndex = 0;
  445. const char* pContent = 0;
  446. size_t contentSize = 0;
  447. if (!pXmlTableReader->ReadCell(nCellIndex, pContent, contentSize))
  448. {
  449. break;
  450. }
  451. if (nCellIndex >= MAX_CELL_COUNT)
  452. {
  453. break;
  454. }
  455. if (contentSize <= 0)
  456. {
  457. continue;
  458. }
  459. sCellContent.assign(pContent, contentSize);
  460. AZStd::to_lower(sCellContent.begin(), sCellContent.end());
  461. for (int i = 0; i < sizeof(sLocalizedColumnNames) / sizeof(sLocalizedColumnNames[0]); ++i)
  462. {
  463. const char* pFind = strstr(sCellContent.c_str(), sLocalizedColumnNames[i]);
  464. if (pFind != 0)
  465. {
  466. nCellIndexToType[nCellIndex] = static_cast<char>(i);
  467. // find SoundMood
  468. if (i == ELOCALIZED_COLUMN_SOUNDMOOD)
  469. {
  470. const char* pSoundMoodName = pFind + strlen(sLocalizedColumnNames[i]) + 1;
  471. int nSoundMoodNameLength = static_cast<int>(sCellContent.length() - strlen(sLocalizedColumnNames[i]) - 1);
  472. if (nSoundMoodNameLength > 0)
  473. {
  474. SoundMoodIndex[nCellIndex] = pSoundMoodName;
  475. }
  476. }
  477. // find EventParameter
  478. if (i == ELOCALIZED_COLUMN_EVENTPARAMETER)
  479. {
  480. const char* pParameterName = pFind + strlen(sLocalizedColumnNames[i]) + 1;
  481. int nParameterNameLength = static_cast<int>(sCellContent.length() - strlen(sLocalizedColumnNames[i]) - 1);
  482. if (nParameterNameLength > 0)
  483. {
  484. EventParameterIndex[nCellIndex] = pParameterName;
  485. }
  486. }
  487. break;
  488. }
  489. // HACK until all columns are renamed to "Translation"
  490. //if (_stricmp(sCellContent, "Your Translation") == 0)
  491. //{
  492. // nCellIndexToType[nCellIndex] = ELOCALIZED_COLUMN_TRANSLATED_ACTOR_LINE;
  493. // break;
  494. //}
  495. }
  496. }
  497. }
  498. // copy characters to lower-case 0-terminated buffer
  499. static void CopyLowercase(char* dst, size_t dstSize, const char* src, size_t srcCount)
  500. {
  501. if (dstSize > 0)
  502. {
  503. if (srcCount > dstSize - 1)
  504. {
  505. srcCount = dstSize - 1;
  506. }
  507. while (srcCount--)
  508. {
  509. const char c = *src++;
  510. *dst++ = (c <= 'Z' && c >= 'A') ? c + ('a' - 'A') : c;
  511. }
  512. *dst = '\0';
  513. }
  514. }
  515. //////////////////////////////////////////////////////////////////////////
  516. static void ReplaceEndOfLine(AZStd::fixed_string<CLocalizedStringsManager::LOADING_FIXED_STRING_LENGTH>& s)
  517. {
  518. const AZStd::string oldSubstr("\\n");
  519. const AZStd::string newSubstr(" \n");
  520. size_t pos = 0;
  521. for (;; )
  522. {
  523. pos = s.find(oldSubstr, pos);
  524. if (pos == AZStd::fixed_string<CLocalizedStringsManager::LOADING_FIXED_STRING_LENGTH>::npos)
  525. {
  526. return;
  527. }
  528. s.replace(pos, oldSubstr.length(), newSubstr);
  529. }
  530. }
  531. //////////////////////////////////////////////////////////////////////////
  532. void CLocalizedStringsManager::OnSystemEvent(
  533. ESystemEvent eEvent, [[maybe_unused]] UINT_PTR wparam, [[maybe_unused]] UINT_PTR lparam)
  534. {
  535. // might want to add an event which tells us that we are loading the main menu
  536. // so everything can be unloaded and init files reloaded so safe some memory
  537. switch (eEvent)
  538. {
  539. case ESYSTEM_EVENT_LEVEL_LOAD_START:
  540. {
  541. // This event is here not of interest while we're in the Editor.
  542. if (!gEnv->IsEditor())
  543. {
  544. if (m_cvarLocalizationDebug >= 2)
  545. {
  546. CryLog("<Localization> Loading Requested Tags");
  547. }
  548. for (TStringVec::iterator it = m_tagLoadRequests.begin(); it != m_tagLoadRequests.end(); ++it)
  549. {
  550. LoadLocalizationDataByTag(it->c_str());
  551. }
  552. }
  553. m_tagLoadRequests.clear();
  554. break;
  555. }
  556. case ESYSTEM_EVENT_EDITOR_ON_INIT:
  557. {
  558. // Load all tags after the Editor has finished initialization.
  559. for (TTagFileNames::iterator it = m_tagFileNames.begin(); it != m_tagFileNames.end(); ++it)
  560. {
  561. LoadLocalizationDataByTag(it->first.c_str());
  562. }
  563. break;
  564. }
  565. }
  566. }
  567. //////////////////////////////////////////////////////////////////////////
  568. bool CLocalizedStringsManager::InitLocalizationData(
  569. const char* sFileName, [[maybe_unused]] bool bReload)
  570. {
  571. XmlNodeRef root = m_pSystem->LoadXmlFromFile(sFileName);
  572. if (!root)
  573. {
  574. CryLog("Loading Localization File %s failed!", sFileName);
  575. return false;
  576. }
  577. for (int i = 0; i < root->getChildCount(); i++)
  578. {
  579. XmlNodeRef typeNode = root->getChild(i);
  580. AZStd::string sType = typeNode->getTag();
  581. // tags should be unique
  582. if (m_tagFileNames.find(sType) != m_tagFileNames.end())
  583. {
  584. continue;
  585. }
  586. TStringVec vEntries;
  587. for (int j = 0; j < typeNode->getChildCount(); j++)
  588. {
  589. XmlNodeRef entry = typeNode->getChild(j);
  590. if (!entry->isTag("entry"))
  591. {
  592. continue;
  593. }
  594. vEntries.push_back(entry->getContent());
  595. }
  596. CRY_ASSERT(m_tagFileNames.size() < 255);
  597. uint8 curNumTags = static_cast<uint8>(m_tagFileNames.size());
  598. m_tagFileNames[sType].filenames = vEntries;
  599. m_tagFileNames[sType].id = curNumTags + 1;
  600. m_tagFileNames[sType].loaded = false;
  601. }
  602. return true;
  603. }
  604. //////////////////////////////////////////////////////////////////////////
  605. bool CLocalizedStringsManager::RequestLoadLocalizationDataByTag(const char* sTag)
  606. {
  607. TTagFileNames::iterator it = m_tagFileNames.find(sTag);
  608. if (it == m_tagFileNames.end())
  609. {
  610. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "[LocError] RequestLoadLocalizationDataByTag - Localization tag '%s' not found", sTag);
  611. return false;
  612. }
  613. if (m_cvarLocalizationDebug >= 2)
  614. {
  615. CryLog("<Localization> RequestLoadLocalizationDataByTag %s", sTag);
  616. }
  617. m_tagLoadRequests.push_back(sTag);
  618. return true;
  619. }
  620. //////////////////////////////////////////////////////////////////////////
  621. bool CLocalizedStringsManager::LoadLocalizationDataByTag(
  622. const char* sTag, bool bReload)
  623. {
  624. TTagFileNames::iterator it = m_tagFileNames.find(sTag);
  625. if (it == m_tagFileNames.end())
  626. {
  627. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "[LocError] LoadLocalizationDataByTag - Localization tag '%s' not found", sTag);
  628. return false;
  629. }
  630. if (it->second.loaded)
  631. {
  632. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "[LocError] LoadLocalizationDataByTag - Already loaded tag '%s'", sTag);
  633. return true;
  634. }
  635. bool bResult = true;
  636. stack_string const sLocalizationFolder(PathUtil::GetLocalizationFolder());
  637. int32_t localizationFormat{};
  638. if (auto console = AZ::Interface<AZ::IConsole>::Get();
  639. console !=nullptr)
  640. {
  641. console->GetCvarValue(c_sys_localization_format, localizationFormat);
  642. }
  643. LoadFunc loadFunction = GetLoadFunction();
  644. TStringVec& vEntries = it->second.filenames;
  645. for (TStringVec::iterator it2 = vEntries.begin(); it2 != vEntries.end(); ++it2)
  646. {
  647. //Only load files of the correct type for the configured format
  648. if ((localizationFormat == 0 && strstr(it2->c_str(), ".xml")) || (localizationFormat == 1 && strstr(it2->c_str(), ".agsxml")))
  649. {
  650. bResult &= (this->*loadFunction)(it2->c_str(), it->second.id, bReload);
  651. }
  652. }
  653. if (m_cvarLocalizationDebug >= 2)
  654. {
  655. CryLog("<Localization> LoadLocalizationDataByTag %s with result %d", sTag, bResult);
  656. }
  657. it->second.loaded = true;
  658. return bResult;
  659. }
  660. //////////////////////////////////////////////////////////////////////////
  661. bool CLocalizedStringsManager::ReleaseLocalizationDataByTag(
  662. const char* sTag)
  663. {
  664. INDENT_LOG_DURING_SCOPE(true, "Releasing localization data with the tag '%s'", sTag);
  665. ListAndClearProblemLabels();
  666. TTagFileNames::iterator it = m_tagFileNames.find(sTag);
  667. if (it == m_tagFileNames.end())
  668. {
  669. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "[LocError] ReleaseLocalizationDataByTag - Localization tag '%s' not found", sTag);
  670. return false;
  671. }
  672. if (it->second.loaded == false)
  673. {
  674. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "[LocError] ReleaseLocalizationDataByTag - tag '%s' not loaded", sTag);
  675. return false;
  676. }
  677. const uint8 nTagID = it->second.id;
  678. tmapFilenames newLoadedTables;
  679. for (tmapFilenames::iterator iter = m_loadedTables.begin(); iter != m_loadedTables.end(); iter++)
  680. {
  681. if (iter->second.nTagID != nTagID)
  682. {
  683. newLoadedTables[iter->first] = iter->second;
  684. }
  685. }
  686. m_loadedTables = newLoadedTables;
  687. if (m_pLanguage)
  688. {
  689. //LARGE_INTEGER liStart;
  690. //QueryPerformanceCounter(&liStart);
  691. AutoLock lock(m_cs); //Make sure to lock, as this is a modifying operation
  692. bool bMapEntryErased = false;
  693. //First, remove entries from the map
  694. for (StringsKeyMap::iterator keyMapIt = m_pLanguage->m_keysMap.begin(); keyMapIt != m_pLanguage->m_keysMap.end(); )
  695. {
  696. if (keyMapIt->second->nTagID == nTagID)
  697. {
  698. //VECTORMAP ONLY
  699. keyMapIt = m_pLanguage->m_keysMap.erase(keyMapIt);
  700. bMapEntryErased = true;
  701. }
  702. else
  703. {
  704. keyMapIt++;
  705. }
  706. }
  707. if (bMapEntryErased == true)
  708. {
  709. StringsKeyMap newMap = m_pLanguage->m_keysMap;
  710. m_pLanguage->m_keysMap.clearAndFreeMemory();
  711. m_pLanguage->m_keysMap = newMap;
  712. }
  713. bool bVecEntryErased = false;
  714. //Then remove the entries in the storage vector
  715. const int32 numEntries = static_cast<int32>(m_pLanguage->m_vLocalizedStrings.size());
  716. for (int32 i = numEntries - 1; i >= 0; i--)
  717. {
  718. SLocalizedStringEntry* entry = m_pLanguage->m_vLocalizedStrings[i];
  719. PREFAST_ASSUME(entry);
  720. if (entry->nTagID == nTagID)
  721. {
  722. if (m_cvarLocalizationEncode == 1)
  723. {
  724. if (entry->huffmanTreeIndex != -1)
  725. {
  726. HuffmanCoder* pCoder = m_pLanguage->m_vEncoders[entry->huffmanTreeIndex];
  727. if (pCoder != NULL)
  728. {
  729. pCoder->DecRef();
  730. if (pCoder->RefCount() == 0)
  731. {
  732. if (m_cvarLocalizationDebug >= 2)
  733. {
  734. CryLog("<Localization> Releasing coder %u as it no longer has associated strings", entry->huffmanTreeIndex);
  735. }
  736. //This coding table no longer needed, it has no more associated strings
  737. SAFE_DELETE(m_pLanguage->m_vEncoders[entry->huffmanTreeIndex]);
  738. }
  739. }
  740. }
  741. }
  742. bVecEntryErased = true;
  743. delete(entry);
  744. m_pLanguage->m_vLocalizedStrings.erase(m_pLanguage->m_vLocalizedStrings.begin() + i);
  745. }
  746. }
  747. //Shrink the vector if necessary
  748. if (bVecEntryErased == true)
  749. {
  750. SLanguage::TLocalizedStringEntries newVec = m_pLanguage->m_vLocalizedStrings;
  751. m_pLanguage->m_vLocalizedStrings.clear();
  752. m_pLanguage->m_vLocalizedStrings = newVec;
  753. }
  754. }
  755. if (m_cvarLocalizationDebug >= 2)
  756. {
  757. CryLog("<Localization> ReleaseLocalizationDataByTag %s", sTag);
  758. }
  759. it->second.loaded = false;
  760. return true;
  761. }
  762. //////////////////////////////////////////////////////////////////////////
  763. bool CLocalizedStringsManager::LoadAllLocalizationData(bool bReload)
  764. {
  765. for (TTagFileNames::iterator it = m_tagFileNames.begin(); it != m_tagFileNames.end(); ++it)
  766. {
  767. if(!LoadLocalizationDataByTag(it->first.c_str(), bReload))
  768. return false;
  769. }
  770. return true;
  771. }
  772. //////////////////////////////////////////////////////////////////////////
  773. bool CLocalizedStringsManager::LoadExcelXmlSpreadsheet(const char* sFileName, bool bReload)
  774. {
  775. LoadFunc loadFunction = GetLoadFunction();
  776. return (this->*loadFunction)(sFileName, 0, bReload);
  777. }
  778. enum class YesNoType
  779. {
  780. Yes,
  781. No,
  782. Invalid
  783. };
  784. // parse the yes/no string
  785. /*!
  786. \param szString any of the following strings: yes, enable, true, 1, no, disable, false, 0
  787. \return YesNoType::Yes if szString is yes/enable/true/1, YesNoType::No if szString is no, disable, false, 0 and YesNoType::Invalid if the string is not one of the expected values.
  788. */
  789. inline YesNoType ToYesNoType(const char* szString)
  790. {
  791. if (!_stricmp(szString, "yes")
  792. || !_stricmp(szString, "enable")
  793. || !_stricmp(szString, "true")
  794. || !_stricmp(szString, "1"))
  795. {
  796. return YesNoType::Yes;
  797. }
  798. if (!_stricmp(szString, "no")
  799. || !_stricmp(szString, "disable")
  800. || !_stricmp(szString, "false")
  801. || !_stricmp(szString, "0"))
  802. {
  803. return YesNoType::No;
  804. }
  805. return YesNoType::Invalid;
  806. }
  807. //////////////////////////////////////////////////////////////////////
  808. // Loads a string-table from a Excel XML Spreadsheet file.
  809. bool CLocalizedStringsManager::DoLoadExcelXmlSpreadsheet(const char* sFileName, uint8 nTagID, bool bReload)
  810. {
  811. if (!m_pLanguage)
  812. {
  813. return false;
  814. }
  815. //check if this table has already been loaded
  816. if (!bReload)
  817. {
  818. if (m_loadedTables.find(AZStd::string(sFileName)) != m_loadedTables.end())
  819. {
  820. return (true);
  821. }
  822. }
  823. // Use the invariant culture while loading files from disk
  824. AZ::Locale::ScopedSerializationLocale scopedLocale;
  825. ListAndClearProblemLabels();
  826. IXmlTableReader* const pXmlTableReader = m_pSystem->GetXmlUtils()->CreateXmlTableReader();
  827. if (!pXmlTableReader)
  828. {
  829. CryLog("Loading Localization File %s failed (XML system failure)!", sFileName);
  830. return false;
  831. }
  832. XmlNodeRef root;
  833. AZStd::string sPath;
  834. {
  835. const AZStd::string sLocalizationFolder(PathUtil::GetLocalizationRoot());
  836. const AZStd::string& languageFolder = m_pLanguage->sLanguage;
  837. sPath = sLocalizationFolder.c_str() + languageFolder + PathUtil::GetSlash() + sFileName;
  838. root = m_pSystem->LoadXmlFromFile(sPath.c_str());
  839. if (!root)
  840. {
  841. CryLog("Loading Localization File %s failed!", sPath.c_str());
  842. pXmlTableReader->Release();
  843. return false;
  844. }
  845. }
  846. // bug search, re-export to a file to compare it
  847. //string sReExport = sFileName;
  848. //sReExport += ".re";
  849. //root->saveToFile(sReExport.c_str());
  850. CryLog("Loading Localization File %s", sFileName);
  851. INDENT_LOG_DURING_SCOPE();
  852. //Create a huffman coding table for these strings - if they're going to be encoded or compressed
  853. HuffmanCoder* pEncoder = NULL;
  854. uint8 iEncoder = 0;
  855. size_t startOfStringsToCompress = 0;
  856. if (m_cvarLocalizationEncode == 1)
  857. {
  858. {
  859. for (iEncoder = 0; iEncoder < m_pLanguage->m_vEncoders.size(); iEncoder++)
  860. {
  861. if (m_pLanguage->m_vEncoders[iEncoder] == NULL)
  862. {
  863. m_pLanguage->m_vEncoders[iEncoder] = pEncoder = new HuffmanCoder();
  864. break;
  865. }
  866. }
  867. if (iEncoder == m_pLanguage->m_vEncoders.size())
  868. {
  869. pEncoder = new HuffmanCoder();
  870. m_pLanguage->m_vEncoders.push_back(pEncoder);
  871. }
  872. //Make a note of the current end of the loc strings array, as encoding is done in two passes.
  873. //One pass to build the code table, another to apply it
  874. pEncoder->Init();
  875. }
  876. startOfStringsToCompress = m_pLanguage->m_vLocalizedStrings.size();
  877. }
  878. {
  879. if (!pXmlTableReader->Begin(root))
  880. {
  881. CryLog("Loading Localization File %s failed! The file is in an unsupported format.", sPath.c_str());
  882. pXmlTableReader->Release();
  883. return false;
  884. }
  885. }
  886. int rowCount = pXmlTableReader->GetEstimatedRowCount();
  887. {
  888. AutoLock lock(m_cs); //Make sure to lock, as this is a modifying operation
  889. m_pLanguage->m_vLocalizedStrings.reserve(m_pLanguage->m_vLocalizedStrings.size() + rowCount);
  890. }
  891. {
  892. AutoLock lock(m_cs); //Make sure to lock, as this is a modifying operation
  893. //VectorMap only, not applicable to std::map
  894. m_pLanguage->m_keysMap.reserve(m_pLanguage->m_keysMap.size() + rowCount);
  895. }
  896. {
  897. pairFileName sNewFile;
  898. sNewFile.first = sFileName;
  899. sNewFile.second.bDataStripping = false; // this is off for now
  900. sNewFile.second.nTagID = nTagID;
  901. m_loadedTables.insert(sNewFile);
  902. }
  903. // Cell Index
  904. char nCellIndexToType[MAX_CELL_COUNT];
  905. memset(nCellIndexToType, 0, sizeof(nCellIndexToType));
  906. // SoundMood Index
  907. std::map<int, AZStd::string> SoundMoodIndex;
  908. // EventParameter Index
  909. std::map<int, AZStd::string> EventParameterIndex;
  910. bool bFirstRow = true;
  911. AZStd::fixed_string<LOADING_FIXED_STRING_LENGTH> sTmp;
  912. // lower case event name
  913. char szLowerCaseEvent[128];
  914. // lower case key
  915. char szLowerCaseKey[1024];
  916. // key CRC
  917. uint32 keyCRC;
  918. for (;; )
  919. {
  920. int nRowIndex = -1;
  921. {
  922. if (!pXmlTableReader->ReadRow(nRowIndex))
  923. {
  924. break;
  925. }
  926. }
  927. if (bFirstRow)
  928. {
  929. bFirstRow = false;
  930. ParseFirstLine(pXmlTableReader, nCellIndexToType, SoundMoodIndex, EventParameterIndex);
  931. // Skip first row, it contains description only.
  932. continue;
  933. }
  934. bool bValidKey = false;
  935. bool bValidTranslatedText = false;
  936. bool bValidTranslatedCharacterName = false;
  937. bool bValidTranslatedActorLine = false;
  938. bool bUseSubtitle = true;
  939. bool bIsDirectRadio = false;
  940. bool bIsIntercepted = false;
  941. struct CConstCharArray
  942. {
  943. const char* ptr;
  944. size_t count;
  945. CConstCharArray()
  946. {
  947. clear();
  948. }
  949. void clear()
  950. {
  951. ptr = "";
  952. count = 0;
  953. }
  954. bool empty() const
  955. {
  956. return count == 0;
  957. }
  958. };
  959. CConstCharArray sKeyString;
  960. CConstCharArray sCharacterName;
  961. CConstCharArray sTranslatedCharacterName; // Legacy, to be removed some day...
  962. CConstCharArray sSubtitleText;
  963. CConstCharArray sTranslatedText; // Legacy, to be removed some day...
  964. CConstCharArray sActorLine;
  965. CConstCharArray sTranslatedActorLine; // Legacy, to be removed some day...
  966. CConstCharArray sSoundEvent;
  967. float fVolume = 1.0f;
  968. float fRadioRatio = 1.0f;
  969. float fEventParameterValue = 0.0f;
  970. float fSoundMoodValue = 0.0f;
  971. int nItems = 0;
  972. std::map<int, float> SoundMoodValues;
  973. std::map<int, float> EventParameterValues;
  974. for (;; )
  975. {
  976. int nCellIndex = -1;
  977. CConstCharArray cell;
  978. {
  979. if (!pXmlTableReader->ReadCell(nCellIndex, cell.ptr, cell.count))
  980. {
  981. break;
  982. }
  983. }
  984. if (nCellIndex >= MAX_CELL_COUNT)
  985. {
  986. break;
  987. }
  988. // skip empty cells
  989. if (cell.count <= 0)
  990. {
  991. continue;
  992. }
  993. const char nCellType = nCellIndexToType[nCellIndex];
  994. switch (nCellType)
  995. {
  996. case ELOCALIZED_COLUMN_SKIP:
  997. break;
  998. case ELOCALIZED_COLUMN_KEY:
  999. sKeyString = cell;
  1000. bValidKey = true;
  1001. ++nItems;
  1002. break;
  1003. case ELOCALIZED_COLUMN_AUDIOFILE:
  1004. sKeyString = cell;
  1005. bValidKey = true;
  1006. ++nItems;
  1007. break;
  1008. case ELOCALIZED_COLUMN_CHARACTER_NAME:
  1009. sCharacterName = cell;
  1010. ++nItems;
  1011. break;
  1012. case ELOCALIZED_COLUMN_SUBTITLE_TEXT:
  1013. sSubtitleText = cell;
  1014. ++nItems;
  1015. break;
  1016. case ELOCALIZED_COLUMN_ACTOR_LINE:
  1017. sActorLine = cell;
  1018. ++nItems;
  1019. break;
  1020. case ELOCALIZED_COLUMN_USE_SUBTITLE:
  1021. sTmp.assign(cell.ptr, cell.count);
  1022. bUseSubtitle = ToYesNoType(sTmp.c_str()) == YesNoType::No ? false : true; // favor yes (yes and invalid -> yes)
  1023. break;
  1024. case ELOCALIZED_COLUMN_VOLUME:
  1025. sTmp.assign(cell.ptr, cell.count);
  1026. fVolume = (float)atof(sTmp.c_str());
  1027. ++nItems;
  1028. break;
  1029. case ELOCALIZED_COLUMN_SOUNDEVENT:
  1030. sSoundEvent = cell;
  1031. ++nItems;
  1032. break;
  1033. case ELOCALIZED_COLUMN_RADIO_RATIO:
  1034. sTmp.assign(cell.ptr, cell.count);
  1035. fRadioRatio = (float)atof(sTmp.c_str());
  1036. ++nItems;
  1037. break;
  1038. case ELOCALIZED_COLUMN_EVENTPARAMETER:
  1039. sTmp.assign(cell.ptr, cell.count);
  1040. fEventParameterValue = (float)atof(sTmp.c_str());
  1041. {
  1042. EventParameterValues[nCellIndex] = fEventParameterValue;
  1043. }
  1044. ++nItems;
  1045. break;
  1046. case ELOCALIZED_COLUMN_SOUNDMOOD:
  1047. sTmp.assign(cell.ptr, cell.count);
  1048. fSoundMoodValue = (float)atof(sTmp.c_str());
  1049. {
  1050. SoundMoodValues[nCellIndex] = fSoundMoodValue;
  1051. }
  1052. ++nItems;
  1053. break;
  1054. case ELOCALIZED_COLUMN_IS_DIRECT_RADIO:
  1055. sTmp.assign(cell.ptr, cell.count);
  1056. if (!_stricmp(sTmp.c_str(), "intercept"))
  1057. {
  1058. bIsIntercepted = true;
  1059. }
  1060. bIsDirectRadio = bIsIntercepted || (ToYesNoType(sTmp.c_str()) == YesNoType::Yes ? true : false); // favor no (no and invalid -> no)
  1061. ++nItems;
  1062. break;
  1063. // legacy names
  1064. case ELOCALIZED_COLUMN_LEGACY_PERSON:
  1065. // old file often only have content in this column
  1066. if (!cell.empty())
  1067. {
  1068. sCharacterName = cell;
  1069. sTranslatedCharacterName = cell;
  1070. bValidTranslatedCharacterName = true;
  1071. }
  1072. ++nItems;
  1073. break;
  1074. case ELOCALIZED_COLUMN_LEGACY_CHARACTERNAME:
  1075. sCharacterName = cell;
  1076. sTranslatedCharacterName = cell;
  1077. bValidTranslatedCharacterName = true;
  1078. ++nItems;
  1079. break;
  1080. case ELOCALIZED_COLUMN_LEGACY_TRANSLATED_CHARACTERNAME:
  1081. sTranslatedCharacterName = cell;
  1082. bValidTranslatedCharacterName = true;
  1083. ++nItems;
  1084. break;
  1085. case ELOCALIZED_COLUMN_LEGACY_ENGLISH_DIALOGUE:
  1086. // old file often only have content in this column
  1087. sActorLine = cell;
  1088. sSubtitleText = cell;
  1089. ++nItems;
  1090. break;
  1091. case ELOCALIZED_COLUMN_LEGACY_TRANSLATION:
  1092. sTranslatedActorLine = cell;
  1093. sTranslatedText = cell;
  1094. bValidTranslatedText = true;
  1095. ++nItems;
  1096. break;
  1097. case ELOCALIZED_COLUMN_LEGACY_YOUR_TRANSLATION:
  1098. sTranslatedActorLine = cell;
  1099. sTranslatedText = cell;
  1100. bValidTranslatedText = true;
  1101. ++nItems;
  1102. break;
  1103. case ELOCALIZED_COLUMN_LEGACY_ENGLISH_SUBTITLE:
  1104. sSubtitleText = cell;
  1105. ++nItems;
  1106. break;
  1107. case ELOCALIZED_COLUMN_LEGACY_TRANSLATED_SUBTITLE:
  1108. sTranslatedText = cell;
  1109. sTranslatedActorLine = cell;
  1110. bValidTranslatedText = true;
  1111. ++nItems;
  1112. break;
  1113. case ELOCALIZED_COLUMN_LEGACY_ORIGINAL_CHARACTER_NAME:
  1114. sCharacterName = cell;
  1115. ++nItems;
  1116. break;
  1117. case ELOCALIZED_COLUMN_LEGACY_TRANSLATED_CHARACTER_NAME:
  1118. sTranslatedCharacterName = cell;
  1119. bValidTranslatedCharacterName = true;
  1120. ++nItems;
  1121. break;
  1122. case ELOCALIZED_COLUMN_LEGACY_ORIGINAL_TEXT:
  1123. sSubtitleText = cell;
  1124. ++nItems;
  1125. break;
  1126. case ELOCALIZED_COLUMN_LEGACY_TRANSLATED_TEXT:
  1127. sTranslatedText = cell;
  1128. bValidTranslatedText = true;
  1129. ++nItems;
  1130. break;
  1131. case ELOCALIZED_COLUMN_LEGACY_ORIGINAL_ACTOR_LINE:
  1132. sActorLine = cell;
  1133. ++nItems;
  1134. break;
  1135. case ELOCALIZED_COLUMN_LEGACY_TRANSLATED_ACTOR_LINE:
  1136. sTranslatedActorLine = cell;
  1137. bValidTranslatedActorLine = true;
  1138. ++nItems;
  1139. break;
  1140. }
  1141. }
  1142. if (!bValidKey)
  1143. {
  1144. continue;
  1145. }
  1146. if (!bValidTranslatedText)
  1147. {
  1148. // if this is a dialog entry with a soundevent and with subtitles then a warning should be issued
  1149. if (m_cvarLocalizationDebug && !sSoundEvent.empty() && bUseSubtitle)
  1150. {
  1151. sTmp.assign(sKeyString.ptr, sKeyString.count);
  1152. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "[LocError] Key '%s' in file <%s> has no translated text", sTmp.c_str(), sFileName);
  1153. }
  1154. // use translated actor line entry if available before falling back to original entry
  1155. if (!sTranslatedActorLine.empty())
  1156. {
  1157. sTranslatedText = sTranslatedActorLine;
  1158. }
  1159. else
  1160. {
  1161. sTranslatedText = sSubtitleText;
  1162. }
  1163. }
  1164. if (!bValidTranslatedActorLine)
  1165. {
  1166. // if this is a dialog entry with a soundevent then a warning should be issued
  1167. if (m_cvarLocalizationDebug && !sSoundEvent.empty())
  1168. {
  1169. sTmp.assign(sKeyString.ptr, sKeyString.count);
  1170. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "[LocError] Key '%s' in file <%s> has no translated actor line", sTmp.c_str(), sFileName);
  1171. }
  1172. // use translated text entry if available before falling back to original entry
  1173. if (!sTranslatedText.empty())
  1174. {
  1175. sTranslatedActorLine = sTranslatedText;
  1176. }
  1177. else
  1178. {
  1179. sTranslatedActorLine = sSubtitleText;
  1180. }
  1181. }
  1182. if (!sSoundEvent.empty() && !bValidTranslatedCharacterName)
  1183. {
  1184. if (m_cvarLocalizationDebug)
  1185. {
  1186. sTmp.assign(sKeyString.ptr, sKeyString.count);
  1187. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "[LocError] Key '%s' in file <%s> has no translated character name", sTmp.c_str(), sFileName);
  1188. }
  1189. sTranslatedCharacterName = sCharacterName;
  1190. }
  1191. if (nItems == 1) // skip lines which contain just one item in the key
  1192. {
  1193. continue;
  1194. }
  1195. // reject to store text if line was marked with no subtitles in game mode
  1196. if (!gEnv->IsEditor())
  1197. {
  1198. if (!bUseSubtitle)
  1199. {
  1200. sSubtitleText.clear();
  1201. sTranslatedText.clear();
  1202. }
  1203. }
  1204. // Skip @ character in the key string.
  1205. if (!sKeyString.empty() && sKeyString.ptr[0] == '@')
  1206. {
  1207. sKeyString.ptr++;
  1208. sKeyString.count--;
  1209. }
  1210. {
  1211. CopyLowercase(szLowerCaseEvent, sizeof(szLowerCaseEvent), sSoundEvent.ptr, sSoundEvent.count);
  1212. CopyLowercase(szLowerCaseKey, sizeof(szLowerCaseKey), sKeyString.ptr, sKeyString.count);
  1213. }
  1214. //Compute the CRC32 of the key
  1215. keyCRC = AZ::Crc32(szLowerCaseKey);
  1216. if (m_cvarLocalizationDebug >= 3)
  1217. {
  1218. CryLogAlways("<Localization dupe/clash detection> CRC32: 0x%8X, Key: %s", keyCRC, szLowerCaseKey);
  1219. }
  1220. if (m_pLanguage->m_keysMap.find(keyCRC) != m_pLanguage->m_keysMap.end())
  1221. {
  1222. sTmp.assign(sKeyString.ptr, sKeyString.count);
  1223. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "[LocError] Localized String '%s' Already Loaded for Language %s OR there is a CRC hash clash", sTmp.c_str(), m_pLanguage->sLanguage.c_str());
  1224. continue;
  1225. }
  1226. SLocalizedStringEntry* pEntry = new SLocalizedStringEntry();
  1227. pEntry->flags = 0;
  1228. if (bUseSubtitle == true)
  1229. {
  1230. pEntry->flags |= SLocalizedStringEntry::USE_SUBTITLE;
  1231. }
  1232. pEntry->nTagID = nTagID;
  1233. if (gEnv->IsEditor())
  1234. {
  1235. pEntry->pEditorExtension = new SLocalizedStringEntryEditorExtension();
  1236. pEntry->pEditorExtension->sKey = szLowerCaseKey;
  1237. pEntry->pEditorExtension->nRow = nRowIndex;
  1238. if (!sActorLine.empty())
  1239. {
  1240. sTmp.assign(sActorLine.ptr, sActorLine.count);
  1241. ReplaceEndOfLine(sTmp);
  1242. pEntry->pEditorExtension->sOriginalActorLine.assign(sTmp.c_str());
  1243. }
  1244. if (!sTranslatedActorLine.empty())
  1245. {
  1246. sTmp.assign(sTranslatedActorLine.ptr, sTranslatedActorLine.count);
  1247. ReplaceEndOfLine(sTmp);
  1248. pEntry->pEditorExtension->sUtf8TranslatedActorLine.append(sTmp.c_str());
  1249. }
  1250. if (bUseSubtitle && !sSubtitleText.empty())
  1251. {
  1252. sTmp.assign(sSubtitleText.ptr, sSubtitleText.count);
  1253. ReplaceEndOfLine(sTmp);
  1254. pEntry->pEditorExtension->sOriginalText.assign(sTmp.c_str());
  1255. }
  1256. // only use the translated character name
  1257. {
  1258. pEntry->pEditorExtension->sOriginalCharacterName.assign(sCharacterName.ptr, sCharacterName.count);
  1259. }
  1260. }
  1261. if (bUseSubtitle && !sTranslatedText.empty())
  1262. {
  1263. sTmp.assign(sTranslatedText.ptr, sTranslatedText.count);
  1264. ReplaceEndOfLine(sTmp);
  1265. if (m_cvarLocalizationEncode == 1)
  1266. {
  1267. pEncoder->Update((const uint8*)(sTmp.c_str()), sTmp.length());
  1268. //CryLogAlways("%u Storing %s (%u)", m_pLanguage->m_vLocalizedStrings.size(), sTmp.c_str(), sTmp.length());
  1269. pEntry->TranslatedText.szCompressed = new uint8[sTmp.length() + 1];
  1270. pEntry->flags |= SLocalizedStringEntry::IS_COMPRESSED;
  1271. //Store the raw string. It'll be compressed later
  1272. memcpy(pEntry->TranslatedText.szCompressed, sTmp.c_str(), sTmp.length());
  1273. pEntry->TranslatedText.szCompressed[sTmp.length()] = '\0'; //Null terminate
  1274. }
  1275. else
  1276. {
  1277. pEntry->TranslatedText.psUtf8Uncompressed = new AZStd::string(sTmp.c_str(), sTmp.c_str() + sTmp.length());
  1278. }
  1279. }
  1280. // the following is used to cleverly assign strings
  1281. // we store all known string into the m_prototypeEvents set and assign known entries from there
  1282. // the CryString makes sure, that only the ref-count is increment on assignment
  1283. if (*szLowerCaseEvent)
  1284. {
  1285. PrototypeSoundEvents::iterator it = m_prototypeEvents.find(AZStd::string(szLowerCaseEvent));
  1286. if (it != m_prototypeEvents.end())
  1287. {
  1288. pEntry->sPrototypeSoundEvent = *it;
  1289. }
  1290. else
  1291. {
  1292. pEntry->sPrototypeSoundEvent = szLowerCaseEvent;
  1293. m_prototypeEvents.insert(pEntry->sPrototypeSoundEvent);
  1294. }
  1295. }
  1296. const CConstCharArray sWho = sTranslatedCharacterName.empty() ? sCharacterName : sTranslatedCharacterName;
  1297. if (!sWho.empty())
  1298. {
  1299. sTmp.assign(sWho.ptr, sWho.count);
  1300. ReplaceEndOfLine(sTmp);
  1301. AZStd::replace(sTmp.begin(), sTmp.end(), ' ', '_');
  1302. AZStd::string tmp;
  1303. {
  1304. tmp = sTmp.c_str();
  1305. }
  1306. CharacterNameSet::iterator it = m_characterNameSet.find(tmp);
  1307. if (it != m_characterNameSet.end())
  1308. {
  1309. pEntry->sCharacterName = *it;
  1310. }
  1311. else
  1312. {
  1313. pEntry->sCharacterName = tmp;
  1314. m_characterNameSet.insert(pEntry->sCharacterName);
  1315. }
  1316. }
  1317. pEntry->fVolume = CryConvertFloatToHalf(fVolume);
  1318. // SoundMood Entries
  1319. {
  1320. pEntry->SoundMoods.resize(static_cast<int>(SoundMoodValues.size()));
  1321. if (SoundMoodValues.size() > 0)
  1322. {
  1323. std::map<int, float>::const_iterator itEnd = SoundMoodValues.end();
  1324. int nSoundMoodCount = 0;
  1325. for (std::map<int, float>::const_iterator it = SoundMoodValues.begin(); it != itEnd; ++it)
  1326. {
  1327. pEntry->SoundMoods[nSoundMoodCount].fValue = (*it).second;
  1328. pEntry->SoundMoods[nSoundMoodCount].sName = SoundMoodIndex[(*it).first];
  1329. ++nSoundMoodCount;
  1330. }
  1331. }
  1332. }
  1333. // EventParameter Entries
  1334. {
  1335. pEntry->EventParameters.resize(static_cast<int>(EventParameterValues.size()));
  1336. if (EventParameterValues.size() > 0)
  1337. {
  1338. std::map<int, float>::const_iterator itEnd = EventParameterValues.end();
  1339. int nEventParameterCount = 0;
  1340. for (std::map<int, float>::const_iterator it = EventParameterValues.begin(); it != itEnd; ++it)
  1341. {
  1342. pEntry->EventParameters[nEventParameterCount].fValue = (*it).second;
  1343. pEntry->EventParameters[nEventParameterCount].sName = EventParameterIndex[(*it).first];
  1344. ++nEventParameterCount;
  1345. }
  1346. }
  1347. }
  1348. pEntry->fRadioRatio = CryConvertFloatToHalf(fRadioRatio);
  1349. if (bIsDirectRadio == true)
  1350. {
  1351. pEntry->flags |= SLocalizedStringEntry::IS_DIRECTED_RADIO;
  1352. }
  1353. if (bIsIntercepted == true)
  1354. {
  1355. pEntry->flags |= SLocalizedStringEntry::IS_INTERCEPTED;
  1356. }
  1357. AddLocalizedString(m_pLanguage, pEntry, keyCRC);
  1358. }
  1359. if (m_cvarLocalizationEncode == 1)
  1360. {
  1361. pEncoder->Finalize();
  1362. uint8 compressionBuffer[COMPRESSION_FIXED_BUFFER_LENGTH];
  1363. for (size_t stringToCompress = startOfStringsToCompress; stringToCompress < m_pLanguage->m_vLocalizedStrings.size(); stringToCompress++)
  1364. {
  1365. SLocalizedStringEntry* pStringToCompress = m_pLanguage->m_vLocalizedStrings[stringToCompress];
  1366. if (pStringToCompress->TranslatedText.szCompressed != NULL)
  1367. {
  1368. size_t compBufSize = COMPRESSION_FIXED_BUFFER_LENGTH;
  1369. memset(compressionBuffer, 0, COMPRESSION_FIXED_BUFFER_LENGTH);
  1370. size_t inputStringLength = strlen((const char*)(pStringToCompress->TranslatedText.szCompressed));
  1371. pEncoder->CompressInput(pStringToCompress->TranslatedText.szCompressed, inputStringLength, compressionBuffer, &compBufSize);
  1372. compressionBuffer[compBufSize] = 0;
  1373. pStringToCompress->huffmanTreeIndex = iEncoder;
  1374. pEncoder->AddRef();
  1375. uint8* szCompressedString = new uint8[compBufSize];
  1376. SAFE_DELETE_ARRAY(pStringToCompress->TranslatedText.szCompressed);
  1377. memcpy(szCompressedString, compressionBuffer, compBufSize);
  1378. pStringToCompress->TranslatedText.szCompressed = szCompressedString;
  1379. }
  1380. }
  1381. }
  1382. pXmlTableReader->Release();
  1383. return true;
  1384. }
  1385. bool CLocalizedStringsManager::DoLoadAGSXmlDocument(const char* sFileName, uint8 nTagID, bool bReload)
  1386. {
  1387. if (!sFileName|| !m_pLanguage)
  1388. {
  1389. return false;
  1390. }
  1391. if (!bReload)
  1392. {
  1393. if (m_loadedTables.find(AZStd::string(sFileName)) != m_loadedTables.end())
  1394. {
  1395. return true;
  1396. }
  1397. }
  1398. ListAndClearProblemLabels();
  1399. XmlNodeRef root;
  1400. AZStd::string sPath;
  1401. {
  1402. const AZStd::string sLocalizationFolder(PathUtil::GetLocalizationRoot());
  1403. const AZStd::string& languageFolder = m_pLanguage->sLanguage;
  1404. sPath = sLocalizationFolder.c_str() + languageFolder + PathUtil::GetSlash() + sFileName;
  1405. root = m_pSystem->LoadXmlFromFile(sPath.c_str());
  1406. if (!root)
  1407. {
  1408. AZ_TracePrintf(LOC_WINDOW, "Loading Localization File %s failed!", sPath.c_str());
  1409. return false;
  1410. }
  1411. }
  1412. AZ_TracePrintf(LOC_WINDOW, "Loading Localization File %s", sPath.c_str());
  1413. HuffmanCoder* pEncoder = nullptr;
  1414. uint8 iEncoder = 0;
  1415. size_t startOfStringsToCompress = 0;
  1416. if (m_cvarLocalizationEncode == 1)
  1417. {
  1418. {
  1419. for (iEncoder = 0; iEncoder < m_pLanguage->m_vEncoders.size(); iEncoder++)
  1420. {
  1421. if (m_pLanguage->m_vEncoders[iEncoder] == nullptr)
  1422. {
  1423. pEncoder = new HuffmanCoder();
  1424. m_pLanguage->m_vEncoders[iEncoder] = pEncoder;
  1425. break;
  1426. }
  1427. }
  1428. if (iEncoder == m_pLanguage->m_vEncoders.size())
  1429. {
  1430. pEncoder = new HuffmanCoder();
  1431. m_pLanguage->m_vEncoders.push_back(pEncoder);
  1432. }
  1433. pEncoder->Init();
  1434. }
  1435. startOfStringsToCompress = m_pLanguage->m_vLocalizedStrings.size();
  1436. }
  1437. int rowCount = 0;
  1438. {
  1439. AutoLock lock(m_cs); // Make sure to lock, as this is a modifying operation
  1440. rowCount = root->getChildCount();
  1441. {
  1442. m_pLanguage->m_vLocalizedStrings.reserve(m_pLanguage->m_vLocalizedStrings.size() + rowCount);
  1443. }
  1444. m_pLanguage->m_keysMap.reserve(m_pLanguage->m_keysMap.size() + rowCount);
  1445. }
  1446. {
  1447. pairFileName sNewFile;
  1448. sNewFile.first = sFileName;
  1449. sNewFile.second.bDataStripping = false; // this is off for now
  1450. sNewFile.second.nTagID = nTagID;
  1451. m_loadedTables.insert(sNewFile);
  1452. }
  1453. const char* key = nullptr;
  1454. AZStd::string keyString;
  1455. AZStd::string lowerKey;
  1456. AZStd::string textValue;
  1457. uint32 keyCRC=0;
  1458. for (int i = 0; i < rowCount; ++i)
  1459. {
  1460. XmlNodeRef childNode = root->getChild(i);
  1461. if (azstricmp(childNode->getTag(), "string") || !childNode->getAttr("key", &key))
  1462. {
  1463. continue;
  1464. }
  1465. keyString = key;
  1466. textValue = childNode->getContent();
  1467. if (textValue.empty())
  1468. {
  1469. continue;
  1470. }
  1471. AzFramework::StringFunc::Replace(textValue, "\\n", " \n");
  1472. if (keyString[0] == '@')
  1473. {
  1474. AzFramework::StringFunc::LChop(keyString, 1);
  1475. }
  1476. lowerKey = keyString;
  1477. AZStd::to_lower(lowerKey.begin(), lowerKey.end());
  1478. keyCRC = AZ::Crc32(lowerKey);
  1479. if (m_cvarLocalizationDebug >= 3)
  1480. {
  1481. CryLogAlways("<Localization dupe/clash detection> CRC32: 0%8X, Key: %s", keyCRC, lowerKey.c_str());
  1482. }
  1483. if (m_pLanguage->m_keysMap.find(keyCRC) != m_pLanguage->m_keysMap.end())
  1484. {
  1485. AZ_Warning(LOC_WINDOW, false, "Localized String '%s' Already Loaded for Language %s OR there is a CRC hash clash", keyString.c_str(), m_pLanguage->sLanguage.c_str());
  1486. continue;
  1487. }
  1488. SLocalizedStringEntry* pEntry = new SLocalizedStringEntry;
  1489. pEntry->flags = SLocalizedStringEntry::USE_SUBTITLE;
  1490. pEntry->nTagID = nTagID;
  1491. if (gEnv->IsEditor())
  1492. {
  1493. pEntry->pEditorExtension = new SLocalizedStringEntryEditorExtension();
  1494. pEntry->pEditorExtension->sKey = lowerKey.c_str();
  1495. pEntry->pEditorExtension->nRow = i;
  1496. {
  1497. pEntry->pEditorExtension->sUtf8TranslatedActorLine.append(textValue.c_str());
  1498. }
  1499. {
  1500. pEntry->pEditorExtension->sOriginalText.assign(textValue.c_str());
  1501. }
  1502. }
  1503. {
  1504. const char* textString = textValue.c_str();
  1505. size_t textLength = textValue.length();
  1506. if (m_cvarLocalizationEncode == 1)
  1507. {
  1508. pEncoder->Update((const uint8*)(textString), textLength);
  1509. pEntry->TranslatedText.szCompressed = new uint8[textLength + 1];
  1510. pEntry->flags |= SLocalizedStringEntry::IS_COMPRESSED;
  1511. memcpy(pEntry->TranslatedText.szCompressed, textString, textLength);
  1512. pEntry->TranslatedText.szCompressed[textLength] = '\0'; //Null terminate
  1513. }
  1514. else
  1515. {
  1516. pEntry->TranslatedText.psUtf8Uncompressed = new AZStd::string(textString, textString + textLength);
  1517. }
  1518. }
  1519. {
  1520. AddLocalizedString(m_pLanguage, pEntry, keyCRC);
  1521. }
  1522. }
  1523. if (m_cvarLocalizationEncode == 1)
  1524. {
  1525. {
  1526. pEncoder->Finalize();
  1527. }
  1528. {
  1529. uint8 compressionBuffer[COMPRESSION_FIXED_BUFFER_LENGTH] = {};
  1530. for (size_t stringToCompress = startOfStringsToCompress; stringToCompress < m_pLanguage->m_vLocalizedStrings.size(); stringToCompress++)
  1531. {
  1532. SLocalizedStringEntry* pStringToCompress = m_pLanguage->m_vLocalizedStrings[stringToCompress];
  1533. if (pStringToCompress->TranslatedText.szCompressed != nullptr)
  1534. {
  1535. size_t compBufSize = COMPRESSION_FIXED_BUFFER_LENGTH;
  1536. memset(compressionBuffer, 0, COMPRESSION_FIXED_BUFFER_LENGTH);
  1537. size_t inputStringLength = strnlen((const char*)(pStringToCompress->TranslatedText.szCompressed), COMPRESSION_FIXED_BUFFER_LENGTH);
  1538. pEncoder->CompressInput(pStringToCompress->TranslatedText.szCompressed, inputStringLength, compressionBuffer, &compBufSize);
  1539. compressionBuffer[compBufSize] = 0;
  1540. pStringToCompress->huffmanTreeIndex = iEncoder;
  1541. pEncoder->AddRef();
  1542. uint8* szCompressedString = new uint8[compBufSize];
  1543. SAFE_DELETE_ARRAY(pStringToCompress->TranslatedText.szCompressed);
  1544. memcpy(szCompressedString, compressionBuffer, compBufSize);
  1545. pStringToCompress->TranslatedText.szCompressed = szCompressedString;
  1546. }
  1547. }
  1548. }
  1549. }
  1550. return true;
  1551. }
  1552. //////////////////////////////////////////////////////////////////////////
  1553. CLocalizedStringsManager::LoadFunc CLocalizedStringsManager::GetLoadFunction() const
  1554. {
  1555. CRY_ASSERT_MESSAGE(gEnv && gEnv->pConsole, "System environment or console missing!");
  1556. if (gEnv && gEnv->pConsole)
  1557. {
  1558. int32_t localizationFormat{};
  1559. if (auto console = AZ::Interface<AZ::IConsole>::Get();
  1560. console !=nullptr)
  1561. {
  1562. console->GetCvarValue(c_sys_localization_format, localizationFormat);
  1563. }
  1564. if(localizationFormat == 1)
  1565. {
  1566. return &CLocalizedStringsManager::DoLoadAGSXmlDocument;
  1567. }
  1568. }
  1569. return &CLocalizedStringsManager::DoLoadExcelXmlSpreadsheet;
  1570. }
  1571. void CLocalizedStringsManager::ReloadData()
  1572. {
  1573. tmapFilenames temp = m_loadedTables;
  1574. LoadFunc loadFunction = GetLoadFunction();
  1575. FreeLocalizationData();
  1576. for (tmapFilenames::iterator it = temp.begin(); it != temp.end(); it++)
  1577. {
  1578. (this->*loadFunction)((*it).first.c_str(), (*it).second.nTagID, true);
  1579. }
  1580. }
  1581. //////////////////////////////////////////////////////////////////////////
  1582. void CLocalizedStringsManager::AddLocalizedString(SLanguage* pLanguage, SLocalizedStringEntry* pEntry, const uint32 keyCRC32)
  1583. {
  1584. pLanguage->m_vLocalizedStrings.push_back(pEntry);
  1585. [[maybe_unused]] int nId = (int)pLanguage->m_vLocalizedStrings.size() - 1;
  1586. pLanguage->m_keysMap[keyCRC32] = pEntry;
  1587. if (m_cvarLocalizationDebug >= 2)
  1588. {
  1589. CryLog("<Localization> Add new string <%u> with ID %d to <%s>", keyCRC32, nId, pLanguage->sLanguage.c_str());
  1590. }
  1591. }
  1592. //////////////////////////////////////////////////////////////////////////
  1593. bool CLocalizedStringsManager::LocalizeString_ch(const char* sString, AZStd::string& outLocalizedString, bool bEnglish)
  1594. {
  1595. return LocalizeStringInternal(sString, strlen(sString), outLocalizedString, bEnglish);
  1596. }
  1597. //////////////////////////////////////////////////////////////////////////
  1598. bool CLocalizedStringsManager::LocalizeString_s(const AZStd::string& sString, AZStd::string& outLocalizedString, bool bEnglish)
  1599. {
  1600. return LocalizeStringInternal(sString.c_str(), sString.length(), outLocalizedString, bEnglish);
  1601. }
  1602. //////////////////////////////////////////////////////////////////////////
  1603. bool CLocalizedStringsManager::LocalizeStringInternal(const char* pStr, size_t len, AZStd::string& outLocalizedString, bool bEnglish)
  1604. {
  1605. assert (m_pLanguage);
  1606. if (m_pLanguage == nullptr)
  1607. {
  1608. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "LocalizeString: No language set.");
  1609. outLocalizedString.assign(pStr, pStr + len);
  1610. return false;
  1611. }
  1612. // note: we don't write directly to outLocalizedString, in case it aliases pStr
  1613. AZStd::string out;
  1614. // scan the string
  1615. const char* pPos = pStr;
  1616. const char* pEnd = pStr + len;
  1617. while (true)
  1618. {
  1619. const char* pLabel = strchr(pPos, '@');
  1620. if (!pLabel)
  1621. {
  1622. break;
  1623. }
  1624. // found an occurrence
  1625. // we have skipped a few characters, so copy them over
  1626. if (pLabel != pPos)
  1627. {
  1628. out.append(pPos, pLabel);
  1629. }
  1630. // Search label for first occurence of any label terminating character
  1631. const char* pLabelEnd = strpbrk(pLabel, " !\"#$%&\'()*+,./:;<=>\?[\\]^`{|}~\n\t\r");
  1632. if (!pLabelEnd)
  1633. {
  1634. pLabelEnd = pEnd;
  1635. }
  1636. // localize token
  1637. AZStd::string token(pLabel, pLabelEnd);
  1638. AZStd::string sLocalizedToken;
  1639. if (bEnglish)
  1640. {
  1641. GetEnglishString(token.c_str(), sLocalizedToken);
  1642. }
  1643. else
  1644. {
  1645. LocalizeLabel(token.c_str(), sLocalizedToken);
  1646. }
  1647. out.append(sLocalizedToken);
  1648. pPos = pLabelEnd;
  1649. }
  1650. out.append(pPos, pEnd);
  1651. out.swap(outLocalizedString);
  1652. return true;
  1653. }
  1654. void CLocalizedStringsManager::LocalizeAndSubstituteInternal(AZStd::string& locString, const AZStd::vector<AZStd::string>& keys, const AZStd::vector<AZStd::string>& values)
  1655. {
  1656. AZStd::string outString;
  1657. LocalizeString_ch(locString.c_str(), outString);
  1658. locString = outString .c_str();
  1659. if (values.size() != keys.size())
  1660. {
  1661. AZ_Warning("game", false, "Localization Error: LocalizeAndSubstitute was given %u keys and %u values to replace. These numbers must be equal.", keys.size(), values.size());
  1662. return;
  1663. }
  1664. size_t startIndex = locString.find('{');
  1665. size_t endIndex = locString.find('}', startIndex);
  1666. while (startIndex != AZStd::string::npos && endIndex != AZStd::string::npos)
  1667. {
  1668. const size_t subLength = endIndex - startIndex - 1;
  1669. AZStd::string substituteOut = locString.substr(startIndex + 1, subLength).c_str();
  1670. int index = 0;
  1671. if (LocalizationHelpers::IsKeyInList(keys, substituteOut, index))
  1672. {
  1673. const char* value = values[index].c_str();
  1674. locString.replace(startIndex, subLength + 2, value);
  1675. startIndex += strlen(value);
  1676. }
  1677. else
  1678. {
  1679. AZ_Warning("game", false, "Localization Error: Localized string '%s' contains a key '%s' that is not mapped to a data element.", locString.c_str(), substituteOut.c_str());
  1680. startIndex += substituteOut.length();
  1681. }
  1682. startIndex = locString.find_first_of('{', startIndex);
  1683. endIndex = locString.find_first_of('}', startIndex);
  1684. }
  1685. }
  1686. #if defined(LOG_DECOMP_TIMES)
  1687. static double g_fSecondsPerTick = 0.0;
  1688. static FILE* pDecompLog = NULL;
  1689. // engine independent game timer since gEnv/pSystem isn't available yet
  1690. static void LogDecompTimer(__int64 nTotalTicks, __int64 nDecompTicks, __int64 nAllocTicks)
  1691. {
  1692. if (g_fSecondsPerTick == 0.0)
  1693. {
  1694. __int64 nPerfFreq;
  1695. QueryPerformanceFrequency((LARGE_INTEGER*)&nPerfFreq);
  1696. g_fSecondsPerTick = 1.0 / (double)nPerfFreq;
  1697. }
  1698. if (!pDecompLog)
  1699. {
  1700. char szFilenameBuffer[MAX_PATH];
  1701. time_t rawTime;
  1702. time(&rawTime);
  1703. struct tm* pTimeInfo = localtime(&rawTime);
  1704. CreateDirectory("TestResults\\", 0);
  1705. strftime(szFilenameBuffer, sizeof(szFilenameBuffer), "TestResults\\Decomp_%Y_%m_%d-%H_%M_%S.csv", pTimeInfo);
  1706. pDecompLog = fopen(szFilenameBuffer, "wb");
  1707. fprintf(pDecompLog, "Total,Decomp,Alloc\n");
  1708. }
  1709. float nTotalMillis = float( g_fSecondsPerTick * 1000.0 * nTotalTicks );
  1710. float nDecompMillis = float( g_fSecondsPerTick * 1000.0 * nDecompTicks );
  1711. float nAllocMillis = float( g_fSecondsPerTick * 1000.0 * nAllocTicks );
  1712. fprintf(pDecompLog, "%f,%f,%f\n", nTotalMillis, nDecompMillis, nAllocMillis);
  1713. fflush(pDecompLog);
  1714. }
  1715. #endif
  1716. AZStd::string CLocalizedStringsManager::SLocalizedStringEntry::GetTranslatedText(const SLanguage* pLanguage) const
  1717. {
  1718. if ((flags & IS_COMPRESSED) != 0)
  1719. {
  1720. #if defined(LOG_DECOMP_TIMES)
  1721. __int64 nTotalTicks, nDecompTicks, nAllocTicks;
  1722. nTotalTicks = CryGetTicks();
  1723. #endif //LOG_DECOMP_TIMES
  1724. AZStd::string outputString;
  1725. if (TranslatedText.szCompressed != NULL)
  1726. {
  1727. uint8 decompressionBuffer[COMPRESSION_FIXED_BUFFER_LENGTH];
  1728. HuffmanCoder* pEncoder = pLanguage->m_vEncoders[huffmanTreeIndex];
  1729. #if defined(LOG_DECOMP_TIMES)
  1730. nDecompTicks = CryGetTicks();
  1731. #endif //LOG_DECOMP_TIMES
  1732. //We don't actually know how much memory was allocated for this string, but the maximum compression buffer size is known
  1733. size_t decompBufSize = pEncoder->UncompressInput(TranslatedText.szCompressed, COMPRESSION_FIXED_BUFFER_LENGTH, decompressionBuffer, COMPRESSION_FIXED_BUFFER_LENGTH);
  1734. assert(decompBufSize < COMPRESSION_FIXED_BUFFER_LENGTH && "Buffer overflow");
  1735. #if defined(LOG_DECOMP_TIMES)
  1736. nDecompTicks = CryGetTicks() - nDecompTicks;
  1737. #endif //LOG_DECOMP_TIMES
  1738. #if !defined(NDEBUG)
  1739. size_t len = strnlen((const char*)decompressionBuffer, COMPRESSION_FIXED_BUFFER_LENGTH);
  1740. assert(len < COMPRESSION_FIXED_BUFFER_LENGTH && "Buffer not null-terminated");
  1741. #endif
  1742. #if defined(LOG_DECOMP_TIMES)
  1743. nAllocTicks = CryGetTicks();
  1744. #endif //LOG_DECOMP_TIMES
  1745. outputString.assign((const char*)decompressionBuffer, (const char*)decompressionBuffer + decompBufSize);
  1746. #if defined(LOG_DECOMP_TIMES)
  1747. nAllocTicks = CryGetTicks() - nAllocTicks;
  1748. nTotalTicks = CryGetTicks() - nTotalTicks;
  1749. LogDecompTimer(nTotalTicks, nDecompTicks, nAllocTicks);
  1750. #endif //LOG_DECOMP_TIMES
  1751. }
  1752. return outputString;
  1753. }
  1754. else
  1755. {
  1756. if (TranslatedText.psUtf8Uncompressed != NULL)
  1757. {
  1758. return *TranslatedText.psUtf8Uncompressed;
  1759. }
  1760. else
  1761. {
  1762. AZStd::string emptyOutputString;
  1763. return emptyOutputString;
  1764. }
  1765. }
  1766. }
  1767. #ifndef _RELEASE
  1768. //////////////////////////////////////////////////////////////////////////
  1769. void CLocalizedStringsManager::LocalizedStringsManagerWarning(const char* label, const char* message)
  1770. {
  1771. if (!m_warnedAboutLabels[label])
  1772. {
  1773. CryWarning (VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "Failed to localize label '%s' - %s", label, message);
  1774. m_warnedAboutLabels[label] = true;
  1775. m_haveWarnedAboutAtLeastOneLabel = true;
  1776. }
  1777. }
  1778. //////////////////////////////////////////////////////////////////////////
  1779. void CLocalizedStringsManager::ListAndClearProblemLabels()
  1780. {
  1781. if (m_haveWarnedAboutAtLeastOneLabel)
  1782. {
  1783. CryLog ("These labels caused localization problems:");
  1784. INDENT_LOG_DURING_SCOPE();
  1785. for (std::map<AZStd::string, bool>::iterator iter = m_warnedAboutLabels.begin(); iter != m_warnedAboutLabels.end(); iter++)
  1786. {
  1787. CryLog ("%s", iter->first.c_str());
  1788. }
  1789. m_warnedAboutLabels.clear();
  1790. m_haveWarnedAboutAtLeastOneLabel = false;
  1791. }
  1792. }
  1793. #endif
  1794. //////////////////////////////////////////////////////////////////////////
  1795. bool CLocalizedStringsManager::LocalizeLabel(const char* sLabel, AZStd::string& outLocalString, bool bEnglish)
  1796. {
  1797. assert(sLabel);
  1798. if (!m_pLanguage || !sLabel)
  1799. {
  1800. return false;
  1801. }
  1802. // Label sign.
  1803. if (sLabel[0] == '@')
  1804. {
  1805. uint32 labelCRC32 = AZ::Crc32(sLabel + 1); // skip @ character.
  1806. {
  1807. AutoLock lock(m_cs); //Lock here, to prevent strings etc being modified underneath this lookup
  1808. SLocalizedStringEntry* entry = stl::find_in_map(m_pLanguage->m_keysMap, labelCRC32, NULL);
  1809. if (entry != NULL)
  1810. {
  1811. AZStd::string translatedText = entry->GetTranslatedText(m_pLanguage);
  1812. if ((bEnglish || translatedText.empty()) && entry->pEditorExtension != NULL)
  1813. {
  1814. //assert(!"No Localization Text available!");
  1815. outLocalString.assign(entry->pEditorExtension->sOriginalText);
  1816. return true;
  1817. }
  1818. else
  1819. {
  1820. outLocalString.swap(translatedText);
  1821. }
  1822. return true;
  1823. }
  1824. else
  1825. {
  1826. LocalizedStringsManagerWarning(sLabel, "entry not found in string table");
  1827. }
  1828. }
  1829. }
  1830. else
  1831. {
  1832. LocalizedStringsManagerWarning(sLabel, "must start with @ symbol");
  1833. }
  1834. outLocalString.assign(sLabel);
  1835. return false;
  1836. }
  1837. //////////////////////////////////////////////////////////////////////////
  1838. bool CLocalizedStringsManager::GetEnglishString(const char* sKey, AZStd::string& sLocalizedString)
  1839. {
  1840. assert(sKey);
  1841. if (!m_pLanguage || !sKey)
  1842. {
  1843. return false;
  1844. }
  1845. // Label sign.
  1846. if (sKey[0] == '@')
  1847. {
  1848. uint32 keyCRC32 = AZ::Crc32(sKey + 1);
  1849. {
  1850. AutoLock lock(m_cs); // Lock here, to prevent strings etc being modified underneath this lookup
  1851. SLocalizedStringEntry* entry = stl::find_in_map(m_pLanguage->m_keysMap, keyCRC32, NULL); // skip @ character.
  1852. if (entry != NULL && entry->pEditorExtension != NULL)
  1853. {
  1854. sLocalizedString = entry->pEditorExtension->sOriginalText;
  1855. return true;
  1856. }
  1857. else
  1858. {
  1859. keyCRC32 = AZ::Crc32(sKey);
  1860. entry = stl::find_in_map(m_pLanguage->m_keysMap, keyCRC32, NULL);
  1861. if (entry != NULL && entry->pEditorExtension != NULL)
  1862. {
  1863. sLocalizedString = entry->pEditorExtension->sOriginalText;
  1864. return true;
  1865. }
  1866. else
  1867. {
  1868. // CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"Localized string for Label <%s> not found", sKey );
  1869. sLocalizedString = sKey;
  1870. return false;
  1871. }
  1872. }
  1873. }
  1874. }
  1875. else
  1876. {
  1877. // CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"Not a valid localized string Label <%s>, must start with @ symbol", sKey
  1878. // );
  1879. }
  1880. sLocalizedString = sKey;
  1881. return false;
  1882. }
  1883. bool CLocalizedStringsManager::IsLocalizedInfoFound(const char* sKey)
  1884. {
  1885. if (!m_pLanguage)
  1886. {
  1887. return false;
  1888. }
  1889. uint32 keyCRC32 = AZ::Crc32(sKey);
  1890. {
  1891. AutoLock lock(m_cs); //Lock here, to prevent strings etc being modified underneath this lookup
  1892. const SLocalizedStringEntry* entry = stl::find_in_map(m_pLanguage->m_keysMap, keyCRC32, NULL);
  1893. return (entry != NULL);
  1894. }
  1895. }
  1896. //////////////////////////////////////////////////////////////////////////
  1897. bool CLocalizedStringsManager::GetLocalizedInfoByKey(const char* sKey, SLocalizedInfoGame& outGameInfo)
  1898. {
  1899. if (!m_pLanguage)
  1900. {
  1901. return false;
  1902. }
  1903. uint32 keyCRC32 = AZ::Crc32(sKey);
  1904. {
  1905. AutoLock lock(m_cs); //Lock here, to prevent strings etc being modified underneath this lookup
  1906. const SLocalizedStringEntry* entry = stl::find_in_map(m_pLanguage->m_keysMap, keyCRC32, NULL);
  1907. if (entry != NULL)
  1908. {
  1909. outGameInfo.szCharacterName = entry->sCharacterName.c_str();
  1910. outGameInfo.sUtf8TranslatedText = entry->GetTranslatedText(m_pLanguage);
  1911. outGameInfo.bUseSubtitle = (entry->flags & SLocalizedStringEntry::USE_SUBTITLE);
  1912. return true;
  1913. }
  1914. else
  1915. {
  1916. return false;
  1917. }
  1918. }
  1919. }
  1920. //////////////////////////////////////////////////////////////////////////
  1921. bool CLocalizedStringsManager::GetLocalizedInfoByKey(const char* sKey, SLocalizedSoundInfoGame* pOutSoundInfo)
  1922. {
  1923. assert(sKey);
  1924. if (!m_pLanguage || !sKey || !pOutSoundInfo)
  1925. {
  1926. return false;
  1927. }
  1928. bool bResult = false;
  1929. uint32 keyCRC32 = AZ::Crc32(sKey);
  1930. {
  1931. AutoLock lock(m_cs); //Lock here, to prevent strings etc being modified underneath this lookup
  1932. const SLocalizedStringEntry* pEntry = stl::find_in_map(m_pLanguage->m_keysMap, keyCRC32, NULL);
  1933. if (pEntry != NULL)
  1934. {
  1935. bResult = true;
  1936. pOutSoundInfo->szCharacterName = pEntry->sCharacterName.c_str();
  1937. pOutSoundInfo->sUtf8TranslatedText = pEntry->GetTranslatedText(m_pLanguage);
  1938. //pOutSoundInfo->sOriginalActorLine = pEntry->sOriginalActorLine.c_str();
  1939. //pOutSoundInfo->sTranslatedActorLine = pEntry->swTranslatedActorLine.c_str();
  1940. //pOutSoundInfo->sOriginalText = pEntry->sOriginalText;
  1941. // original Character
  1942. pOutSoundInfo->sSoundEvent = pEntry->sPrototypeSoundEvent.c_str();
  1943. pOutSoundInfo->fVolume = CryConvertHalfToFloat(pEntry->fVolume);
  1944. pOutSoundInfo->fRadioRatio = CryConvertHalfToFloat(pEntry->fRadioRatio);
  1945. pOutSoundInfo->bUseSubtitle = (pEntry->flags & SLocalizedStringEntry::USE_SUBTITLE) != 0;
  1946. pOutSoundInfo->bIsDirectRadio = (pEntry->flags & SLocalizedStringEntry::IS_DIRECTED_RADIO) != 0;
  1947. pOutSoundInfo->bIsIntercepted = (pEntry->flags & SLocalizedStringEntry::IS_INTERCEPTED) != 0;
  1948. //SoundMoods
  1949. if (pOutSoundInfo->nNumSoundMoods >= pEntry->SoundMoods.size())
  1950. {
  1951. // enough space to copy data
  1952. int i = 0;
  1953. for (; i < pEntry->SoundMoods.size(); ++i)
  1954. {
  1955. pOutSoundInfo->pSoundMoods[i].sName = pEntry->SoundMoods[i].sName;
  1956. pOutSoundInfo->pSoundMoods[i].fValue = pEntry->SoundMoods[i].fValue;
  1957. }
  1958. // if mode is available fill it with default
  1959. for (; i < pOutSoundInfo->nNumSoundMoods; ++i)
  1960. {
  1961. pOutSoundInfo->pSoundMoods[i].sName = "";
  1962. pOutSoundInfo->pSoundMoods[i].fValue = 0.0f;
  1963. }
  1964. pOutSoundInfo->nNumSoundMoods = pEntry->SoundMoods.size();
  1965. }
  1966. else
  1967. {
  1968. // not enough memory, say what is needed
  1969. pOutSoundInfo->nNumSoundMoods = pEntry->SoundMoods.size();
  1970. bResult = (pOutSoundInfo->pSoundMoods == NULL); // only report error if memory was provided but is too small
  1971. }
  1972. //EventParameters
  1973. if (pOutSoundInfo->nNumEventParameters >= pEntry->EventParameters.size())
  1974. {
  1975. // enough space to copy data
  1976. int i = 0;
  1977. for (; i < pEntry->EventParameters.size(); ++i)
  1978. {
  1979. pOutSoundInfo->pEventParameters[i].sName = pEntry->EventParameters[i].sName;
  1980. pOutSoundInfo->pEventParameters[i].fValue = pEntry->EventParameters[i].fValue;
  1981. }
  1982. // if mode is available fill it with default
  1983. for (; i < pOutSoundInfo->nNumEventParameters; ++i)
  1984. {
  1985. pOutSoundInfo->pEventParameters[i].sName = "";
  1986. pOutSoundInfo->pEventParameters[i].fValue = 0.0f;
  1987. }
  1988. pOutSoundInfo->nNumEventParameters = pEntry->EventParameters.size();
  1989. }
  1990. else
  1991. {
  1992. // not enough memory, say what is needed
  1993. pOutSoundInfo->nNumEventParameters = pEntry->EventParameters.size();
  1994. bResult = (pOutSoundInfo->pSoundMoods == NULL); // only report error if memory was provided but is too small
  1995. }
  1996. }
  1997. }
  1998. return bResult;
  1999. }
  2000. //////////////////////////////////////////////////////////////////////////
  2001. int CLocalizedStringsManager::GetLocalizedStringCount()
  2002. {
  2003. if (!m_pLanguage)
  2004. {
  2005. return 0;
  2006. }
  2007. return static_cast<int>(m_pLanguage->m_vLocalizedStrings.size());
  2008. }
  2009. //////////////////////////////////////////////////////////////////////////
  2010. bool CLocalizedStringsManager::GetLocalizedInfoByIndex(int nIndex, SLocalizedInfoGame& outGameInfo)
  2011. {
  2012. if (!m_pLanguage)
  2013. {
  2014. return false;
  2015. }
  2016. const std::vector<SLocalizedStringEntry*>& entryVec = m_pLanguage->m_vLocalizedStrings;
  2017. if (nIndex < 0 || nIndex >= (int)entryVec.size())
  2018. {
  2019. return false;
  2020. }
  2021. const SLocalizedStringEntry* pEntry = entryVec[nIndex];
  2022. outGameInfo.szCharacterName = pEntry->sCharacterName.c_str();
  2023. outGameInfo.sUtf8TranslatedText = pEntry->GetTranslatedText(m_pLanguage);
  2024. outGameInfo.bUseSubtitle = (pEntry->flags & SLocalizedStringEntry::USE_SUBTITLE);
  2025. return true;
  2026. }
  2027. //////////////////////////////////////////////////////////////////////////
  2028. bool CLocalizedStringsManager::GetLocalizedInfoByIndex(int nIndex, SLocalizedInfoEditor& outEditorInfo)
  2029. {
  2030. if (!m_pLanguage)
  2031. {
  2032. return false;
  2033. }
  2034. const std::vector<SLocalizedStringEntry*>& entryVec = m_pLanguage->m_vLocalizedStrings;
  2035. if (nIndex < 0 || nIndex >= (int)entryVec.size())
  2036. {
  2037. return false;
  2038. }
  2039. const SLocalizedStringEntry* pEntry = entryVec[nIndex];
  2040. outEditorInfo.szCharacterName = pEntry->sCharacterName.c_str();
  2041. outEditorInfo.sUtf8TranslatedText = pEntry->GetTranslatedText(m_pLanguage);
  2042. assert(pEntry->pEditorExtension != NULL);
  2043. outEditorInfo.sKey = pEntry->pEditorExtension->sKey.c_str();
  2044. outEditorInfo.sOriginalActorLine = pEntry->pEditorExtension->sOriginalActorLine.c_str();
  2045. outEditorInfo.sUtf8TranslatedActorLine = pEntry->pEditorExtension->sUtf8TranslatedActorLine.c_str();
  2046. //outEditorInfo.sOriginalText = pEntry->sOriginalText;
  2047. outEditorInfo.sOriginalCharacterName = pEntry->pEditorExtension->sOriginalCharacterName.c_str();
  2048. outEditorInfo.nRow = pEntry->pEditorExtension->nRow;
  2049. outEditorInfo.bUseSubtitle = (pEntry->flags & SLocalizedStringEntry::USE_SUBTITLE);
  2050. return true;
  2051. }
  2052. //////////////////////////////////////////////////////////////////////////
  2053. bool CLocalizedStringsManager::GetSubtitle(const char* sKeyOrLabel, AZStd::string& outSubtitle, bool bForceSubtitle)
  2054. {
  2055. assert(sKeyOrLabel);
  2056. if (!m_pLanguage || !sKeyOrLabel || !*sKeyOrLabel)
  2057. {
  2058. return false;
  2059. }
  2060. if (*sKeyOrLabel == '@')
  2061. {
  2062. ++sKeyOrLabel;
  2063. }
  2064. uint32 keyCRC32 = AZ::Crc32(sKeyOrLabel);
  2065. {
  2066. AutoLock lock(m_cs); //Lock here, to prevent strings etc being modified underneath this lookup
  2067. const SLocalizedStringEntry* pEntry = stl::find_in_map(m_pLanguage->m_keysMap, keyCRC32, NULL);
  2068. if (pEntry != NULL)
  2069. {
  2070. if ((pEntry->flags & SLocalizedStringEntry::USE_SUBTITLE) == false && !bForceSubtitle)
  2071. {
  2072. return false;
  2073. }
  2074. // TODO verify that no fallback is needed
  2075. outSubtitle = pEntry->GetTranslatedText(m_pLanguage);
  2076. if (outSubtitle.empty() == true)
  2077. {
  2078. if (pEntry->pEditorExtension != NULL && pEntry->pEditorExtension->sUtf8TranslatedActorLine.empty() == false)
  2079. {
  2080. outSubtitle = pEntry->pEditorExtension->sUtf8TranslatedActorLine;
  2081. }
  2082. else if (pEntry->pEditorExtension != NULL && pEntry->pEditorExtension->sOriginalText.empty() == false)
  2083. {
  2084. outSubtitle = pEntry->pEditorExtension->sOriginalText;
  2085. }
  2086. else if (pEntry->pEditorExtension != NULL && pEntry->pEditorExtension->sOriginalActorLine.empty() == false)
  2087. {
  2088. outSubtitle = pEntry->pEditorExtension->sOriginalActorLine;
  2089. }
  2090. }
  2091. return true;
  2092. }
  2093. return false;
  2094. }
  2095. }
  2096. void InternalFormatStringMessage(AZStd::string& outString, const AZStd::string& sString, const char** sParams, int nParams)
  2097. {
  2098. static const char token = '%';
  2099. static const char tokens1[2] = { token, '\0' };
  2100. static const char tokens2[3] = { token, token, '\0' };
  2101. int maxArgUsed = 0;
  2102. size_t lastPos = 0;
  2103. size_t curPos = 0;
  2104. const int sourceLen = static_cast<int>(sString.length());
  2105. while (true)
  2106. {
  2107. auto foundPos = sString.find(token, curPos);
  2108. if (foundPos != AZStd::string::npos)
  2109. {
  2110. if (foundPos + 1 < sourceLen)
  2111. {
  2112. const int nArg = sString[foundPos + 1] - '1';
  2113. if (nArg >= 0 && nArg <= 9)
  2114. {
  2115. if (nArg < nParams)
  2116. {
  2117. outString.append(sString, lastPos, foundPos - lastPos);
  2118. outString.append(sParams[nArg]);
  2119. curPos = foundPos + 2;
  2120. lastPos = curPos;
  2121. maxArgUsed = std::max(maxArgUsed, nArg);
  2122. }
  2123. else
  2124. {
  2125. AZStd::string tmp(sString);
  2126. AZ::StringFunc::Replace(tmp, tokens1, tokens2);
  2127. if constexpr (sizeof(*tmp.c_str()) == sizeof(char))
  2128. {
  2129. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "Parameter for argument %d is missing. [%s]", nArg + 1, (const char*)tmp.c_str());
  2130. }
  2131. else
  2132. {
  2133. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "Parameter for argument %d is missing. [%S]", nArg + 1, (const wchar_t*)tmp.c_str());
  2134. }
  2135. curPos = foundPos + 1;
  2136. }
  2137. }
  2138. else
  2139. {
  2140. curPos = foundPos + 1;
  2141. }
  2142. }
  2143. else
  2144. {
  2145. curPos = foundPos + 1;
  2146. }
  2147. }
  2148. else
  2149. {
  2150. outString.append(sString, lastPos, sourceLen - lastPos);
  2151. break;
  2152. }
  2153. }
  2154. }
  2155. void InternalFormatStringMessage(AZStd::string& outString, const AZStd::string& sString, const char* param1, const char* param2 = 0, const char* param3 = 0, const char* param4 = 0)
  2156. {
  2157. static const int MAX_PARAMS = 4;
  2158. const char* params[MAX_PARAMS] = { param1, param2, param3, param4 };
  2159. int nParams = 0;
  2160. while (nParams < MAX_PARAMS && params[nParams])
  2161. {
  2162. ++nParams;
  2163. }
  2164. InternalFormatStringMessage(outString, sString, params, nParams);
  2165. }
  2166. //////////////////////////////////////////////////////////////////////////
  2167. void CLocalizedStringsManager::FormatStringMessage_List(AZStd::string& outString, const AZStd::string& sString, const char** sParams, int nParams)
  2168. {
  2169. InternalFormatStringMessage(outString, sString, sParams, nParams);
  2170. }
  2171. //////////////////////////////////////////////////////////////////////////
  2172. void CLocalizedStringsManager::FormatStringMessage(AZStd::string& outString, const AZStd::string& sString, const char* param1, const char* param2, const char* param3, const char* param4)
  2173. {
  2174. InternalFormatStringMessage(outString, sString, param1, param2, param3, param4);
  2175. }
  2176. //////////////////////////////////////////////////////////////////////////
  2177. #if defined (WIN32) || defined(WIN64)
  2178. namespace
  2179. {
  2180. struct LanguageID
  2181. {
  2182. const char* language;
  2183. unsigned long lcID;
  2184. };
  2185. LanguageID languageIDArray[] =
  2186. {
  2187. { "en-US", 0x0409 }, // English (USA)
  2188. { "en-GB", 0x0809 }, // English (UK)
  2189. { "de-DE", 0x0407 }, // German
  2190. { "ru-RU", 0x0419 }, // Russian (Russia)
  2191. { "pl-PL", 0x0415 }, // Polish
  2192. { "tr-TR", 0x041f }, // Turkish
  2193. { "es-ES", 0x0c0a }, // Spanish (Spain)
  2194. { "es-MX", 0x080a }, // Spanish (Mexico)
  2195. { "fr-FR", 0x040c }, // French (France)
  2196. { "fr-CA", 0x0c0c }, // French (Canada)
  2197. { "it-IT", 0x0410 }, // Italian
  2198. { "pt-PT", 0x0816 }, // Portugese (Portugal)
  2199. { "pt-BR", 0x0416 }, // Portugese (Brazil)
  2200. { "ja-JP", 0x0411 }, // Japanese
  2201. { "ko-KR", 0x0412 }, // Korean
  2202. { "zh-CHT", 0x0804 }, // Traditional Chinese
  2203. { "zh-CHS", 0x0804 }, // Simplified Chinese
  2204. { "nl-NL", 0x0413 }, // Dutch (The Netherlands)
  2205. { "fi-FI", 0x040b }, // Finnish
  2206. { "sv-SE", 0x041d }, // Swedish
  2207. { "cs-CZ", 0x0405 }, // Czech
  2208. { "no-NO", 0x0414 }, // Norwegian (Norway)
  2209. { "ar-SA", 0x0401 }, // Arabic (Saudi Arabia)
  2210. { "da-DK", 0x0406 }, // Danish (Denmark)
  2211. };
  2212. const size_t numLanguagesIDs = sizeof(languageIDArray) / sizeof(languageIDArray[0]);
  2213. LanguageID GetLanguageID(const char* language)
  2214. {
  2215. // default is English (US)
  2216. const LanguageID defaultLanguage = { "en-US", 0x0409 };
  2217. for (int i = 0; i < numLanguagesIDs; ++i)
  2218. {
  2219. if (_stricmp(language, languageIDArray[i].language) == 0)
  2220. {
  2221. return languageIDArray[i];
  2222. }
  2223. }
  2224. return defaultLanguage;
  2225. }
  2226. LanguageID g_currentLanguageID = { 0, 0 };
  2227. };
  2228. #endif
  2229. void CLocalizedStringsManager::InternalSetCurrentLanguage(CLocalizedStringsManager::SLanguage* pLanguage)
  2230. {
  2231. m_pLanguage = pLanguage;
  2232. #if defined (WIN32) || defined(WIN64)
  2233. if (m_pLanguage != 0)
  2234. {
  2235. g_currentLanguageID = GetLanguageID(m_pLanguage->sLanguage.c_str());
  2236. }
  2237. else
  2238. {
  2239. g_currentLanguageID.language = 0;
  2240. g_currentLanguageID.lcID = 0;
  2241. }
  2242. #endif
  2243. // TODO: on Linux based systems we should now set the locale
  2244. // Enabling this on windows something seems to get corrupted...
  2245. // on Windows we always use Windows Platform Functions, which use the lcid
  2246. #if 0
  2247. if (g_currentLanguageID.language)
  2248. {
  2249. const char* currentLocale = setlocale(LC_ALL, 0);
  2250. if (0 == setlocale(LC_ALL, g_currentLanguageID.language))
  2251. {
  2252. setlocale(LC_ALL, currentLocale);
  2253. }
  2254. }
  2255. else
  2256. {
  2257. setlocale(LC_ALL, "C");
  2258. }
  2259. #endif
  2260. ReloadData();
  2261. if (gEnv->pCryFont)
  2262. {
  2263. gEnv->pCryFont->OnLanguageChanged();
  2264. }
  2265. }
  2266. void CLocalizedStringsManager::LocalizeDuration(int seconds, AZStd::string& outDurationString)
  2267. {
  2268. int s = seconds;
  2269. int d, h, m;
  2270. d = s / 86400;
  2271. s -= d * 86400;
  2272. h = s / 3600;
  2273. s -= h * 3600;
  2274. m = s / 60;
  2275. s = s - m * 60;
  2276. AZStd::string str;
  2277. if (d > 1)
  2278. {
  2279. str = AZStd::string::format("%d @ui_days %02d:%02d:%02d", d, h, m, s);
  2280. }
  2281. else if (d > 0)
  2282. {
  2283. str = AZStd::string::format("%d @ui_day %02d:%02d:%02d", d, h, m, s);
  2284. }
  2285. else if (h > 0)
  2286. {
  2287. str = AZStd::string::format("%02d:%02d:%02d", h, m, s);
  2288. }
  2289. else
  2290. {
  2291. str = AZStd::string::format("%02d:%02d", m, s);
  2292. }
  2293. LocalizeString_s(str, outDurationString);
  2294. }
  2295. void CLocalizedStringsManager::LocalizeNumber(int number, AZStd::string& outNumberString)
  2296. {
  2297. if (number == 0)
  2298. {
  2299. outNumberString.assign("0");
  2300. return;
  2301. }
  2302. outNumberString.assign("");
  2303. int n = abs(number);
  2304. AZStd::string separator;
  2305. AZStd::fixed_string<64> tmp;
  2306. LocalizeString_ch("@ui_thousand_separator", separator);
  2307. while (n > 0)
  2308. {
  2309. int a = n / 1000;
  2310. int b = n - (a * 1000);
  2311. if (a > 0)
  2312. {
  2313. tmp = AZStd::string::format("%s%03d%s", separator.c_str(), b, tmp.c_str());
  2314. }
  2315. else
  2316. {
  2317. tmp = AZStd::string::format("%d%s", b, tmp.c_str());
  2318. }
  2319. n = a;
  2320. }
  2321. if (number < 0)
  2322. {
  2323. tmp = AZStd::string::format("-%s", tmp.c_str());
  2324. }
  2325. outNumberString.assign(tmp.c_str());
  2326. }
  2327. void CLocalizedStringsManager::LocalizeNumber_Decimal(float number, int decimals, AZStd::string& outNumberString)
  2328. {
  2329. if (number == 0.0f)
  2330. {
  2331. AZStd::fixed_string<64> tmp;
  2332. tmp = AZStd::fixed_string<64>::format("%.*f", decimals, number);
  2333. outNumberString.assign(tmp.c_str());
  2334. return;
  2335. }
  2336. outNumberString.assign("");
  2337. AZStd::string commaSeparator;
  2338. LocalizeString_ch("@ui_decimal_separator", commaSeparator);
  2339. float f = number > 0.0f ? number : -number;
  2340. int d = (int)f;
  2341. AZStd::string intPart;
  2342. LocalizeNumber(d, intPart);
  2343. float decimalsOnly = f - (float)d;
  2344. int decimalsAsInt = aznumeric_cast<int>(int_round(decimalsOnly * pow(10.0f, decimals)));
  2345. AZStd::fixed_string<64> tmp;
  2346. tmp = AZStd::fixed_string<64>::format("%s%s%0*d", intPart.c_str(), commaSeparator.c_str(), decimals, decimalsAsInt);
  2347. outNumberString.assign(tmp.c_str());
  2348. }
  2349. bool CLocalizedStringsManager::ProjectUsesLocalization() const
  2350. {
  2351. ICVar* pCVar = gEnv->pConsole->GetCVar("sys_localization_folder");
  2352. CRY_ASSERT_MESSAGE(pCVar,
  2353. "Console variable 'sys_localization_folder' not defined! "
  2354. "This was previously defined at startup in CSystem::CreateSystemVars.");
  2355. // If game.cfg didn't provide a sys_localization_folder, we'll assume that
  2356. // the project doesn't want to use localization features.
  2357. return (pCVar->GetFlags() & VF_WASINCONFIG) != 0;
  2358. }
  2359. #if defined (WIN32) || defined(WIN64)
  2360. namespace
  2361. {
  2362. void UnixTimeToFileTime(time_t unixtime, FILETIME* filetime)
  2363. {
  2364. LONGLONG longlong = (unixtime * 10000000LL) + 116444736000000000LL;
  2365. filetime->dwLowDateTime = (DWORD) longlong;
  2366. filetime->dwHighDateTime = (DWORD)(longlong >> 32);
  2367. }
  2368. void UnixTimeToSystemTime(time_t unixtime, SYSTEMTIME* systemtime)
  2369. {
  2370. FILETIME filetime;
  2371. UnixTimeToFileTime(unixtime, &filetime);
  2372. FileTimeToSystemTime(&filetime, systemtime);
  2373. }
  2374. }
  2375. void CLocalizedStringsManager::LocalizeTime(time_t t, bool bMakeLocalTime, bool bShowSeconds, AZStd::string& outTimeString)
  2376. {
  2377. if (bMakeLocalTime)
  2378. {
  2379. struct tm thetime;
  2380. localtime_s(&thetime, &t);
  2381. t = DateToSecondsUTC(thetime);
  2382. }
  2383. outTimeString.clear();
  2384. LCID lcID = g_currentLanguageID.lcID ? g_currentLanguageID.lcID : LOCALE_USER_DEFAULT;
  2385. DWORD flags = bShowSeconds == false ? TIME_NOSECONDS : 0;
  2386. SYSTEMTIME systemTime;
  2387. UnixTimeToSystemTime(t, &systemTime);
  2388. int len = ::GetTimeFormatW(lcID, flags, &systemTime, 0, 0, 0);
  2389. if (len > 0)
  2390. {
  2391. // len includes terminating null!
  2392. AZStd::fixed_wstring<256> tmpString;
  2393. tmpString.resize(len);
  2394. ::GetTimeFormatW(lcID, flags, &systemTime, 0, (wchar_t*) tmpString.c_str(), len);
  2395. AZStd::to_string(outTimeString, tmpString.data());
  2396. }
  2397. }
  2398. void CLocalizedStringsManager::LocalizeDate(time_t t, bool bMakeLocalTime, bool bShort, bool bIncludeWeekday, AZStd::string& outDateString)
  2399. {
  2400. if (bMakeLocalTime)
  2401. {
  2402. struct tm thetime;
  2403. localtime_s(&thetime, &t);
  2404. t = DateToSecondsUTC(thetime);
  2405. }
  2406. outDateString.resize(0);
  2407. LCID lcID = g_currentLanguageID.lcID ? g_currentLanguageID.lcID : LOCALE_USER_DEFAULT;
  2408. SYSTEMTIME systemTime;
  2409. UnixTimeToSystemTime(t, &systemTime);
  2410. // len includes terminating null!
  2411. AZStd::fixed_wstring<256> tmpString;
  2412. if (bIncludeWeekday)
  2413. {
  2414. // Get name of day
  2415. int len = ::GetDateFormatW(lcID, 0, &systemTime, L"ddd", 0, 0);
  2416. if (len > 0)
  2417. {
  2418. // len includes terminating null!
  2419. tmpString.resize(len);
  2420. ::GetDateFormatW(lcID, 0, &systemTime, L"ddd", (wchar_t*) tmpString.c_str(), len);
  2421. AZStd::string utf8;
  2422. AZStd::to_string(utf8, tmpString.data());
  2423. outDateString.append(utf8);
  2424. outDateString.append(" ");
  2425. }
  2426. }
  2427. DWORD flags = bShort ? DATE_SHORTDATE : DATE_LONGDATE;
  2428. int len = ::GetDateFormatW(lcID, flags, &systemTime, 0, 0, 0);
  2429. if (len > 0)
  2430. {
  2431. // len includes terminating null!
  2432. tmpString.resize(len);
  2433. ::GetDateFormatW(lcID, flags, &systemTime, 0, (wchar_t*) tmpString.c_str(), len);
  2434. AZStd::string utf8;
  2435. AZStd::to_string(utf8, tmpString.data());
  2436. outDateString.append(utf8);
  2437. }
  2438. }
  2439. #else // #if defined (WIN32) || defined(WIN64)
  2440. void CLocalizedStringsManager::LocalizeTime(time_t t, bool bMakeLocalTime, bool bShowSeconds, AZStd::string& outTimeString)
  2441. {
  2442. struct tm theTime;
  2443. if (bMakeLocalTime)
  2444. {
  2445. #if AZ_TRAIT_USE_SECURE_CRT_FUNCTIONS
  2446. localtime_s(&theTime, &t);
  2447. #else
  2448. theTime = *localtime(&t);
  2449. #endif
  2450. }
  2451. else
  2452. {
  2453. #if AZ_TRAIT_USE_SECURE_CRT_FUNCTIONS
  2454. gmtime_s(&theTime, &t);
  2455. #else
  2456. theTime = *gmtime(&t);
  2457. #endif
  2458. }
  2459. wchar_t buf[256];
  2460. const size_t bufSize = sizeof(buf) / sizeof(buf[0]);
  2461. wcsftime(buf, bufSize, bShowSeconds ? L"%#X" : L"%X", &theTime);
  2462. buf[bufSize - 1] = 0;
  2463. AZStd::to_string(outTimeString, buf);
  2464. }
  2465. void CLocalizedStringsManager::LocalizeDate(time_t t, bool bMakeLocalTime, bool bShort, bool bIncludeWeekday, AZStd::string& outDateString)
  2466. {
  2467. struct tm theTime;
  2468. if (bMakeLocalTime)
  2469. {
  2470. #if AZ_TRAIT_USE_SECURE_CRT_FUNCTIONS
  2471. localtime_s(&theTime, &t);
  2472. #else
  2473. theTime = *localtime(&t);
  2474. #endif
  2475. }
  2476. else
  2477. {
  2478. #if AZ_TRAIT_USE_SECURE_CRT_FUNCTIONS
  2479. gmtime_s(&theTime, &t);
  2480. #else
  2481. theTime = *gmtime(&t);
  2482. #endif
  2483. }
  2484. wchar_t buf[256];
  2485. const size_t bufSize = sizeof(buf) / sizeof(buf[0]);
  2486. const wchar_t* format = bShort ? (bIncludeWeekday ? L"%a %x" : L"%x") : L"%#x"; // long format always contains Weekday name
  2487. wcsftime(buf, bufSize, format, &theTime);
  2488. buf[bufSize - 1] = 0;
  2489. AZStd::to_string(outDateString, buf);
  2490. }
  2491. #endif