LangDict.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. /*
  2. ===========================================================================
  3. Doom 3 BFG Edition GPL Source Code
  4. Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
  6. Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
  17. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
  18. ===========================================================================
  19. */
  20. #pragma hdrstop
  21. #include "precompiled.h"
  22. // This is the default language dict that the entire system uses, but you can instantiate your own idLangDict classes to manipulate a language dictionary in a tool
  23. idLangDict idLocalization::languageDict;
  24. idCVar lang_maskLocalizedStrings( "lang_maskLocalizedStrings", "0", CVAR_BOOL, "Masks all localized strings to help debugging. When set will replace strings with an equal length of W's and ending in an X. Note: The masking occurs at string table load time." );
  25. /*
  26. ========================
  27. idLocalization::ClearDictionary
  28. ========================
  29. */
  30. void idLocalization::ClearDictionary() {
  31. languageDict.Clear();
  32. }
  33. /*
  34. ========================
  35. idLocalization::LoadDictionary
  36. ========================
  37. */
  38. bool idLocalization::LoadDictionary( const byte * data, int dataLen, const char * fileName ) {
  39. return languageDict.Load( data, dataLen, fileName );
  40. }
  41. /*
  42. ========================
  43. idLocalization::GetString
  44. ========================
  45. */
  46. const char * idLocalization::GetString( const char * inString ) {
  47. return languageDict.GetString( inString );
  48. }
  49. /*
  50. ========================
  51. idLocalization::FindString
  52. ========================
  53. */
  54. const char * idLocalization::FindString( const char * inString ) {
  55. return languageDict.FindString( inString );
  56. }
  57. /*
  58. ========================
  59. idLocalization::VerifyUTF8
  60. ========================
  61. */
  62. utf8Encoding_t idLocalization::VerifyUTF8( const uint8 * buffer, const int bufferLen, const char * name ) {
  63. utf8Encoding_t encoding;
  64. idStr::IsValidUTF8( buffer, bufferLen, encoding );
  65. if ( encoding == UTF8_INVALID ) {
  66. idLib::FatalError( "Language file %s is not valid UTF-8 or plain ASCII.", name );
  67. } else if ( encoding == UTF8_INVALID_BOM ) {
  68. idLib::FatalError( "Language file %s is marked as UTF-8 but has invalid encoding.", name );
  69. } else if ( encoding == UTF8_ENCODED_NO_BOM ) {
  70. idLib::FatalError( "Language file %s has no byte order marker. Fix this or roll back to a version that has the marker.", name );
  71. } else if ( encoding != UTF8_ENCODED_BOM && encoding != UTF8_PURE_ASCII ) {
  72. idLib::FatalError( "Language file %s has unknown utf8Encoding_t.", name );
  73. }
  74. return encoding;
  75. }
  76. // string entries can refer to other string entries,
  77. // recursing up to this many times before we decided someone did something stupid
  78. const char * idLangDict::KEY_PREFIX = "#str_"; // all keys should be prefixed with this for redirection to work
  79. const int idLangDict::KEY_PREFIX_LEN = idStr::Length( KEY_PREFIX );
  80. /*
  81. ========================
  82. idLangDict::idLangDict
  83. ========================
  84. */
  85. idLangDict::idLangDict() : keyIndex( 4096, 4096 ) {
  86. }
  87. /*
  88. ========================
  89. idLangDict::~idLangDict
  90. ========================
  91. */
  92. idLangDict::~idLangDict() {
  93. Clear();
  94. }
  95. /*
  96. ========================
  97. idLangDict::Clear
  98. ========================
  99. */
  100. void idLangDict::Clear() {
  101. //mem.PushHeap();
  102. for ( int i = 0; i < keyVals.Num(); i++ ) {
  103. if ( keyVals[i].value == NULL ) {
  104. continue;
  105. }
  106. blockAlloc.Free( keyVals[i].value );
  107. keyVals[i].value = NULL;
  108. }
  109. //mem.PopHeap();
  110. }
  111. /*
  112. ========================
  113. idLangDict::Load
  114. ========================
  115. */
  116. bool idLangDict::Load( const byte * buffer, const int bufferLen, const char *name ) {
  117. if ( buffer == NULL || bufferLen <= 0 ) {
  118. // let whoever called us deal with the failure (so sys_lang can be reset)
  119. return false;
  120. }
  121. idLib::Printf( "Reading %s", name );
  122. bool utf8 = false;
  123. // in all but retail builds, ensure that the byte-order mark is NOT MISSING so that
  124. // we can avoid debugging UTF-8 code
  125. #ifndef ID_RETAIL
  126. utf8Encoding_t encoding = idLocalization::VerifyUTF8( buffer, bufferLen, name );
  127. if ( encoding == UTF8_ENCODED_BOM ) {
  128. utf8 = true;
  129. } else if ( encoding == UTF8_PURE_ASCII ) {
  130. utf8 = false;
  131. } else {
  132. assert( false ); // this should have been handled in VerifyUTF8 with a FatalError
  133. return false;
  134. }
  135. #else
  136. // in release we just check the BOM so we're not scanning the lang file twice on startup
  137. if ( bufferLen > 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF ) {
  138. utf8 = true;
  139. }
  140. #endif
  141. if ( utf8 ) {
  142. idLib::Printf( " as UTF-8\n" );
  143. } else {
  144. idLib::Printf( " as ASCII\n" );
  145. }
  146. idStr tempKey;
  147. idStr tempVal;
  148. int line = 0;
  149. int numStrings = 0;
  150. int i = 0;
  151. while ( i < bufferLen ) {
  152. uint32 c = buffer[i++];
  153. if ( c == '/' ) { // comment, read until new line
  154. while ( i < bufferLen ) {
  155. c = buffer[i++];
  156. if ( c == '\n' ) {
  157. line++;
  158. break;
  159. }
  160. }
  161. } else if ( c == '}' ) {
  162. break;
  163. } else if ( c == '\n' ) {
  164. line++;
  165. } else if ( c == '\"' ) {
  166. int keyStart = i;
  167. int keyEnd = -1;
  168. while ( i < bufferLen ) {
  169. c = buffer[i++];
  170. if ( c == '\"' ) {
  171. keyEnd = i - 1;
  172. break;
  173. }
  174. }
  175. if ( keyEnd < keyStart ) {
  176. idLib::FatalError( "%s File ended while reading key at line %d", name, line );
  177. }
  178. tempKey.CopyRange( (char *)buffer, keyStart, keyEnd );
  179. int valStart = -1;
  180. while ( i < bufferLen ) {
  181. c = buffer[i++];
  182. if ( c == '\"' ) {
  183. valStart = i;
  184. break;
  185. }
  186. }
  187. if ( valStart < 0 ) {
  188. idLib::FatalError( "%s File ended while reading value at line %d", name, line );
  189. }
  190. int valEnd = -1;
  191. tempVal.CapLength( 0 );
  192. while ( i < bufferLen ) {
  193. c = utf8 ? idStr::UTF8Char( buffer, i ) : buffer[i++];
  194. if ( !utf8 && c >= 0x80 ) {
  195. // this is a serious error and we must check this to avoid accidentally shipping a file where someone squased UTF-8 encodings
  196. idLib::FatalError( "Language file %s is supposed to be plain ASCII, but has byte values > 127!", name );
  197. }
  198. if ( c == '\"' ) {
  199. valEnd = i - 1;
  200. continue;
  201. }
  202. if ( c == '\n' ) {
  203. line++;
  204. break;
  205. }
  206. if ( c == '\r' ) {
  207. continue;
  208. }
  209. if ( c == '\\' ) {
  210. c = utf8 ? idStr::UTF8Char( buffer, i ) : buffer[i++];
  211. if ( c == 'n' ) {
  212. c = '\n';
  213. } else if ( c == 't' ) {
  214. c = '\t';
  215. } else if ( c == '\"' ) {
  216. c = '\"';
  217. } else if ( c == '\\' ) {
  218. c = '\\';
  219. } else {
  220. idLib::Warning( "Unknown escape sequence %x at line %d", c, line );
  221. }
  222. }
  223. tempVal.AppendUTF8Char( c );
  224. }
  225. if ( valEnd < valStart ) {
  226. idLib::FatalError( "%s File ended while reading value at line %d", name, line );
  227. }
  228. if ( lang_maskLocalizedStrings.GetBool() && tempVal.Length() > 0 && tempKey.Find( "#font_" ) == -1 ) {
  229. int len = tempVal.Length();
  230. if ( len > 0 ) {
  231. tempVal.Fill( 'W', len - 1 );
  232. } else {
  233. tempVal.Empty();
  234. }
  235. tempVal.Append( 'X' );
  236. }
  237. AddKeyVal( tempKey, tempVal );
  238. numStrings++;
  239. }
  240. }
  241. idLib::Printf( "%i strings read\n", numStrings );
  242. // get rid of any waste due to geometric list growth
  243. //mem.PushHeap();
  244. keyVals.Condense();
  245. //mem.PopHeap();
  246. return true;
  247. }
  248. /*
  249. ========================
  250. idLangDict::Save
  251. ========================
  252. */
  253. bool idLangDict::Save( const char * fileName ) {
  254. idFile * outFile = fileSystem->OpenFileWrite( fileName );
  255. if ( outFile == NULL ) {
  256. idLib::Warning( "Error saving: %s", fileName );
  257. return false;
  258. }
  259. byte bof[3] = { 0xEF, 0xBB, 0xBF };
  260. outFile->Write( bof, 3 );
  261. outFile->WriteFloatString( "// string table\n//\n\n{\n" );
  262. for ( int j = 0; j < keyVals.Num(); j++ ) {
  263. const idLangKeyValue & kvp = keyVals[j];
  264. if ( kvp.value == NULL ) {
  265. continue;
  266. }
  267. outFile->WriteFloatString( "\t\"%s\"\t\"", kvp.key );
  268. for ( int k = 0; kvp.value[k] != 0; k++ ) {
  269. char ch = kvp.value[k];
  270. if ( ch == '\t' ) {
  271. outFile->Write( "\\t", 2 );
  272. } else if ( ch == '\n' || ch == '\r' ) {
  273. outFile->Write( "\\n", 2 );
  274. } else if ( ch == '"' ) {
  275. outFile->Write( "\\\"", 2 );
  276. } else if ( ch == '\\' ) {
  277. outFile->Write( "\\\\", 2 );
  278. } else {
  279. outFile->Write( &ch, 1 );
  280. }
  281. }
  282. outFile->WriteFloatString( "\"\n" );
  283. }
  284. outFile->WriteFloatString( "\n}\n" );
  285. delete outFile;
  286. return true;
  287. }
  288. /*
  289. ========================
  290. idLangDict::GetString
  291. ========================
  292. */
  293. const char * idLangDict::GetString( const char * str ) const {
  294. const char * localized = FindString( str );
  295. if ( localized == NULL ) {
  296. return str;
  297. }
  298. return localized;
  299. }
  300. /*
  301. ========================
  302. idLangDict::FindStringIndex
  303. ========================
  304. */
  305. int idLangDict::FindStringIndex( const char * str ) const {
  306. if ( str == NULL ) {
  307. return -1;
  308. }
  309. int hash = idStr::IHash( str );
  310. for ( int i = keyIndex.GetFirst( hash ); i >= 0; i = keyIndex.GetNext( i ) ) {
  311. if ( idStr::Icmp( str, keyVals[i].key ) == 0 ) {
  312. return i;
  313. }
  314. }
  315. return -1;
  316. }
  317. /*
  318. ========================
  319. idLangDict::FindString_r
  320. ========================
  321. */
  322. const char * idLangDict::FindString_r( const char * str, int & depth ) const {
  323. depth++;
  324. if ( depth > MAX_REDIRECTION_DEPTH ) {
  325. // This isn't an error because we assume the error will be obvious somewhere in a GUI or something,
  326. // and the whole point of tracking the depth is to avoid a crash.
  327. idLib::Warning( "String '%s', indirection depth > %d", str, MAX_REDIRECTION_DEPTH );
  328. return NULL;
  329. }
  330. if ( str == NULL || str[0] == '\0' ) {
  331. return NULL;
  332. }
  333. int index = FindStringIndex( str );
  334. if ( index < 0 ) {
  335. return NULL;
  336. }
  337. const char * value = keyVals[index].value;
  338. if ( value == NULL ) {
  339. return NULL;
  340. }
  341. if ( IsStringId( value ) ) {
  342. // this string is re-directed to another entry
  343. return FindString_r( value, depth );
  344. }
  345. return value;
  346. }
  347. /*
  348. ========================
  349. idLangDict::FindString
  350. ========================
  351. */
  352. const char * idLangDict::FindString( const char * str ) const {
  353. int depth = 0;
  354. return FindString_r( str, depth );
  355. }
  356. /*
  357. ========================
  358. idLangDict::DeleteString
  359. ========================
  360. */
  361. bool idLangDict::DeleteString( const char * key ) {
  362. return DeleteString( FindStringIndex( key ) );
  363. }
  364. /*
  365. ========================
  366. idLangDict::DeleteString
  367. ========================
  368. */
  369. bool idLangDict::DeleteString( const int idx ) {
  370. if ( idx < 0 || idx >= keyVals.Num() ) {
  371. return false;
  372. }
  373. //mem.PushHeap();
  374. blockAlloc.Free( keyVals[idx].value );
  375. keyVals[idx].value = NULL;
  376. //mem.PopHeap();
  377. return true;
  378. }
  379. /*
  380. ========================
  381. idLangDict::RenameStringKey
  382. ========================
  383. */
  384. bool idLangDict::RenameStringKey( const char * oldKey, const char * newKey ) {
  385. int index = FindStringIndex( oldKey );
  386. if ( index < 0 ) {
  387. return false;
  388. }
  389. //mem.PushHeap();
  390. blockAlloc.Free( keyVals[index].key );
  391. int newKeyLen = idStr::Length( newKey );
  392. keyVals[index].key = blockAlloc.Alloc( newKeyLen + 1 );
  393. idStr::Copynz( keyVals[index].key, newKey, newKeyLen + 1 );
  394. int oldHash = idStr::IHash( oldKey );
  395. int newHash = idStr::IHash( newKey );
  396. if ( oldHash != newHash ) {
  397. keyIndex.Remove( oldHash, index );
  398. keyIndex.Add( newHash, index );
  399. }
  400. //mem.PopHeap();
  401. return true;
  402. }
  403. /*
  404. ========================
  405. idLangDict::SetString
  406. ========================
  407. */
  408. bool idLangDict::SetString( const char * key, const char * val ) {
  409. int index = FindStringIndex( key );
  410. if ( index < 0 ) {
  411. return false;
  412. }
  413. //mem.PushHeap();
  414. if ( keyVals[index].value != NULL ) {
  415. blockAlloc.Free( keyVals[index].value );
  416. }
  417. int valLen = idStr::Length( val );
  418. keyVals[index].value = blockAlloc.Alloc( valLen + 1 );
  419. idStr::Copynz( keyVals[index].value, val, valLen + 1 );
  420. //mem.PopHeap();
  421. return true;
  422. }
  423. /*
  424. ========================
  425. idLangDict::AddKeyVal
  426. ========================
  427. */
  428. void idLangDict::AddKeyVal( const char * key, const char * val ) {
  429. if ( SetString( key, val ) ) {
  430. return;
  431. }
  432. //mem.PushHeap();
  433. int keyLen = idStr::Length( key );
  434. char * k = blockAlloc.Alloc( keyLen + 1 );
  435. idStr::Copynz( k, key, keyLen + 1 );
  436. char * v = NULL;
  437. if ( val != NULL ) {
  438. int valLen = idStr::Length( val );
  439. v = blockAlloc.Alloc( valLen + 1 );
  440. idStr::Copynz( v, val, valLen + 1 );
  441. }
  442. int index = keyVals.Append( idLangKeyValue( k, v ) );
  443. int hash = idStr::IHash( key );
  444. keyIndex.Add( hash, index );
  445. //mem.PopHeap();
  446. }
  447. /*
  448. ========================
  449. idLangDict::AddString
  450. ========================
  451. */
  452. const char * idLangDict::AddString( const char * val ) {
  453. int i = Sys_Milliseconds();
  454. idStr key;
  455. sprintf( key, "#str_%06d", ( i++ % 1000000 ) );
  456. while ( FindStringIndex( key ) > 0 ) {
  457. sprintf( key, "#str_%06d", ( i++ % 1000000 ) );
  458. }
  459. AddKeyVal( key, val );
  460. int index = FindStringIndex( key );
  461. return keyVals[index].key;
  462. }
  463. /*
  464. ========================
  465. idLangDict::GetNumKeyVals
  466. ========================
  467. */
  468. int idLangDict::GetNumKeyVals() const {
  469. return keyVals.Num();
  470. }
  471. /*
  472. ========================
  473. idLangDict::GetKeyVal
  474. ========================
  475. */
  476. const idLangKeyValue * idLangDict::GetKeyVal( int i ) const {
  477. return &keyVals[i];
  478. }
  479. /*
  480. ========================
  481. idLangDict::IsStringId
  482. ========================
  483. */
  484. bool idLangDict::IsStringId( const char * str ) {
  485. return idStr::Icmpn( str, KEY_PREFIX, KEY_PREFIX_LEN ) == 0;
  486. }
  487. /*
  488. ========================
  489. idLangDict::GetLocalizedString
  490. ========================
  491. */
  492. const char * idLangDict::GetLocalizedString( const idStrId & strId ) const {
  493. if ( strId.GetIndex() >= 0 && strId.GetIndex() < keyVals.Num() ) {
  494. if ( keyVals[ strId.GetIndex() ].value == NULL ) {
  495. return keyVals[ strId.GetIndex() ].key;
  496. } else {
  497. return keyVals[ strId.GetIndex() ].value;
  498. }
  499. }
  500. return "";
  501. }
  502. /*
  503. ================================================================================================
  504. idStrId
  505. ================================================================================================
  506. */
  507. /*
  508. ========================
  509. idStrId::Set
  510. ========================
  511. */
  512. void idStrId::Set( const char * key ) {
  513. if ( key == NULL || key[0] == 0 ) {
  514. index = -1;
  515. } else {
  516. index = idLocalization::languageDict.FindStringIndex( key );
  517. if ( index < 0 ) {
  518. // don't allow setting of string ID's to an unknown ID... this should only be allowed from
  519. // the string table tool because additions from anywhere else are not guaranteed to be
  520. // saved to the .lang file.
  521. idLib::Warning( "Attempted to set unknown string ID '%s'", key );
  522. }
  523. }
  524. }
  525. /*
  526. ========================
  527. idStrId::GetKey
  528. ========================
  529. */
  530. const char * idStrId::GetKey() const {
  531. if ( index >= 0 && index < idLocalization::languageDict.keyVals.Num() ) {
  532. return idLocalization::languageDict.keyVals[index].key;
  533. }
  534. return "";
  535. }
  536. /*
  537. ========================
  538. idStrId::GetLocalizedString
  539. ========================
  540. */
  541. const char * idStrId::GetLocalizedString() const {
  542. return idLocalization::languageDict.GetLocalizedString( *this );
  543. }