lang.cpp 15 KB

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