core/logic/Translator.cpp
author David Anderson <dvander@alliedmods.net>
Sun Aug 25 01:42:16 2013 -0700 (2013-08-25)
changeset 4058 c3ebd769e128
parent 4057 9f9f98843ca8
child 4090 06731c8a772e
permissions -rw-r--r--
Switch GameConfigs to hash tables (bug 5878 part 3, r=ds).
     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 		size_t len = strlen(key);
   478 		if (len < 2 || len > 3)
   479 		{
   480 			ParseWarning("Ignoring translation to invalid language \"%s\" on line %d.", key, states->line);
   481 			return SMCResult_Continue;
   482 		}
   483 
   484 		unsigned int lang;
   485 		if (!m_pTranslator->GetLanguageByCode(key, &lang))
   486 		{
   487 			/* Ignore if we don't have a language.
   488 			 * :IDEA: issue a one-time alert?
   489 			 */
   490 			return SMCResult_Continue;
   491 		}
   492 
   493 		/* See how many bytes we need for this string, then allocate.
   494 		 * NOTE: THIS SHOULD GUARANTEE THAT WE DO NOT NEED TO NEED TO SIZE CHECK 
   495 		 */
   496 		len = strlen(value) + pPhrase->fmt_bytes + 1;
   497 		char *out_buf;
   498 		int out_idx;
   499 
   500 		out_idx = m_pMemory->CreateMem(len, (void **)&out_buf);
   501 
   502 		/* Update pointer */
   503 		pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
   504 
   505 		int *fmt_order;
   506 		int *fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
   507 		trans_t *pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
   508 
   509 		pTrans = &pTrans[lang];
   510 		pTrans->stridx = out_idx;
   511 
   512 		bool params[MAX_TRANSLATE_PARAMS];
   513 
   514 		/* Build the format order list, if necessary */
   515 		if (fmt_list)
   516 		{
   517 			int tmp = m_pMemory->CreateMem(pPhrase->fmt_count * sizeof(int), (void **)&fmt_order);
   518 			
   519 			/* Update pointers */
   520 			pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
   521 			pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
   522 			fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
   523 			out_buf = (char *)m_pMemory->GetAddress(out_idx);
   524 			pTrans = &pTrans[lang];
   525 
   526 			/* Now it's safe to save the index */
   527 			pTrans->fmt_order = tmp;
   528 
   529 			for (unsigned int i=0; i<pPhrase->fmt_count; i++)
   530 			{
   531 				fmt_order[i] = -1;
   532 			}
   533 			memset(&params[0], 0, sizeof(params));
   534 		}
   535 
   536 		const char *in_ptr = value;
   537 		char *out_ptr = out_buf;
   538 		unsigned int order_idx = 0;
   539 		while (*in_ptr != '\0')
   540 		{
   541 			if (*in_ptr == '\\')
   542 			{
   543 				switch (*(in_ptr + 1))
   544 				{
   545 				case '\n':
   546 					*out_ptr++ = '\n';
   547 					break;
   548 				case '\t':
   549 					*out_ptr++ = '\t';
   550 					break;
   551 				case '\r':
   552 					*out_ptr++ = '\r';
   553 					break;
   554 				case '{':
   555 					*out_ptr++ = '{';
   556 					break;
   557 				default:
   558 					/* Copy both bytes since we don't know what's going on */
   559 					*out_ptr++ = *in_ptr++;
   560 					*out_ptr++ = *in_ptr;
   561 					break;
   562 				}
   563 				/* Skip past the last byte read */
   564 				in_ptr++;
   565 			} 
   566 			else if (*in_ptr == '{' && fmt_list != NULL) 
   567 			{
   568 				/* Search for parameters if this is a formatted string */
   569 				const char *scrap_in_point = in_ptr;
   570 				const char *digit_start = ++in_ptr;
   571 				unsigned int bytes;
   572 				while (*in_ptr != '\0')
   573 				{
   574 					bytes = textparsers->GetUTF8CharBytes(in_ptr);
   575 					if (bytes != 1)
   576 					{
   577 						goto scrap_point;
   578 					}
   579 					if (*in_ptr == '}')
   580 					{
   581 						/* Did we get an index? */
   582 						if (in_ptr == digit_start)
   583 						{
   584 							goto scrap_point;
   585 						}
   586 						/* Is it valid? */
   587 						long idx = strtol(digit_start, NULL, 10);
   588 						if (idx < 1 || idx > (int)pPhrase->fmt_count)
   589 						{
   590 							goto scrap_point;
   591 						}
   592 						if (params[idx-1])
   593 						{
   594 							goto scrap_point;
   595 						}
   596 
   597 						/* We're safe to insert the string.  First mark order. */
   598 						fmt_order[order_idx++] = (int)idx - 1;
   599 						/* Now concatenate */
   600 						out_ptr += sprintf(out_ptr, "%s", m_pStringTab->GetString(fmt_list[idx-1]));
   601 						/* Mark as used */
   602 						params[idx-1] = true;
   603 
   604 						goto cont_loop;
   605 					}
   606 					in_ptr++;
   607 				}
   608 scrap_point:
   609 				/* Pretend none of this ever happened.  Move along now! */
   610 				in_ptr = scrap_in_point;
   611 				*out_ptr++ = *in_ptr;
   612 			} else {
   613 				*out_ptr++ = *in_ptr;
   614 			}
   615 cont_loop:
   616 			in_ptr++;
   617 		}
   618 		*out_ptr = '\0';
   619 		pPhrase->translations++;
   620 	}
   621 
   622 	return SMCResult_Continue;
   623 }
   624 
   625 SMCResult CPhraseFile::ReadSMC_LeavingSection(const SMCStates *states)
   626 {
   627 	if (m_ParseState == PPS_InPhrase)
   628 	{
   629 		if (m_CurPhrase == -1 && m_LastPhraseString.size())
   630 			m_PhraseLookup.remove(m_LastPhraseString.c_str());
   631 		m_CurPhrase = -1;
   632 		m_ParseState = PPS_Phrases;
   633 		m_LastPhraseString.assign("");
   634 	} 
   635 	else if (m_ParseState == PPS_Phrases) 
   636 	{
   637 		m_ParseState = PPS_None;
   638 	}
   639 
   640 	return SMCResult_Continue;
   641 }
   642 
   643 void CPhraseFile::ReadSMC_ParseEnd(bool halted, bool failed)
   644 {
   645 	/* Check to see if we have any dangling phrases that weren't completed, and scrap them */
   646 	if ((halted || failed) && m_LastPhraseString.size())
   647 		m_PhraseLookup.remove(m_LastPhraseString.c_str());
   648 }
   649 
   650 TransError CPhraseFile::GetTranslation(const char *szPhrase, unsigned int lang_id, Translation *pTrans)
   651 {
   652 	if (lang_id >= m_LangCount)
   653 	{
   654 		return Trans_BadLanguage;
   655 	}
   656 
   657 	int address;
   658 	if (!m_PhraseLookup.retrieve(szPhrase, &address))
   659 		return Trans_BadPhrase;
   660 
   661 	phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(address);
   662 	trans_t *trans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
   663 
   664 	trans = &trans[lang_id];
   665 
   666 	if (trans->stridx == -1)
   667 	{
   668 		return Trans_BadPhraseLanguage;
   669 	}
   670 
   671 	pTrans->fmt_count = pPhrase->fmt_count;
   672 	pTrans->fmt_order = pTrans->fmt_count > 0 ? (int *)m_pMemory->GetAddress(trans->fmt_order) : NULL;
   673 	pTrans->szPhrase = m_pStringTab->GetString(trans->stridx);
   674 
   675 	return Trans_Okay;
   676 }
   677 
   678 const char *CPhraseFile::GetFilename()
   679 {
   680 	return m_File.c_str();
   681 }
   682 
   683 /**************************
   684  ** MAIN TRANSLATOR CODE **
   685  **************************/
   686 
   687 Translator::Translator() : m_ServerLang(SOURCEMOD_LANGUAGE_ENGLISH)
   688 {
   689 	m_pStringTab = new BaseStringTable(2048);
   690 	strncopy(m_InitialLang, "en", sizeof(m_InitialLang));
   691 }
   692 
   693 Translator::~Translator()
   694 {
   695 	for (size_t i=0; i<m_Files.size(); i++)
   696 	{
   697 		delete m_Files[i];
   698 	}
   699 
   700 	for (size_t i=0; i<m_Languages.size(); i++)
   701 	{
   702 		delete m_Languages[i];
   703 	}
   704 
   705 	delete m_pStringTab;
   706 }
   707 
   708 ConfigResult Translator::OnSourceModConfigChanged(const char *key, 
   709 									  const char *value, 
   710 									  ConfigSource source, 
   711 									  char *error, 
   712 									  size_t maxlength)
   713 {
   714 	if (strcasecmp(key, "ServerLang") == 0)
   715 	{
   716 		if (source == ConfigSource_Console)
   717 		{
   718 			unsigned int index;
   719 			if (!GetLanguageByCode(value, &index))
   720 			{
   721 				smcore.Format(error, maxlength, "Language code \"%s\" is not registered", value);
   722 				return ConfigResult_Reject;
   723 			}
   724 
   725 			m_ServerLang = index;
   726 		} else {
   727 			smcore.strncopy(m_InitialLang, value, sizeof(m_InitialLang));
   728 		}
   729 
   730 		return ConfigResult_Accept;
   731 	}
   732 
   733 	return ConfigResult_Ignore;
   734 }
   735 
   736 void Translator::OnSourceModLevelChange(const char *mapName)
   737 {
   738 	/* Refresh language stuff */
   739 	char path[PLATFORM_MAX_PATH];
   740 	g_pSM->BuildPath(Path_SM, path, sizeof(path), "configs/languages.cfg");
   741 	RebuildLanguageDatabase(path);
   742 }
   743 
   744 void Translator::OnSourceModAllInitialized()
   745 {
   746 	AddLanguage("en", "English");
   747 
   748 	const char* lang = smcore.GetCoreConfigValue("ServerLang");
   749 	if (lang)
   750 	{
   751 		strncpy(m_InitialLang, lang, sizeof(m_InitialLang));
   752 	}
   753 
   754 	g_pCorePhrases = CreatePhraseCollection();
   755 	g_pCorePhrases->AddPhraseFile("core.phrases");
   756 
   757 	sharesys->AddInterface(NULL, this);
   758 }
   759 
   760 void Translator::OnSourceModShutdown()
   761 {
   762 	g_pCorePhrases->Destroy();
   763 }
   764 
   765 bool Translator::GetLanguageByCode(const char *code, unsigned int *index)
   766 {
   767 	return m_LCodeLookup.retrieve(code, index);
   768 }
   769 
   770 bool Translator::GetLanguageByName(const char *name, unsigned int *index)
   771 {
   772 	char lower[256];
   773 	size_t len = strlen(name);
   774 	if (len > 255) len = 255;
   775 	for (size_t i = 0; i < len; i++)
   776 	{
   777 		if (name[i] >= 'A' && name[i] <= 'Z')
   778 			lower[i] = tolower(name[i]);
   779 		else
   780 			lower[i] = name[i];
   781 	}
   782 	lower[len] = '\0';
   783 
   784 	return m_LAliases.retrieve(lower, index);
   785 }
   786 
   787 unsigned int Translator::GetLanguageCount()
   788 {
   789 	return (unsigned int)m_Languages.size();
   790 }
   791 
   792 BaseStringTable *Translator::GetStringTable()
   793 {
   794 	return m_pStringTab;
   795 }
   796 
   797 unsigned int Translator::FindOrAddPhraseFile(const char *phrase_file)
   798 {
   799 	for (size_t i=0; i<m_Files.size(); i++)
   800 	{
   801 		if (strcmp(m_Files[i]->GetFilename(), phrase_file) == 0)
   802 		{
   803 			return (unsigned int)i;
   804 		}
   805 	}
   806 
   807 	CPhraseFile *pFile = new CPhraseFile(this, phrase_file);
   808 	unsigned int idx = (unsigned int)m_Files.size();
   809 	
   810 	m_Files.push_back(pFile);
   811 
   812 	pFile->ReparseFile();
   813 
   814 	return idx;
   815 }
   816 
   817 void Translator::RebuildLanguageDatabase(const char *lang_header_file)
   818 {
   819 	/* Erase everything we have */
   820 	m_LCodeLookup.clear();
   821 	m_LAliases.clear();
   822 	m_pStringTab->Reset();
   823 
   824 	for (size_t i=0; i<m_Languages.size(); i++)
   825 	{
   826 		delete m_Languages[i];
   827 	}
   828 	m_Languages.clear();
   829 
   830 	/* Start anew */
   831 	SMCError err;
   832 	SMCStates states;
   833 	if ((err=textparsers->ParseFile_SMC(lang_header_file, this, &states)) != SMCError_Okay)
   834 	{
   835 		const char *str_err = textparsers->GetSMCErrorString(err);
   836 		if (!str_err)
   837 		{
   838 			str_err = m_CustomError.c_str();
   839 		}
   840 
   841 		smcore.LogError("[SM] Failed to parse language header file: \"%s\"", lang_header_file);
   842 		smcore.LogError("[SM] Parse error (line %d, column %d): %s", states.line, states.col, str_err);
   843 	}
   844 
   845 	if (!m_LCodeLookup.retrieve(m_InitialLang, &m_ServerLang))
   846 	{
   847 		smcore.LogError("Server language was set to bad language \"%s\" -- reverting to English", m_InitialLang);
   848 
   849 		smcore.strncopy(m_InitialLang, "en", sizeof(m_InitialLang));
   850 		m_ServerLang = SOURCEMOD_LANGUAGE_ENGLISH;
   851 	}
   852 
   853 	if (!m_Languages.size())
   854 	{
   855 		smcore.LogError("[SM] Fatal error, no languages found! Translation will not work.");
   856 	}
   857 
   858 	for (size_t i=0; i<m_Files.size(); i++)
   859 	{
   860 		m_Files[i]->ReparseFile();
   861 	}
   862 }
   863 
   864 void Translator::ReadSMC_ParseStart()
   865 {
   866 	m_InLanguageSection = false;
   867 	m_CustomError.clear();
   868 }
   869 
   870 SMCResult Translator::ReadSMC_NewSection(const SMCStates *states, const char *name)
   871 {
   872 	if (!m_InLanguageSection)
   873 	{
   874 		if (strcmp(name, "Languages") == 0)
   875 		{
   876 			m_InLanguageSection = true;
   877 		}
   878 	}
   879 
   880 	if (!m_InLanguageSection)
   881 	{
   882 		smcore.LogError("[SM] Warning: Unrecognized section \"%s\" in languages.cfg", name);
   883 	}
   884 
   885 	return SMCResult_Continue;
   886 }
   887 
   888 SMCResult Translator::ReadSMC_LeavingSection(const SMCStates *states)
   889 {
   890 	m_InLanguageSection = false;
   891 
   892 	return SMCResult_Continue;
   893 }
   894 
   895 SMCResult Translator::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
   896 {
   897 	size_t len = strlen(key);
   898 
   899 	if (len < 2 || len > 3)
   900 	{
   901 		smcore.LogError("[SM] Warning encountered parsing languages.cfg file.");
   902 		smcore.LogError("[SM] Invalid language code \"%s\" is being ignored.", key);
   903 	}
   904 
   905 	AddLanguage(key, value);
   906 
   907 	return SMCResult_Continue;
   908 }
   909 
   910 bool Translator::AddLanguage(const char *langcode, const char *description)
   911 {
   912 	char lower[256];
   913 	size_t len = strlen(description);
   914 	if (len > 255) len = 255;
   915 	for (size_t i = 0; i < len; i++)
   916 	{
   917 		if (description[i] >= 'A' && description[i] <= 'Z')
   918 			lower[i] = tolower(description[i]);
   919 		else
   920 			lower[i] = description[i];
   921 	}
   922 	lower[len] = '\0';
   923 
   924 	if (m_LAliases.contains(lower))
   925 		return false;
   926 
   927 	unsigned int idx;
   928 	if (!m_LCodeLookup.retrieve(langcode, &idx))
   929 	{
   930 		Language *pLanguage = new Language;
   931 		idx = m_Languages.size();
   932 
   933 		smcore.Format(pLanguage->m_code2, sizeof(pLanguage->m_code2), "%s", langcode);
   934 		pLanguage->m_CanonicalName = m_pStringTab->AddString(lower);
   935 
   936 		m_LCodeLookup.insert(langcode, idx);
   937 
   938 		m_Languages.push_back(pLanguage);
   939 	}
   940 	
   941 	m_LAliases.insert(lower, idx);
   942 
   943 	return true;
   944 }
   945 
   946 CPhraseFile *Translator::GetFileByIndex(unsigned int index)
   947 {
   948 	if (index >= m_Files.size())
   949 	{
   950 		return NULL;
   951 	}
   952 
   953 	return m_Files[index];
   954 }
   955 
   956 unsigned int Translator::GetServerLanguage()
   957 {
   958 	return m_ServerLang;
   959 }
   960 
   961 unsigned int Translator::GetClientLanguage(int client)
   962 {
   963 	IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(client);
   964 	return pPlayer->GetLanguageId();
   965 }
   966 
   967 bool Translator::GetLanguageInfo(unsigned int number, const char **code, const char **name)
   968 {
   969 	if (number >= GetLanguageCount())
   970 	{
   971 		return false;
   972 	}
   973 
   974 	Language *l = m_Languages[number];
   975 	if (code)
   976 	{
   977 		*code = l->m_code2;
   978 	}
   979 	if (name)
   980 	{
   981 		*name = m_pStringTab->GetString(l->m_CanonicalName);
   982 	}
   983 
   984 	return true;
   985 }
   986 
   987 const char *Translator::GetInterfaceName()
   988 {
   989 	return SMINTERFACE_TRANSLATOR_NAME;
   990 }
   991 
   992 unsigned int Translator::GetInterfaceVersion()
   993 {
   994 	return SMINTERFACE_TRANSLATOR_VERSION;
   995 }
   996 
   997 IPhraseCollection *Translator::CreatePhraseCollection()
   998 {
   999 	return new CPhraseCollection();
  1000 }
  1001 
  1002 int Translator::SetGlobalTarget(int index)
  1003 {
  1004 	return g_pSM->SetGlobalTarget(index);
  1005 }
  1006 
  1007 int Translator::GetGlobalTarget() const
  1008 {
  1009 	return g_pSM->GetGlobalTarget();
  1010 }
  1011 
  1012 bool CoreTranslate(char *buffer,  size_t maxlength, const char *format, unsigned int numparams,
  1013                    size_t *pOutLength, ...)
  1014 {
  1015 	va_list ap;
  1016 	unsigned int i;
  1017 	const char *fail_phrase;
  1018 	void *params[MAX_TRANSLATE_PARAMS];
  1019 
  1020 	if (numparams > MAX_TRANSLATE_PARAMS)
  1021 	{
  1022 		assert(false);
  1023 		return false;
  1024 	}
  1025 
  1026 	va_start(ap, pOutLength);
  1027 	for (i = 0; i < numparams; i++)
  1028 	{
  1029 		params[i] = va_arg(ap, void *);
  1030 	}
  1031 	va_end(ap);
  1032 
  1033 	if (!g_pCorePhrases->FormatString(buffer,
  1034 		maxlength, 
  1035 		format, 
  1036 		params,
  1037 		numparams,
  1038 		pOutLength,
  1039 		&fail_phrase))
  1040 	{
  1041 		if (fail_phrase != NULL)
  1042 		{
  1043 			smcore.LogError("[SM] Could not find core phrase: %s", fail_phrase);
  1044 		}
  1045 		else
  1046 		{
  1047 			smcore.LogError("[SM] Unknown fatal error while translating a core phrase.");
  1048 		}
  1049 
  1050 		return false;
  1051 	}
  1052 
  1053 	return true;
  1054 }
  1055 
  1056 bool Translator::FormatString(char *buffer,
  1057 							  size_t maxlength,
  1058 							  const char *format,
  1059 							  IPhraseCollection *pPhrases,
  1060 							  void **params,
  1061 							  unsigned int numparams,
  1062 							  size_t *pOutLength,
  1063 							  const char **pFailPhrase)
  1064 {
  1065 	unsigned int arg;
  1066 
  1067 	arg = 0;
  1068 	if (!smcore.gnprintf(buffer, maxlength, format, pPhrases, params, numparams, arg, pOutLength, pFailPhrase))
  1069 	{
  1070 		return false;
  1071 	}
  1072 
  1073 	if (arg != numparams)
  1074 	{
  1075 		if (pFailPhrase != NULL)
  1076 		{
  1077 			*pFailPhrase = NULL;
  1078 		}
  1079 		return false;
  1080 	}
  1081 
  1082 	return true;
  1083 }