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