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