lang.cpp 15 KB

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