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