2 * ===============================================================
3 * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
4 * ===============================================================
6 * This file is not open source and may not be copied without explicit
7 * written permission of AlliedModders LLC. This file may not be redistributed
8 * in whole or significant part.
9 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
17 #include "CTranslator.h"
19 #include "CTextParsers.h"
20 #include "LibrarySys.h"
21 #include "sm_stringutil.h"
22 #include "sourcemod.h"
24 CTranslator g_Translator;
35 unsigned int fmt_count;
36 unsigned int fmt_bytes;
38 unsigned int translations;
41 CPhraseFile::CPhraseFile(CTranslator *pTranslator, const char *file)
43 m_pStringTab = pTranslator->GetStringTable();
44 m_pMemory = m_pStringTab->GetMemTable();
45 m_LangCount = pTranslator->GetLanguageCount();
47 m_pTranslator = pTranslator;
48 m_pPhraseLookup = NULL;
51 CPhraseFile::~CPhraseFile()
55 sm_trie_destroy(m_pPhraseLookup);
59 void CPhraseFile::ParseError(const char *message, ...)
63 va_start(ap, message);
64 vsnprintf(buffer, sizeof(buffer), message, ap);
67 m_ParseError.assign(buffer);
70 void CPhraseFile::ParseWarning(const char *message, ...)
74 va_start(ap, message);
75 vsnprintf(buffer, sizeof(buffer), message, ap);
80 g_Logger.LogError("[SM] Warning(s) encountered in translation file \"%s\"", m_File.c_str());
84 g_Logger.LogError("[SM] %s", message);
87 void CPhraseFile::ReparseFile()
91 sm_trie_destroy(m_pPhraseLookup);
93 m_pPhraseLookup = sm_trie_create();
95 m_LangCount = m_pTranslator->GetLanguageCount();
103 char path[PLATFORM_MAX_PATH+1];
104 g_SourceMod.BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "translations/%s", m_File.c_str());
105 unsigned int line=0, col=0;
107 if ((err=g_TextParser.ParseFile_SMC(path, this, &line, &col)) != SMCParse_Okay)
109 const char *msg = g_TextParser.GetSMCErrorString(err);
112 msg = m_ParseError.c_str();
115 g_Logger.LogError("[SM] Fatal error encountered parsing translation file \"%s\"", m_File.c_str());
116 g_Logger.LogError("[SM] Error (line %d, column %d): %s", line, col, msg);
120 void CPhraseFile::ReadSMC_ParseStart()
123 m_ParseState = PPS_None;
125 m_FileLogged = false;
126 m_LastPhraseString.clear();
129 SMCParseResult CPhraseFile::ReadSMC_RawLine(const char *line, unsigned int curline)
133 return SMCParse_Continue;
136 SMCParseResult CPhraseFile::ReadSMC_NewSection(const char *name, bool opt_quotes)
138 bool recognized = false;
139 if (m_ParseState == PPS_None)
141 if (strcmp(name, "Phrases") == 0)
143 m_ParseState = PPS_Phrases;
146 } else if (m_ParseState == PPS_Phrases) {
147 m_ParseState = PPS_InPhrase;
151 m_CurPhrase = m_pMemory->CreateMem(sizeof(phrase_t), (void **)&pPhrase);
153 /* Create the reverse lookup */
154 if (!sm_trie_insert(m_pPhraseLookup, name, reinterpret_cast<void *>(m_CurPhrase)))
156 ParseWarning("Skipping duplicate phrase \"%s\" on line %d.", name, m_CurLine);
157 /* :IDEA: prevent memory waste by seeking backwards in memtable? */
160 /* Initialize new phrase */
163 pPhrase->fmt_count = 0;
164 pPhrase->fmt_list = -1;
165 pPhrase->trans_tbl = m_pMemory->CreateMem(sizeof(trans_t) * m_LangCount, (void **)&pTrans);
166 pPhrase->translations = 0;
167 pPhrase->fmt_bytes = 0;
169 for (unsigned int i=0; i<m_LangCount; i++)
171 pTrans[i].stridx = -1;
173 m_LastPhraseString.assign(name);
175 } else if (m_ParseState == PPS_InPhrase) {
176 ParseError("Phrase sections may not have sub-sections");
177 return SMCParse_HaltFail;
182 ParseWarning("Ignoring invalid section \"%s\" on line %d.", name, m_CurLine);
185 return SMCParse_Continue;
188 SMCParseResult CPhraseFile::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
190 /* See if we are ignoring a phrase */
193 return SMCParse_Continue;
196 phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
198 if (key[0] == '#' && strcmp(key, "#format") == 0)
200 if (pPhrase->fmt_list != -1)
202 ParseWarning("Ignoring duplicated #format property on line %d", m_CurLine);
203 return SMCParse_Continue;
206 if (pPhrase->translations > 0)
208 ParseWarning("#format property should come before translations on line %d, ignoring", m_CurLine);
209 return SMCParse_Continue;
212 /* Do an initial browsing for error checking and what not */
220 ParseStates state = Parse_None;
222 const char *old_value = value;
223 while (*value != '\0')
225 if (state == Parse_None)
229 pPhrase->fmt_count++;
231 } else if (*value == ',') {
234 unsigned int bytes = g_TextParser.GetUTF8CharBytes(value);
235 if (bytes != 1 || !isalpha(*value))
237 ParseWarning("Invalid token '%c' in #format property on line %d.", *value, m_CurLine);
240 } else if (state == Parse_Index) {
243 state = Parse_Format;
244 if (value - old_value >= 15)
246 ParseWarning("Too many digits in format index on line %d, phrase will be ignored.", m_CurLine);
248 return SMCParse_Continue;
251 unsigned int bytes = g_TextParser.GetUTF8CharBytes(value);
252 if (bytes != 1 || !isdigit(*value))
254 ParseWarning("Token '%c' in #format property on line %d is not a digit, phrase will be ignored.",
258 return SMCParse_Continue;
261 } else if (state == Parse_Format) {
270 if (state != Parse_None)
272 /* Moose clam cow. */
273 ParseWarning("Unterminated format string on line %d, phrase will be ignored.", m_CurLine);
275 return SMCParse_Continue;
279 /* Allocate the format table */
282 pPhrase->fmt_list = m_pMemory->CreateMem(sizeof(int) * pPhrase->fmt_count, (void **)&fmt_list);
285 for (size_t i=0; i<pPhrase->fmt_count; i++)
290 /* Now we need to read again... this time into the format buffer */
291 const char *in_ptr = value;
292 const char *idx_ptr = NULL;
293 char *out_ptr = NULL;
294 unsigned int cur_idx = 0;
296 while (*in_ptr != '\0')
298 if (state == Parse_None)
305 } else if (state == Parse_Index) {
308 /* Check the number! */
311 ParseWarning("Format property contains unindexed format string on line %d, phrase will be ignored.", m_CurLine);
313 return SMCParse_Continue;
315 long idx = strtol(idx_ptr, NULL, 10);
316 if (idx < 1 || idx > (long)pPhrase->fmt_count)
318 ParseWarning("Format property contains invalid index '%d' on line %d, phrase will be ignored.", idx, m_CurLine);
320 return SMCParse_Continue;
321 } else if (fmt_list[idx - 1] != -1) {
322 ParseWarning("Format property contains duplicated index '%d' on line %d, phrase will be ignored.", idx, m_CurLine);
324 return SMCParse_Continue;
326 cur_idx = (unsigned int)idx;
327 state = Parse_Format;
329 } else if (!idx_ptr) {
332 } else if (state == Parse_Format) {
337 ParseWarning("Format property contains empty format string on line %d, phrase will be ignored.", m_CurLine);
339 return SMCParse_Continue;
343 /* Now, add this to our table */
344 fmt_list[cur_idx - 1] = m_pStringTab->AddString(fmt_buf);
345 pPhrase->fmt_bytes += strlen(fmt_buf);
352 /* Check length ... */
353 if ((unsigned)(out_ptr - fmt_buf) >= sizeof(fmt_buf) - 1)
355 ParseWarning("Format property contains format string that exceeds maximum length on line %d, phrase will be ignored.",
358 return SMCParse_Continue;
360 *out_ptr++ = *in_ptr;
366 /* If we've gotten here, we only accepted unique indexes in a range.
367 * Therefore, the list has to be completely filled. Double check anyway.
369 for (size_t i=0; i<pPhrase->fmt_count; i++)
371 if (fmt_list[i] == -1)
373 ParseWarning("Format property contains no string for index %d on line %d, phrase will be ignored.",
377 return SMCParse_Continue;
381 size_t len = strlen(key);
384 ParseWarning("Ignoring translation to invalid language \"%s\" on line %d.", key, m_CurLine);
385 return SMCParse_Continue;
389 if (!m_pTranslator->GetLanguageByCode(key, &lang))
391 /* Ignore if we don't have a language.
392 * :IDEA: issue a one-time alert?
394 return SMCParse_Continue;
397 /* See how many bytes we need for this string, then allocate.
398 * NOTE: THIS SHOULD GUARANTEE THAT WE DO NOT NEED TO NEED TO SIZE CHECK
400 len = strlen(value) + pPhrase->fmt_bytes + 1;
404 out_idx = m_pMemory->CreateMem(len, (void **)&out_buf);
407 int *fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
408 trans_t *pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
410 pTrans = &pTrans[lang];
411 pTrans->stridx = out_idx;
413 bool params[MAX_TRANSLATE_PARAMS];
415 /* Build the format order list, if necessary */
418 pTrans->fmt_order = m_pMemory->CreateMem(pPhrase->fmt_count * sizeof(int), (void **)&fmt_order);
420 for (unsigned int i=0; i<pPhrase->fmt_count; i++)
424 memset(¶ms[0], 0, sizeof(params));
427 const char *in_ptr = value;
428 char *out_ptr = out_buf;
429 unsigned int order_idx = 0;
430 while (*in_ptr != '\0')
434 switch (*(in_ptr + 1))
449 /* Copy both bytes since we don't know what's going on */
450 *out_ptr++ = *in_ptr++;
451 *out_ptr++ = *in_ptr;
454 /* Skip past the last byte read */
456 } else if (*in_ptr == '{' && fmt_list != NULL) {
457 /* Search for parameters if this is a formatted string */
458 const char *scrap_in_point = in_ptr;
459 const char *digit_start = ++in_ptr;
461 while (*in_ptr != '\0')
463 bytes = g_TextParser.GetUTF8CharBytes(in_ptr);
470 /* Did we get an index? */
471 if (in_ptr == digit_start)
476 long idx = strtol(digit_start, NULL, 10);
477 if (idx < 1 || idx > (int)pPhrase->fmt_count)
486 /* We're safe to insert the string. First mark order. */
487 fmt_order[order_idx++] = (int)idx - 1;
488 /* Now concatenate */
489 out_ptr += sprintf(out_ptr, "%s", m_pStringTab->GetString(fmt_list[idx-1]));
491 params[idx-1] = true;
498 /* Pretend none of this ever happened. Move along now! */
499 in_ptr = scrap_in_point;
500 *out_ptr++ = *in_ptr;
502 *out_ptr++ = *in_ptr;
508 pPhrase->translations++;
511 return SMCParse_Continue;
514 SMCParseResult CPhraseFile::ReadSMC_LeavingSection()
516 if (m_ParseState == PPS_InPhrase)
518 if (m_CurPhrase == -1 && m_LastPhraseString.size())
520 sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
523 m_ParseState = PPS_Phrases;
524 m_LastPhraseString.assign("");
525 } else if (m_ParseState == PPS_Phrases) {
526 m_ParseState = PPS_None;
529 return SMCParse_Continue;
532 void CPhraseFile::ReadSMC_ParseEnd(bool halted, bool failed)
534 /* Check to see if we have any dangling phrases that weren't completed, and scrap them */
535 if ((halted || failed) && m_LastPhraseString.size())
537 sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
541 const char *CPhraseFile::GetTranslation(const Translation *pTrans, int **fmt_order, unsigned int *fmt_count)
543 if (pTrans->lang_id >= m_LangCount)
549 if (!sm_trie_retrieve(m_pPhraseLookup, pTrans->szPhrase, &object))
554 phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(reinterpret_cast<int>(object));
555 trans_t *trans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
557 trans = &trans[pTrans->lang_id];
559 if (trans->stridx == -1)
564 *fmt_order = (int *)m_pMemory->GetAddress(trans->fmt_order);
565 *fmt_count = pPhrase->fmt_count;
567 return m_pStringTab->GetString(trans->stridx);
570 const char *CPhraseFile::GetFilename()
572 return m_File.c_str();
575 CTranslator::CTranslator()
577 m_pStringTab = new BaseStringTable(2048);
578 m_pLCodeLookup = sm_trie_create();
581 CTranslator::~CTranslator()
583 for (size_t i=0; i<m_Files.size(); i++)
588 for (size_t i=0; i<m_Languages.size(); i++)
590 delete m_Languages[i];
593 sm_trie_destroy(m_pLCodeLookup);
598 bool CTranslator::GetLanguageByCode(const char *code, unsigned int *index)
602 if (!sm_trie_retrieve(m_pLCodeLookup, code, &_index))
609 *index = reinterpret_cast<unsigned int>(_index);
615 unsigned int CTranslator::GetLanguageCount()
617 return (unsigned int)m_Languages.size();
620 BaseStringTable *CTranslator::GetStringTable()
625 unsigned int CTranslator::FindOrAddPhraseFile(const char *phrase_file)
627 for (size_t i=0; i<m_Files.size(); i++)
629 if (strcmp(m_Files[i]->GetFilename(), phrase_file) == 0)
631 return (unsigned int)i;
635 CPhraseFile *pFile = new CPhraseFile(this, phrase_file);
636 unsigned int idx = (unsigned int)m_Files.size();
638 m_Files.push_back(pFile);
640 pFile->ReparseFile();
645 void CTranslator::RebuildLanguageDatabase(const char *lang_header_file)
647 /* Erase everything we have */
648 sm_trie_destroy(m_pLCodeLookup);
649 m_pLCodeLookup = sm_trie_create();
650 m_pStringTab->Reset();
652 for (size_t i=0; i<m_Languages.size(); i++)
654 delete m_Languages[i];
660 unsigned int line=0, col=0;
661 if ((err=g_TextParser.ParseFile_SMC(lang_header_file, this, &line, &col)) != SMCParse_Okay)
663 const char *str_err = g_TextParser.GetSMCErrorString(err);
666 str_err = m_CustomError.c_str();
669 g_Logger.LogError("[SM] Failed to parse language header file: \"%s\"", lang_header_file);
670 g_Logger.LogError("[SM] Parse error (line %d, column %d): %s", line, col, str_err);
673 if (!m_Languages.size())
675 g_Logger.LogError("[SM] Fatal error, no languages found! Translation will not work.");
678 for (size_t i=0; i<m_Files.size(); i++)
680 m_Files[i]->ReparseFile();
684 void CTranslator::ReadSMC_ParseStart()
686 m_InLanguageSection = false;
687 m_CustomError.clear();
690 SMCParseResult CTranslator::ReadSMC_NewSection(const char *name, bool opt_quotes)
692 if (!m_InLanguageSection)
694 if (strcmp(name, "Languages") == 0)
696 m_InLanguageSection = true;
700 if (!m_InLanguageSection)
702 g_Logger.LogError("[SM] Warning: Unrecognized section \"%s\" in languages.cfg", name);
705 return SMCParse_Continue;
708 SMCParseResult CTranslator::ReadSMC_LeavingSection()
710 m_InLanguageSection = false;
712 return SMCParse_Continue;
715 SMCParseResult CTranslator::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
717 size_t len = strlen(key);
721 g_Logger.LogError("[SM] Warning encountered parsing languages.cfg file.");
722 g_Logger.LogError("[SM] Invalid language code \"%s\" is being ignored.", key);
725 Language *pLanguage = new Language;
726 unsigned int idx = m_Languages.size();
728 strcpy(pLanguage->m_code2, key);
729 pLanguage->m_FullName = m_pStringTab->AddString(value);
731 sm_trie_insert(m_pLCodeLookup, key, reinterpret_cast<void *>(idx));
733 m_Languages.push_back(pLanguage);
735 return SMCParse_Continue;
738 size_t CTranslator::Translate(char *buffer, size_t maxlength, const Translation *pTrans)
740 if (pTrans->file_id >= m_Files.size())
745 CPhraseFile *pFile = m_Files[pTrans->file_id];
750 if ((str=pFile->GetTranslation(pTrans, &order_table, &count)) == NULL)
755 void *params[MAX_TRANSLATE_PARAMS];
757 /* Rewrite the parameter order */
758 for (unsigned int i=0; i<count; i++)
760 params[i] = pTrans->params[order_table[i]];
763 return gnprintf(buffer, maxlength, str, params);