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