core/CTranslator.cpp
author Scott Ehlert <ds@alliedmods.net>
Thu Feb 08 21:41:28 2007 +0000 (2007-02-08)
changeset 457 9b8a85bf4175
parent 396 c0af59ee9ca4
child 515 643240c66787
permissions -rw-r--r--
Initial import of convar stuff
1) Natives to create, find, set, and get convars
2) "cvars" option added to sm menu
3) Some internal additions to CPlugin to store a convar list
Still some things left to do...
     1 /**
     2  * ===============================================================
     3  * SourceMod (C)2004-2007 AlliedModders LLC.  All rights reserved.
     4  * ===============================================================
     5  *
     6  * This file is not open source and may not be copied without explicit
     7  * written permission of AlliedModders LLC.  This file may not be redistributed 
     8  * in whole or significant part.
     9  * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
    10  *
    11  * Version: $Id$
    12  */
    13 
    14 #include <stdarg.h>
    15 #include <stdlib.h>
    16 #include <ctype.h>
    17 #include "CTranslator.h"
    18 #include "CLogger.h"
    19 #include "CTextParsers.h"
    20 #include "LibrarySys.h"
    21 #include "sm_stringutil.h"
    22 #include "sourcemod.h"
    23 
    24 CTranslator g_Translator;
    25 
    26 struct trans_t
    27 {
    28 	int stridx;
    29 	int fmt_order;
    30 };
    31 
    32 struct phrase_t
    33 {
    34 	int fmt_list;
    35 	unsigned int fmt_count;
    36 	unsigned int fmt_bytes;
    37 	int trans_tbl;
    38 	unsigned int translations;
    39 };
    40 
    41 CPhraseFile::CPhraseFile(CTranslator *pTranslator, const char *file)
    42 {
    43 	m_pStringTab = pTranslator->GetStringTable();
    44 	m_pMemory = m_pStringTab->GetMemTable();
    45 	m_LangCount = pTranslator->GetLanguageCount();
    46 	m_File.assign(file);
    47 	m_pTranslator = pTranslator;
    48 	m_pPhraseLookup = NULL;
    49 }
    50 
    51 CPhraseFile::~CPhraseFile()
    52 {
    53 	if (m_pPhraseLookup)
    54 	{
    55 		sm_trie_destroy(m_pPhraseLookup);
    56 	}
    57 }
    58 
    59 void CPhraseFile::ParseError(const char *message, ...)
    60 {
    61 	char buffer[1024];
    62 	va_list ap;
    63 	va_start(ap, message);
    64 	vsnprintf(buffer, sizeof(buffer), message, ap);
    65 	va_end(ap);
    66 
    67 	m_ParseError.assign(buffer);
    68 }
    69 
    70 void CPhraseFile::ParseWarning(const char *message, ...)
    71 {
    72 	char buffer[1024];
    73 	va_list ap;
    74 	va_start(ap, message);
    75 	vsnprintf(buffer, sizeof(buffer), message, ap);
    76 	va_end(ap);
    77 
    78 	if (!m_FileLogged)
    79 	{
    80 		g_Logger.LogError("[SM] Warning(s) encountered in translation file \"%s\"", m_File.c_str());
    81 		m_FileLogged = true;
    82 	}
    83 
    84 	g_Logger.LogError("[SM] %s", message);
    85 }
    86 
    87 void CPhraseFile::ReparseFile()
    88 {
    89 	if (m_pPhraseLookup)
    90 	{
    91 		sm_trie_destroy(m_pPhraseLookup);
    92 	}
    93 	m_pPhraseLookup = sm_trie_create();
    94 
    95 	m_LangCount = m_pTranslator->GetLanguageCount();
    96 
    97 	if (!m_LangCount)
    98 	{
    99 		return;
   100 	}
   101 
   102 	SMCParseError err;
   103 	char path[PLATFORM_MAX_PATH+1];
   104 	g_SourceMod.BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "translations/%s", m_File.c_str());
   105 	unsigned int line=0, col=0;
   106 
   107 	if ((err=g_TextParser.ParseFile_SMC(path, this, &line, &col)) != SMCParse_Okay)
   108 	{
   109 		const char *msg = g_TextParser.GetSMCErrorString(err);
   110 		if (!msg)
   111 		{
   112 			msg = m_ParseError.c_str();
   113 		}
   114 
   115 		g_Logger.LogError("[SM] Fatal error encountered parsing translation file \"%s\"", m_File.c_str());
   116 		g_Logger.LogError("[SM] Error (line %d, column %d): %s", line, col, msg);
   117 	}
   118 }
   119 
   120 void CPhraseFile::ReadSMC_ParseStart()
   121 {
   122 	m_CurPhrase = -1;
   123 	m_ParseState = PPS_None;
   124 	m_CurLine = 0;
   125 	m_FileLogged = false;
   126 	m_LastPhraseString.clear();
   127 }
   128 
   129 SMCParseResult CPhraseFile::ReadSMC_RawLine(const char *line, unsigned int curline)
   130 {
   131 	m_CurLine = curline;
   132 
   133 	return SMCParse_Continue;
   134 }
   135 
   136 SMCParseResult CPhraseFile::ReadSMC_NewSection(const char *name, bool opt_quotes)
   137 {
   138 	bool recognized = false;
   139 	if (m_ParseState == PPS_None)
   140 	{
   141 		if (strcmp(name, "Phrases") == 0)
   142 		{
   143 			m_ParseState = PPS_Phrases;
   144 			recognized = true;
   145 		}
   146 	} else if (m_ParseState == PPS_Phrases) {
   147 		m_ParseState = PPS_InPhrase;
   148 		recognized = true;
   149 
   150 		phrase_t *pPhrase;
   151 		m_CurPhrase = m_pMemory->CreateMem(sizeof(phrase_t), (void **)&pPhrase);
   152 
   153 		/* Create the reverse lookup */
   154 		if (!sm_trie_insert(m_pPhraseLookup, name, reinterpret_cast<void *>(m_CurPhrase)))
   155 		{
   156 			ParseWarning("Skipping duplicate phrase \"%s\" on line %d.", name, m_CurLine);
   157 			/* :IDEA: prevent memory waste by seeking backwards in memtable? */
   158 			m_CurPhrase = -1;
   159 		} else {
   160 			/* Initialize new phrase */
   161 			trans_t *pTrans;
   162 	
   163 			pPhrase->fmt_count = 0;
   164 			pPhrase->fmt_list = -1;
   165 			pPhrase->trans_tbl = m_pMemory->CreateMem(sizeof(trans_t) * m_LangCount, (void **)&pTrans);
   166 			pPhrase->translations = 0;
   167 			pPhrase->fmt_bytes = 0;
   168 	
   169 			for (unsigned int i=0; i<m_LangCount; i++)
   170 			{
   171 				pTrans[i].stridx = -1;
   172 			}
   173 			m_LastPhraseString.assign(name);
   174 		}
   175 	} else if (m_ParseState == PPS_InPhrase) {
   176 		ParseError("Phrase sections may not have sub-sections");
   177 		return SMCParse_HaltFail;
   178 	}
   179 
   180 	if (!recognized)
   181 	{
   182 		ParseWarning("Ignoring invalid section \"%s\" on line %d.", name, m_CurLine);
   183 	}
   184 
   185 	return SMCParse_Continue;
   186 }
   187 
   188 SMCParseResult CPhraseFile::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
   189 {
   190 	/* See if we are ignoring a phrase */
   191 	if (!m_CurPhrase)
   192 	{
   193 		return SMCParse_Continue;
   194 	}
   195 
   196 	phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
   197 	
   198 	if (key[0] == '#' && strcmp(key, "#format") == 0)
   199 	{
   200 		if (pPhrase->fmt_list != -1)
   201 		{
   202 			ParseWarning("Ignoring duplicated #format property on line %d", m_CurLine);
   203 			return SMCParse_Continue;
   204 		}
   205 
   206 		if (pPhrase->translations > 0)
   207 		{
   208 			ParseWarning("#format property should come before translations on line %d, ignoring", m_CurLine);
   209 			return SMCParse_Continue;
   210 		}
   211 
   212 		/* Do an initial browsing for error checking and what not */
   213 		enum ParseStates
   214 		{
   215 			Parse_None,
   216 			Parse_Index,
   217 			Parse_Format,
   218 		};
   219 
   220 		ParseStates state = Parse_None;
   221 
   222 		const char *old_value = value;
   223 		while (*value != '\0')
   224 		{
   225 			if (state == Parse_None)
   226 			{
   227 				if (*value == '{')
   228 				{
   229 					pPhrase->fmt_count++;
   230 					state = Parse_Index;
   231 				} else if (*value == ',') {
   232 					/* Do nothing */
   233 				} else {
   234 					unsigned int bytes = g_TextParser.GetUTF8CharBytes(value);
   235 					if (bytes != 1 || !isalpha(*value))
   236 					{
   237 						ParseWarning("Invalid token '%c' in #format property on line %d.", *value, m_CurLine);
   238 					}
   239 				}
   240 			} else if (state == Parse_Index) {
   241 				if (*value == ':')
   242 				{
   243 					state = Parse_Format;
   244 					if (value - old_value >= 15)
   245 					{
   246 						ParseWarning("Too many digits in format index on line %d, phrase will be ignored.", m_CurLine);
   247 						m_CurPhrase = -1;
   248 						return SMCParse_Continue;
   249 					}
   250 				} else {
   251 					unsigned int bytes = g_TextParser.GetUTF8CharBytes(value);
   252 					if (bytes != 1 || !isdigit(*value))
   253 					{
   254 						ParseWarning("Token '%c' in #format property on line %d is not a digit, phrase will be ignored.",
   255 									*value,
   256 									m_CurLine);
   257 						m_CurPhrase = -1;
   258 						return SMCParse_Continue;
   259 					}
   260 				}
   261 			} else if (state == Parse_Format) {
   262 				if (*value == '}')
   263 				{
   264 					state = Parse_None;
   265 				}
   266 			}
   267 			value++;
   268 		}
   269 
   270 		if (state != Parse_None)
   271 		{
   272 			/* Moose clam cow. */
   273 			ParseWarning("Unterminated format string on line %d, phrase will be ignored.", m_CurLine);
   274 			m_CurPhrase = -1;
   275 			return SMCParse_Continue;
   276 		}
   277 		value = old_value;
   278 
   279 		/* Allocate the format table */
   280 		char fmt_buf[16];
   281 		int *fmt_list;
   282 		pPhrase->fmt_list = m_pMemory->CreateMem(sizeof(int) * pPhrase->fmt_count, (void **)&fmt_list);
   283 
   284 		/* Initialize */
   285 		for (size_t i=0; i<pPhrase->fmt_count; i++)
   286 		{
   287 			fmt_list[i] = -1;
   288 		}
   289 
   290 		/* Now we need to read again... this time into the format buffer */
   291 		const char *in_ptr = value;
   292 		const char *idx_ptr = NULL;
   293 		char *out_ptr = NULL;
   294 		unsigned int cur_idx = 0;
   295 		state = Parse_None;
   296 		while (*in_ptr != '\0')
   297 		{
   298 			if (state == Parse_None)
   299 			{
   300 				if (*in_ptr == '{')
   301 				{
   302 					state = Parse_Index;
   303 					idx_ptr = NULL;
   304 				}
   305 			} else if (state == Parse_Index) {
   306 				if (*in_ptr == ':')
   307 				{
   308 					/* Check the number! */
   309 					if (!idx_ptr)
   310 					{
   311 						ParseWarning("Format property contains unindexed format string on line %d, phrase will be ignored.", m_CurLine);
   312 						m_CurPhrase = -1;
   313 						return SMCParse_Continue;
   314 					}
   315 					long idx = strtol(idx_ptr, NULL, 10);
   316 					if (idx < 1 || idx > (long)pPhrase->fmt_count)
   317 					{
   318 						ParseWarning("Format property contains invalid index '%d' on line %d, phrase will be ignored.", idx, m_CurLine);
   319 						m_CurPhrase = -1;
   320 						return SMCParse_Continue;
   321 					} else if (fmt_list[idx - 1] != -1) {
   322 						ParseWarning("Format property contains duplicated index '%d' on line %d, phrase will be ignored.", idx, m_CurLine);
   323 						m_CurPhrase = -1;
   324 						return SMCParse_Continue;
   325 					}
   326 					cur_idx = (unsigned int)idx;
   327 					state = Parse_Format;
   328 					out_ptr = NULL;
   329 				} else if (!idx_ptr) {
   330 					idx_ptr = in_ptr;
   331 				}
   332 			} else if (state == Parse_Format) {
   333 				if (*in_ptr == '}')
   334 				{
   335 					if (!out_ptr)
   336 					{
   337 						ParseWarning("Format property contains empty format string on line %d, phrase will be ignored.", m_CurLine);
   338 						m_CurPhrase = -1;
   339 						return SMCParse_Continue;
   340 					}
   341 					*out_ptr = '\0';
   342 					state = Parse_None;
   343 					/* Now, add this to our table */
   344 					fmt_list[cur_idx - 1] = m_pStringTab->AddString(fmt_buf);
   345 					pPhrase->fmt_bytes += strlen(fmt_buf);
   346 				} else {
   347 					if (!out_ptr)
   348 					{
   349 						out_ptr = fmt_buf;
   350 						*out_ptr++ = '%';
   351 					}
   352 					/* Check length ... */
   353 					if ((unsigned)(out_ptr - fmt_buf) >= sizeof(fmt_buf) - 1)
   354 					{
   355 						ParseWarning("Format property contains format string that exceeds maximum length on line %d, phrase will be ignored.",
   356 									 m_CurLine);
   357 						m_CurPhrase = -1;
   358 						return SMCParse_Continue;
   359 					}
   360 					*out_ptr++ = *in_ptr;
   361 				}
   362 			}
   363 			in_ptr++;
   364 		}
   365 
   366 		/* If we've gotten here, we only accepted unique indexes in a range.
   367 		 * Therefore, the list has to be completely filled.  Double check anyway.
   368 		 */
   369 		for (size_t i=0; i<pPhrase->fmt_count; i++)
   370 		{
   371 			if (fmt_list[i] == -1)
   372 			{
   373 				ParseWarning("Format property contains no string for index %d on line %d, phrase will be ignored.",
   374 							 i + 1,
   375 							 m_CurLine);
   376 				m_CurPhrase = -1;
   377 				return SMCParse_Continue;
   378 			}
   379 		}
   380 	} else {
   381 		size_t len = strlen(key);
   382 		if (len != 2)
   383 		{
   384 			ParseWarning("Ignoring translation to invalid language \"%s\" on line %d.", key, m_CurLine);
   385 			return SMCParse_Continue;
   386 		}
   387 
   388 		unsigned int lang;
   389 		if (!m_pTranslator->GetLanguageByCode(key, &lang))
   390 		{
   391 			/* Ignore if we don't have a language.
   392 			 * :IDEA: issue a one-time alert?
   393 			 */
   394 			return SMCParse_Continue;
   395 		}
   396 
   397 		/* See how many bytes we need for this string, then allocate.
   398 		 * NOTE: THIS SHOULD GUARANTEE THAT WE DO NOT NEED TO NEED TO SIZE CHECK 
   399 		 */
   400 		len = strlen(value) + pPhrase->fmt_bytes + 1;
   401 		char *out_buf;
   402 		int out_idx;
   403 
   404 		out_idx = m_pMemory->CreateMem(len, (void **)&out_buf);
   405 
   406 		int *fmt_order;
   407 		int *fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
   408 		trans_t *pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
   409 
   410 		pTrans = &pTrans[lang];
   411 		pTrans->stridx = out_idx;
   412 
   413 		bool params[MAX_TRANSLATE_PARAMS];
   414 
   415 		/* Build the format order list, if necessary */
   416 		if (fmt_list)
   417 		{
   418 			pTrans->fmt_order = m_pMemory->CreateMem(pPhrase->fmt_count * sizeof(int), (void **)&fmt_order);
   419 
   420 			for (unsigned int i=0; i<pPhrase->fmt_count; i++)
   421 			{
   422 				fmt_order[i] = -1;
   423 			}
   424 			memset(&params[0], 0, sizeof(params));
   425 		}
   426 
   427 		const char *in_ptr = value;
   428 		char *out_ptr = out_buf;
   429 		unsigned int order_idx = 0;
   430 		while (*in_ptr != '\0')
   431 		{
   432 			if (*in_ptr == '\\')
   433 			{
   434 				switch (*(in_ptr + 1))
   435 				{
   436 				case '\n':
   437 					*out_ptr++ = '\n';
   438 					break;
   439 				case '\t':
   440 					*out_ptr++ = '\t';
   441 					break;
   442 				case '\r':
   443 					*out_ptr++ = '\r';
   444 					break;
   445 				case '{':
   446 					*out_ptr++ = '{';
   447 					break;
   448 				default:
   449 					/* Copy both bytes since we don't know what's going on */
   450 					*out_ptr++ = *in_ptr++;
   451 					*out_ptr++ = *in_ptr;
   452 					break;
   453 				}
   454 				/* Skip past the last byte read */
   455 				in_ptr++;
   456 			} else if (*in_ptr == '{' && fmt_list != NULL) {
   457 				/* Search for parameters if this is a formatted string */
   458 				const char *scrap_in_point = in_ptr;
   459 				const char *digit_start = ++in_ptr;
   460 				unsigned int bytes;
   461 				while (*in_ptr != '\0')
   462 				{
   463 					bytes = g_TextParser.GetUTF8CharBytes(in_ptr);
   464 					if (bytes != 1)
   465 					{
   466 						goto scrap_point;
   467 					}
   468 					if (*in_ptr == '}')
   469 					{
   470 						/* Did we get an index? */
   471 						if (in_ptr == digit_start)
   472 						{
   473 							goto scrap_point;
   474 						}
   475 						/* Is it valid? */
   476 						long idx = strtol(digit_start, NULL, 10);
   477 						if (idx < 1 || idx > (int)pPhrase->fmt_count)
   478 						{
   479 							goto scrap_point;
   480 						}
   481 						if (params[idx-1])
   482 						{
   483 							goto scrap_point;
   484 						}
   485 
   486 						/* We're safe to insert the string.  First mark order. */
   487 						fmt_order[order_idx++] = (int)idx - 1;
   488 						/* Now concatenate */
   489 						out_ptr += sprintf(out_ptr, "%s", m_pStringTab->GetString(fmt_list[idx-1]));
   490 						/* Mark as used */
   491 						params[idx-1] = true;
   492 
   493 						goto cont_loop;
   494 					}
   495 					in_ptr++;
   496 				}
   497 scrap_point:
   498 				/* Pretend none of this ever happened.  Move along now! */
   499 				in_ptr = scrap_in_point;
   500 				*out_ptr++ = *in_ptr;
   501 			} else {
   502 				*out_ptr++ = *in_ptr;
   503 			}
   504 cont_loop:
   505 			in_ptr++;
   506 		}
   507 		*out_ptr = '\0';
   508 		pPhrase->translations++;
   509 	}
   510 
   511 	return SMCParse_Continue;
   512 }
   513 
   514 SMCParseResult CPhraseFile::ReadSMC_LeavingSection()
   515 {
   516 	if (m_ParseState == PPS_InPhrase)
   517 	{
   518 		if (m_CurPhrase == -1 && m_LastPhraseString.size())
   519 		{
   520 			sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
   521 		}
   522 		m_CurPhrase = -1;
   523 		m_ParseState = PPS_Phrases;
   524 		m_LastPhraseString.assign("");
   525 	} else if (m_ParseState == PPS_Phrases) {
   526 		m_ParseState = PPS_None;
   527 	}
   528 
   529 	return SMCParse_Continue;
   530 }
   531 
   532 void CPhraseFile::ReadSMC_ParseEnd(bool halted, bool failed)
   533 {
   534 	/* Check to see if we have any dangling phrases that weren't completed, and scrap them */
   535 	if ((halted || failed) && m_LastPhraseString.size())
   536 	{
   537 		sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
   538 	}
   539 }
   540 
   541 TransError CPhraseFile::GetTranslation(const char *szPhrase, unsigned int lang_id, Translation *pTrans)
   542 {
   543 	if (lang_id >= m_LangCount)
   544 	{
   545 		return Trans_BadLanguage;
   546 	}
   547 
   548 	void *object;
   549 	if (!sm_trie_retrieve(m_pPhraseLookup, szPhrase, &object))
   550 	{
   551 		return Trans_BadPhrase;
   552 	}
   553 
   554 	phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(reinterpret_cast<int>(object));
   555 	trans_t *trans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
   556 
   557 	trans = &trans[lang_id];
   558 
   559 	if (trans->stridx == -1)
   560 	{
   561 		return Trans_BadPhraseLanguage;
   562 	}
   563 
   564 	pTrans->fmt_order = (int *)m_pMemory->GetAddress(trans->fmt_order);
   565 	pTrans->fmt_count = pPhrase->fmt_count;
   566 
   567 	pTrans->szPhrase = m_pStringTab->GetString(trans->stridx);
   568 
   569 	return Trans_Okay;
   570 }
   571 
   572 const char *CPhraseFile::GetFilename()
   573 {
   574 	return m_File.c_str();
   575 }
   576 
   577 CTranslator::CTranslator()
   578 {
   579 	m_pStringTab = new BaseStringTable(2048);
   580 	m_pLCodeLookup = sm_trie_create();
   581 }
   582 
   583 CTranslator::~CTranslator()
   584 {
   585 	for (size_t i=0; i<m_Files.size(); i++)
   586 	{
   587 		delete m_Files[i];
   588 	}
   589 
   590 	for (size_t i=0; i<m_Languages.size(); i++)
   591 	{
   592 		delete m_Languages[i];
   593 	}
   594 
   595 	sm_trie_destroy(m_pLCodeLookup);
   596 
   597 	delete m_pStringTab;
   598 }
   599 
   600 bool CTranslator::GetLanguageByCode(const char *code, unsigned int *index)
   601 {
   602 	void *_index;
   603 
   604 	if (!sm_trie_retrieve(m_pLCodeLookup, code, &_index))
   605 	{
   606 		return false;
   607 	}
   608 
   609 	if (index)
   610 	{
   611 		*index = reinterpret_cast<unsigned int>(_index);
   612 	}
   613 
   614 	return true;
   615 }
   616 
   617 unsigned int CTranslator::GetLanguageCount()
   618 {
   619 	return (unsigned int)m_Languages.size();
   620 }
   621 
   622 BaseStringTable *CTranslator::GetStringTable()
   623 {
   624 	return m_pStringTab;
   625 }
   626 
   627 unsigned int CTranslator::FindOrAddPhraseFile(const char *phrase_file)
   628 {
   629 	for (size_t i=0; i<m_Files.size(); i++)
   630 	{
   631 		if (strcmp(m_Files[i]->GetFilename(), phrase_file) == 0)
   632 		{
   633 			return (unsigned int)i;
   634 		}
   635 	}
   636 
   637 	CPhraseFile *pFile = new CPhraseFile(this, phrase_file);
   638 	unsigned int idx = (unsigned int)m_Files.size();
   639 	
   640 	m_Files.push_back(pFile);
   641 
   642 	pFile->ReparseFile();
   643 
   644 	return idx;
   645 }
   646 
   647 void CTranslator::RebuildLanguageDatabase(const char *lang_header_file)
   648 {
   649 	/* Erase everything we have */
   650 	sm_trie_destroy(m_pLCodeLookup);
   651 	m_pLCodeLookup = sm_trie_create();
   652 	m_pStringTab->Reset();
   653 
   654 	for (size_t i=0; i<m_Languages.size(); i++)
   655 	{
   656 		delete m_Languages[i];
   657 	}
   658 	m_Languages.clear();
   659 
   660 	/* Start anew */
   661 	SMCParseError err;
   662 	unsigned int line=0, col=0;
   663 	if ((err=g_TextParser.ParseFile_SMC(lang_header_file, this, &line, &col)) != SMCParse_Okay)
   664 	{
   665 		const char *str_err = g_TextParser.GetSMCErrorString(err);
   666 		if (!str_err)
   667 		{
   668 			str_err = m_CustomError.c_str();
   669 		}
   670 
   671 		g_Logger.LogError("[SM] Failed to parse language header file: \"%s\"", lang_header_file);
   672 		g_Logger.LogError("[SM] Parse error (line %d, column %d): %s", line, col, str_err);
   673 	}
   674 
   675 	if (!m_Languages.size())
   676 	{
   677 		g_Logger.LogError("[SM] Fatal error, no languages found! Translation will not work.");
   678 	}
   679 
   680 	for (size_t i=0; i<m_Files.size(); i++)
   681 	{
   682 		m_Files[i]->ReparseFile();
   683 	}
   684 }
   685 
   686 void CTranslator::ReadSMC_ParseStart()
   687 {
   688 	m_InLanguageSection = false;
   689 	m_CustomError.clear();
   690 }
   691 
   692 SMCParseResult CTranslator::ReadSMC_NewSection(const char *name, bool opt_quotes)
   693 {
   694 	if (!m_InLanguageSection)
   695 	{
   696 		if (strcmp(name, "Languages") == 0)
   697 		{
   698 			m_InLanguageSection = true;
   699 		}
   700 	}
   701 
   702 	if (!m_InLanguageSection)
   703 	{
   704 		g_Logger.LogError("[SM] Warning: Unrecognized section \"%s\" in languages.cfg", name);
   705 	}
   706 
   707 	return SMCParse_Continue;
   708 }
   709 
   710 SMCParseResult CTranslator::ReadSMC_LeavingSection()
   711 {
   712 	m_InLanguageSection = false;
   713 
   714 	return SMCParse_Continue;
   715 }
   716 
   717 SMCParseResult CTranslator::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
   718 {
   719 	size_t len = strlen(key);
   720 
   721 	if (len != 2)
   722 	{
   723 		g_Logger.LogError("[SM] Warning encountered parsing languages.cfg file.");
   724 		g_Logger.LogError("[SM] Invalid language code \"%s\" is being ignored.", key);
   725 	}
   726 
   727 	Language *pLanguage = new Language;
   728 	unsigned int idx = m_Languages.size();
   729 
   730 	strcpy(pLanguage->m_code2, key);
   731 	pLanguage->m_FullName = m_pStringTab->AddString(value);
   732 
   733 	sm_trie_insert(m_pLCodeLookup, key, reinterpret_cast<void *>(idx));
   734 
   735 	m_Languages.push_back(pLanguage);
   736 
   737 	return SMCParse_Continue;
   738 }
   739 
   740 CPhraseFile *CTranslator::GetFileByIndex(unsigned int index)
   741 {
   742 	if (index >= m_Files.size())
   743 	{
   744 		return NULL;
   745 	}
   746 
   747 	return m_Files[index];
   748 }
   749 
   750 size_t CTranslator::Translate(char *buffer, size_t maxlength, void **params, const Translation *pTrans)
   751 {
   752 	void *new_params[MAX_TRANSLATE_PARAMS];
   753 
   754 	/* Rewrite the parameter order */
   755 	for (unsigned int i=0; i<pTrans->fmt_count; i++)
   756 	{
   757 		new_params[i] = params[pTrans->fmt_order[i]];
   758 	}
   759 
   760 	return gnprintf(buffer, maxlength, pTrans->szPhrase, new_params);
   761 }