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