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