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