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