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