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