lang.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platform.h"
  23. #include "core/stream/stream.h"
  24. #include "core/stream/fileStream.h"
  25. #include "console/console.h"
  26. #include "console/consoleInternal.h"
  27. #include "console/ast.h"
  28. #include "console/compiler.h"
  29. #include "core/util/safeDelete.h"
  30. #include "console/engineAPI.h"
  31. #include "i18n/lang.h"
  32. //-----------------------------------------------------------------------------
  33. // LangFile Class
  34. //-----------------------------------------------------------------------------
  35. LangFile::LangFile(const UTF8 *langName /* = NULL */)
  36. {
  37. VECTOR_SET_ASSOCIATION( mStringTable );
  38. if(langName)
  39. {
  40. dsize_t langNameLen = dStrlen(langName) + 1;
  41. mLangName = new UTF8 [langNameLen];
  42. dStrcpy(mLangName, langName, langNameLen);
  43. }
  44. else
  45. mLangName = NULL;
  46. mLangFile = NULL;
  47. }
  48. LangFile::~LangFile()
  49. {
  50. // [tom, 3/1/2005] Note: If this is freed in FreeTable() then when the file
  51. // is loaded, the language name will be blitzed.
  52. // Programming after 36 hours without sleep != good.
  53. SAFE_DELETE_ARRAY(mLangName);
  54. SAFE_DELETE_ARRAY(mLangFile);
  55. freeTable();
  56. }
  57. void LangFile::freeTable()
  58. {
  59. U32 i;
  60. for(i = 0;i < mStringTable.size();i++)
  61. {
  62. if(mStringTable[i])
  63. delete [] mStringTable[i];
  64. }
  65. mStringTable.clear();
  66. }
  67. bool LangFile::load(const UTF8 *filename)
  68. {
  69. FileStream * stream;
  70. if((stream = FileStream::createAndOpen( filename, Torque::FS::File::Read )) == NULL)
  71. return false;
  72. bool ret = load(stream);
  73. delete stream;
  74. return ret;
  75. }
  76. bool LangFile::load(Stream *s)
  77. {
  78. freeTable();
  79. while (s->getStatus() == Stream::Ok)
  80. {
  81. char buf[2048];
  82. s->readLongString(2048, buf);
  83. if (s->getStatus() == Stream::Ok)
  84. addString((const UTF8*)buf);
  85. }
  86. return true;
  87. }
  88. bool LangFile::save(const UTF8 *filename)
  89. {
  90. FileStream *fs;
  91. if(!isLoaded())
  92. return false;
  93. if((fs = FileStream::createAndOpen( filename, Torque::FS::File::Write )) == NULL)
  94. return false;
  95. bool ret = save(fs);
  96. delete fs;
  97. return ret;
  98. }
  99. bool LangFile::save(Stream *s)
  100. {
  101. if (!isLoaded())
  102. return false;
  103. U32 i;
  104. for (i = 0; i < mStringTable.size(); i++)
  105. {
  106. s->writeLongString(2048, (char*)mStringTable[i]); //irei1as_ lang
  107. }
  108. return true;
  109. }
  110. const UTF8 * LangFile::getString(U32 id)
  111. {
  112. if(id == LANG_INVALID_ID || id >= mStringTable.size())
  113. return NULL;
  114. return mStringTable[id];
  115. }
  116. U32 LangFile::addString(const UTF8 *str)
  117. {
  118. dsize_t newstrLen = dStrlen(str) + 1;
  119. UTF8 *newstr = new UTF8 [newstrLen];
  120. dStrcpy(newstr, str, newstrLen);
  121. mStringTable.push_back(newstr);
  122. return mStringTable.size() - 1;
  123. }
  124. void LangFile::setString(U32 id, const UTF8 *str)
  125. {
  126. if(id >= mStringTable.size())
  127. {
  128. U32 oldsize = mStringTable.size();
  129. mStringTable.setSize(id+1);
  130. for( U32 i=oldsize; i<mStringTable.size(); ++i )
  131. {
  132. mStringTable[i] = NULL;
  133. }
  134. }
  135. SAFE_DELETE_ARRAY(mStringTable[id]);
  136. dsize_t newstrLen = dStrlen(str) + 1;
  137. UTF8 *newstr = new UTF8 [newstrLen];
  138. dStrcpy(newstr, str, newstrLen);
  139. mStringTable[id] = newstr;
  140. }
  141. void LangFile::setLangName(const UTF8 *newName)
  142. {
  143. if(mLangName)
  144. delete [] mLangName;
  145. dsize_t langNameLen = dStrlen(newName) + 1;
  146. mLangName = new UTF8 [langNameLen];
  147. dStrcpy(mLangName, newName, langNameLen);
  148. }
  149. void LangFile::setLangFile(const UTF8 *langFile)
  150. {
  151. if(mLangFile)
  152. delete [] mLangFile;
  153. dsize_t langFileLen = dStrlen(langFile) + 1;
  154. mLangFile = new UTF8 [langFileLen];
  155. dStrcpy(mLangFile, langFile, langFileLen);
  156. }
  157. bool LangFile::activateLanguage()
  158. {
  159. if(isLoaded())
  160. return true;
  161. if(mLangFile)
  162. {
  163. return load(mLangFile);
  164. }
  165. return false;
  166. }
  167. void LangFile::deactivateLanguage()
  168. {
  169. if(mLangFile && isLoaded())
  170. freeTable();
  171. }
  172. //-----------------------------------------------------------------------------
  173. // LangTable Class
  174. //-----------------------------------------------------------------------------
  175. IMPLEMENT_CONOBJECT(LangTable);
  176. ConsoleDocClass( LangTable,
  177. "@brief Provides the code necessary to handle the low level management "
  178. "of the string tables for localization\n\n"
  179. "One LangTable is created for each mod, as well as one for the C++ code. "
  180. "LangTable is responsible for obtaining the correct strings from each "
  181. "and relaying it to the appropriate controls.\n\n"
  182. "@see Localization for a full description\n\n"
  183. "@ingroup Localization\n"
  184. );
  185. LangTable::LangTable() : mDefaultLang(-1), mCurrentLang(-1)
  186. {
  187. VECTOR_SET_ASSOCIATION( mLangTable );
  188. }
  189. LangTable::~LangTable()
  190. {
  191. S32 i;
  192. for(i = 0;i < mLangTable.size();i++)
  193. {
  194. if(mLangTable[i])
  195. delete mLangTable[i];
  196. }
  197. mLangTable.clear();
  198. }
  199. S32 LangTable::addLanguage(LangFile *lang, const UTF8 *name /* = NULL */)
  200. {
  201. if(name)
  202. lang->setLangName(name);
  203. mLangTable.push_back(lang);
  204. if(mDefaultLang == -1)
  205. setDefaultLanguage(mLangTable.size() - 1);
  206. if(mCurrentLang == -1)
  207. setCurrentLanguage(mLangTable.size() - 1);
  208. return mLangTable.size() - 1;
  209. }
  210. S32 LangTable::addLanguage(const UTF8 *filename, const UTF8 *name /* = NULL */)
  211. {
  212. LangFile * lang = new LangFile(name);
  213. if(Torque::FS::IsFile(filename))
  214. {
  215. lang->setLangFile(filename);
  216. S32 ret = addLanguage(lang);
  217. if(ret >= 0)
  218. return ret;
  219. }
  220. delete lang;
  221. return -1;
  222. }
  223. const UTF8 *LangTable::getString(const U32 id) const
  224. {
  225. const UTF8 *s = NULL;
  226. if(mCurrentLang >= 0)
  227. s = mLangTable[mCurrentLang]->getString(id);
  228. if(s == NULL && mDefaultLang >= 0 && mDefaultLang != mCurrentLang)
  229. s = mLangTable[mDefaultLang]->getString(id);
  230. return s;
  231. }
  232. const U32 LangTable::getStringLength(const U32 id) const
  233. {
  234. const UTF8 *s = getString(id);
  235. if(s)
  236. return dStrlen(s);
  237. return 0;
  238. }
  239. void LangTable::setDefaultLanguage(S32 langid)
  240. {
  241. if(langid >= 0 && langid < mLangTable.size())
  242. {
  243. if(mLangTable[langid]->activateLanguage())
  244. {
  245. if(mDefaultLang >= 0)
  246. mLangTable[mDefaultLang]->deactivateLanguage();
  247. mDefaultLang = langid;
  248. }
  249. }
  250. }
  251. void LangTable::setCurrentLanguage(S32 langid)
  252. {
  253. if(langid >= 0 && langid < mLangTable.size())
  254. {
  255. if(mLangTable[langid]->activateLanguage())
  256. {
  257. Con::printf("Language %s [%s] activated.", mLangTable[langid]->getLangName(), mLangTable[langid]->getLangFile());
  258. if(mCurrentLang >= 0 && mCurrentLang != mDefaultLang)
  259. {
  260. mLangTable[mCurrentLang]->deactivateLanguage();
  261. Con::printf("Language %s [%s] deactivated.", mLangTable[mCurrentLang]->getLangName(), mLangTable[mCurrentLang]->getLangFile());
  262. }
  263. mCurrentLang = langid;
  264. }
  265. }
  266. }
  267. //-----------------------------------------------------------------------------
  268. // LangTable Console Methods
  269. //-----------------------------------------------------------------------------
  270. DefineEngineMethod(LangTable, addLanguage, S32, (String filename, String languageName), ("", ""),
  271. "(string filename, [string languageName])"
  272. "@brief Adds a language to the table\n\n"
  273. "@param filename Name and path to the language file\n"
  274. "@param languageName Optional name to assign to the new language entry\n\n"
  275. "@return True If file was successfully found and language created\n"
  276. )
  277. {
  278. UTF8 scriptFilenameBuffer[1024];
  279. Con::expandScriptFilename((char*)scriptFilenameBuffer, sizeof(scriptFilenameBuffer), filename);
  280. return object->addLanguage(scriptFilenameBuffer, (const UTF8*)languageName);
  281. }
  282. DefineEngineMethod(LangTable, getString, const char *, (U32 id), ,
  283. "(string filename)"
  284. "@brief Grabs a string from the specified table\n\n"
  285. "If an invalid is passed, the function will attempt to "
  286. "to grab from the default table\n\n"
  287. "@param filename Name of the language table to access\n\n"
  288. "@return Text from the specified language table, \"\" if ID was invalid and default table is not set")
  289. {
  290. const char * str = (const char*)object->getString(id);
  291. if(str != NULL)
  292. {
  293. dsize_t retLen = dStrlen(str) + 1;
  294. char * ret = Con::getReturnBuffer(retLen);
  295. dStrcpy(ret, str, retLen);
  296. return ret;
  297. }
  298. return "";
  299. }
  300. DefineEngineMethod(LangTable, setDefaultLanguage, void, (S32 langId), , "(int language)"
  301. "@brief Sets the default language table\n\n"
  302. "@param language ID of the table\n")
  303. {
  304. object->setDefaultLanguage(langId);
  305. }
  306. DefineEngineMethod(LangTable, setCurrentLanguage, void, (S32 langId), ,
  307. "(int language)"
  308. "@brief Sets the current language table for grabbing text\n\n"
  309. "@param language ID of the table\n")
  310. {
  311. object->setCurrentLanguage(langId);
  312. }
  313. DefineEngineMethod(LangTable, getCurrentLanguage, S32, (), , "()"
  314. "@brief Get the ID of the current language table\n\n"
  315. "@return Numerical ID of the current language table")
  316. {
  317. return object->getCurrentLanguage();
  318. }
  319. DefineEngineMethod(LangTable, getLangName, const char *, (S32 langId), , "(int language)"
  320. "@brief Return the readable name of the language table\n\n"
  321. "@param language Numerical ID of the language table to access\n\n"
  322. "@return String containing the name of the table, NULL if ID was invalid or name was never specified")
  323. {
  324. const char * str = (const char*)object->getLangName(langId);
  325. if(str != NULL)
  326. {
  327. dsize_t retLen = dStrlen(str) + 1;
  328. char * ret = Con::getReturnBuffer(retLen);
  329. dStrcpy(ret, str, retLen);
  330. return ret;
  331. }
  332. return "";
  333. }
  334. DefineEngineMethod(LangTable, getNumLang, S32, (), , "()"
  335. "@brief Used to find out how many languages are in the table\n\n"
  336. "@return Size of the vector containing the languages, numerical")
  337. {
  338. return object->getNumLang();
  339. }
  340. //-----------------------------------------------------------------------------
  341. // Support Functions
  342. //-----------------------------------------------------------------------------
  343. UTF8 *sanitiseVarName(const UTF8 *varName, UTF8 *buffer, U32 bufsize)
  344. {
  345. if(! varName || bufsize < 10) // [tom, 3/3/2005] bufsize check gives room to be lazy below
  346. {
  347. *buffer = 0;
  348. return NULL;
  349. }
  350. dStrcpy(buffer, (const UTF8*)"I18N::", bufsize);
  351. UTF8 *dptr = buffer + 6;
  352. const UTF8 *sptr = varName;
  353. while(*sptr)
  354. {
  355. if(dIsalnum(*sptr))
  356. *dptr++ = *sptr++;
  357. else
  358. {
  359. if(*(dptr - 1) != '_')
  360. *dptr++ = '_';
  361. sptr++;
  362. }
  363. if((dptr - buffer) >= (bufsize - 1))
  364. break;
  365. }
  366. *dptr = 0;
  367. return buffer;
  368. }
  369. UTF8 *getCurrentModVarName(UTF8 *buffer, U32 bufsize)
  370. {
  371. char varName[256];
  372. StringTableEntry cbName = CodeBlock::getCurrentCodeBlockName();
  373. const UTF8 *slash = (const UTF8*)dStrchr(cbName, '/');
  374. if (slash == NULL)
  375. {
  376. Con::errorf("Illegal CodeBlock path detected in sanitiseVarName() (no mod directory): %s", cbName);
  377. return NULL;
  378. }
  379. dStrncpy(varName, cbName, slash - (const UTF8*)cbName);
  380. varName[slash - (const UTF8*)cbName] = 0;
  381. return sanitiseVarName((UTF8*)varName, buffer, bufsize);
  382. }
  383. const LangTable *getCurrentModLangTable()
  384. {
  385. UTF8 saneVarName[256];
  386. if(getCurrentModVarName(saneVarName, sizeof(saneVarName)))
  387. {
  388. const LangTable *lt = dynamic_cast<LangTable *>(Sim::findObject(Con::getIntVariable((const char*)saneVarName)));
  389. return lt;
  390. }
  391. return NULL;
  392. }
  393. const LangTable *getModLangTable(const UTF8 *mod)
  394. {
  395. UTF8 saneVarName[256];
  396. if(sanitiseVarName(mod, saneVarName, sizeof(saneVarName)))
  397. {
  398. const LangTable *lt = dynamic_cast<LangTable *>(Sim::findObject(Con::getIntVariable((const char*)saneVarName)));
  399. return lt;
  400. }
  401. return NULL;
  402. }
  403. //lang_ localization
  404. bool compiledFileNeedsUpdate(UTF8* filename)
  405. {
  406. Torque::Path filePath = Torque::Path(filename);
  407. Torque::FS::FileNodeRef sourceFile = Torque::FS::GetFileNode(filePath);
  408. Torque::Path compiledPath = Torque::Path(filePath);
  409. compiledPath.setExtension("lso");
  410. Torque::FS::FileNodeRef compiledFile = Torque::FS::GetFileNode(compiledPath);
  411. Torque::Time sourceModifiedTime, compiledModifiedTime;
  412. if (sourceFile != NULL)
  413. sourceModifiedTime = sourceFile->getModifiedTime();
  414. if (compiledFile != NULL)
  415. compiledModifiedTime = compiledFile->getModifiedTime();
  416. if (sourceModifiedTime > compiledModifiedTime)
  417. return true;
  418. return false;
  419. }
  420. DefineEngineFunction(CompileLanguage, void, (const char* inputFile, bool createMap), (false),
  421. "@brief Compiles a LSO language file."
  422. " if createIndex is true, will also create languageMap." TORQUE_SCRIPT_EXTENSION " with"
  423. " the global variables for each string index."
  424. " The input file must follow this example layout:"
  425. " TXT_HELLO_WORLD = Hello world in english!")
  426. {
  427. UTF8 scriptFilenameBuffer[1024];
  428. Con::expandScriptFilename((char*)scriptFilenameBuffer, sizeof(scriptFilenameBuffer), inputFile);
  429. if (!Torque::FS::IsFile(scriptFilenameBuffer))
  430. {
  431. Con::errorf("CompileLanguage - file %s not found", scriptFilenameBuffer);
  432. return;
  433. }
  434. FileObject file;
  435. if (!file.readMemory(scriptFilenameBuffer))
  436. {
  437. Con::errorf("CompileLanguage - couldn't read file %s", scriptFilenameBuffer);
  438. return;
  439. }
  440. if (compiledFileNeedsUpdate(scriptFilenameBuffer))
  441. {
  442. FileStream *mapStream = NULL;
  443. if (createMap)
  444. {
  445. Torque::Path mapPath = scriptFilenameBuffer;
  446. mapPath.setFileName("languageMap");
  447. mapPath.setExtension(TORQUE_SCRIPT_EXTENSION);
  448. if ((mapStream = FileStream::createAndOpen(mapPath, Torque::FS::File::Write)) == NULL)
  449. Con::errorf("CompileLanguage - failed creating languageMap." TORQUE_SCRIPT_EXTENSION);
  450. }
  451. LangFile langFile;
  452. const U8* inLine = NULL;
  453. const char* separatorStr = " = ";
  454. S32 stringId = 0;
  455. while ((inLine = file.readLine())[0] != 0)
  456. {
  457. char* line;
  458. chompUTF8BOM((const char *)inLine, &line);
  459. char* div = dStrstr(line, separatorStr);
  460. if (div == NULL)
  461. {
  462. Con::errorf("Separator %s not found in line: %s", separatorStr, line);
  463. Con::errorf("Could not determine string name ID");
  464. continue;
  465. }
  466. *div = 0;
  467. char* text = div + dStrlen(separatorStr);
  468. langFile.addString((const UTF8*)text);
  469. if (mapStream)
  470. {
  471. String mapLine = String::ToString("$%s = %i;", line, stringId);
  472. mapStream->writeLine((const U8*)mapLine.c_str());
  473. String commentLine = String::ToString("// %s", text);
  474. mapStream->writeLine((const U8*)commentLine.c_str());
  475. }
  476. stringId++;
  477. }
  478. Torque::Path lsoPath = scriptFilenameBuffer;
  479. lsoPath.setExtension("lso");
  480. langFile.save(lsoPath.getFullPath());
  481. if (mapStream)
  482. delete mapStream;
  483. }
  484. }
  485. //end lang_ localization