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