2 #include "CTranslator.h"
4 #include "CTextParsers.h"
5 #include "LibrarySys.h"
6 #include "sm_stringutil.h"
8 CTranslator g_Translator;
19 unsigned int fmt_count;
20 unsigned int fmt_bytes;
22 unsigned int translations;
25 CPhraseFile::CPhraseFile(CTranslator *pTranslator, const char *file)
27 m_pStringTab = pTranslator->GetStringTable();
28 m_pMemory = m_pStringTab->GetMemTable();
29 m_LangCount = pTranslator->GetLanguageCount();
31 m_pTranslator = pTranslator;
32 m_pPhraseLookup = NULL;
35 CPhraseFile::~CPhraseFile()
39 sm_trie_destroy(m_pPhraseLookup);
43 void CPhraseFile::ParseError(const char *message, ...)
47 va_start(ap, message);
48 vsnprintf(buffer, sizeof(buffer), message, ap);
51 m_ParseError.assign(buffer);
54 void CPhraseFile::ParseWarning(const char *message, ...)
58 va_start(ap, message);
59 vsnprintf(buffer, sizeof(buffer), message, ap);
64 g_Logger.LogError("[SOURCEMOD] Warning(s) encountered in translation file \"%s\"", m_File.c_str());
68 g_Logger.LogError("[SOURCEMOD] %s", message);
71 void CPhraseFile::ReparseFile()
75 sm_trie_destroy(m_pPhraseLookup);
77 m_pPhraseLookup = sm_trie_create();
79 m_LangCount = m_pTranslator->GetLanguageCount();
87 char path[PLATFORM_MAX_PATH+1];
88 g_LibSys.PathFormat(path, PLATFORM_MAX_PATH, "%s/translations/%s", g_SourceMod.GetSMBaseDir(), m_File.c_str());
89 unsigned int line=0, col=0;
91 if ((err=g_TextParser.ParseFile_SMC(path, this, &line, &col)) != SMCParse_Okay)
93 const char *msg = g_TextParser.GetSMCErrorString(err);
96 msg = m_ParseError.c_str();
99 g_Logger.LogError("[SOURCEMOD] Fatal error encountered parsing translation file \"%s\"", m_File.c_str());
100 g_Logger.LogError("[SOURCEMOD] Error (line %d, column %d): %s", line, col, msg);
104 void CPhraseFile::ReadSMC_ParseStart()
107 m_ParseState = PPS_None;
109 m_FileLogged = false;
110 m_LastPhraseString.clear();
113 SMCParseResult CPhraseFile::ReadSMC_RawLine(const char *line, unsigned int curline)
117 return SMCParse_Continue;
120 SMCParseResult CPhraseFile::ReadSMC_NewSection(const char *name, bool opt_quotes)
122 bool recognized = false;
123 if (m_ParseState == PPS_None)
125 if (strcmp(name, "Phrases") == 0)
127 m_ParseState = PPS_Phrases;
130 } else if (m_ParseState == PPS_Phrases) {
131 m_ParseState = PPS_InPhrase;
135 m_CurPhrase = m_pMemory->CreateMem(sizeof(phrase_t), (void **)&pPhrase);
137 /* Create the reverse lookup */
138 if (!sm_trie_insert(m_pPhraseLookup, name, reinterpret_cast<void *>(m_CurPhrase)))
140 ParseWarning("Skipping duplicate phrase \"%s\" on line %d.", name, m_CurLine);
141 /* :IDEA: prevent memory waste by seeking backwards in memtable? */
144 /* Initialize new phrase */
147 pPhrase->fmt_count = 0;
148 pPhrase->fmt_list = -1;
149 pPhrase->trans_tbl = m_pMemory->CreateMem(sizeof(trans_t) * m_LangCount, (void **)&pTrans);
150 pPhrase->translations = 0;
151 pPhrase->fmt_bytes = 0;
153 for (unsigned int i=0; i<m_LangCount; i++)
155 pTrans[i].stridx = -1;
157 m_LastPhraseString.assign(name);
159 } else if (m_ParseState == PPS_InPhrase) {
160 ParseError("Phrase sections may not have sub-sections");
161 return SMCParse_HaltFail;
166 ParseWarning("Ignoring invalid section \"%s\" on line %d.", name, m_CurLine);
169 return SMCParse_Continue;
172 SMCParseResult CPhraseFile::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
174 /* See if we are ignoring a phrase */
177 return SMCParse_Continue;
180 phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
182 if (key[0] == '#' && strcmp(key, "#format") == 0)
184 if (pPhrase->fmt_list != -1)
186 ParseWarning("Ignoring duplicated #format property on line %d", m_CurLine);
187 return SMCParse_Continue;
190 if (pPhrase->translations > 0)
192 ParseWarning("#format property should come before translations on line %d, ignoring", m_CurLine);
193 return SMCParse_Continue;
196 /* Do an initial browsing for error checking and what not */
204 ParseStates state = Parse_None;
206 const char *old_value = value;
207 while (*value != '\0')
209 if (state == Parse_None)
213 pPhrase->fmt_count++;
215 } else if (*value == ',') {
218 unsigned int bytes = g_TextParser.GetUTF8CharBytes(value);
219 if (bytes != 1 || !isalpha(*value))
221 ParseWarning("Invalid token '%c' in #format property on line %d.", *value, m_CurLine);
224 } else if (state == Parse_Index) {
227 state = Parse_Format;
228 if (value - old_value >= 15)
230 ParseWarning("Too many digits in format index on line %d, phrase will be ignored.", m_CurLine);
232 return SMCParse_Continue;
235 unsigned int bytes = g_TextParser.GetUTF8CharBytes(value);
236 if (bytes != 1 || !isdigit(*value))
238 ParseWarning("Token '%c' in #format property on line %d is not a digit, phrase will be ignored.",
242 return SMCParse_Continue;
245 } else if (state == Parse_Format) {
254 if (state != Parse_None)
256 /* Moose clam cow. */
257 ParseWarning("Unterminated format string on line %d, phrase will be ignored.", m_CurLine);
259 return SMCParse_Continue;
263 /* Allocate the format table */
266 pPhrase->fmt_list = m_pMemory->CreateMem(sizeof(int) * pPhrase->fmt_count, (void **)&fmt_list);
269 for (size_t i=0; i<pPhrase->fmt_count; i++)
274 /* Now we need to read again... this time into the format buffer */
275 const char *in_ptr = value;
276 const char *idx_ptr = NULL;
277 char *out_ptr = NULL;
278 unsigned int cur_idx = 0;
280 while (*in_ptr != '\0')
282 if (state == Parse_None)
289 } else if (state == Parse_Index) {
292 /* Check the number! */
295 ParseWarning("Format property contains unindexed format string on line %d, phrase will be ignored.", m_CurLine);
297 return SMCParse_Continue;
299 long idx = strtol(idx_ptr, NULL, 10);
300 if (idx < 1 || idx > (long)pPhrase->fmt_count)
302 ParseWarning("Format property contains invalid index '%d' on line %d, phrase will be ignored.", idx, m_CurLine);
304 return SMCParse_Continue;
305 } else if (fmt_list[idx - 1] != -1) {
306 ParseWarning("Format property contains duplicated index '%d' on line %d, phrase will be ignored.", idx, m_CurLine);
308 return SMCParse_Continue;
310 cur_idx = (unsigned int)idx;
311 state = Parse_Format;
313 } else if (!idx_ptr) {
316 } else if (state == Parse_Format) {
321 ParseWarning("Format property contains empty format string on line %d, phrase will be ignored.", m_CurLine);
323 return SMCParse_Continue;
327 /* Now, add this to our table */
328 fmt_list[cur_idx - 1] = m_pStringTab->AddString(fmt_buf);
329 pPhrase->fmt_bytes += strlen(fmt_buf);
336 /* Check length ... */
337 if (out_ptr - fmt_buf >= sizeof(fmt_buf) - 1)
339 ParseWarning("Format property contains format string that exceeds maximum length on line %d, phrase will be ignored.",
342 return SMCParse_Continue;
344 *out_ptr++ = *in_ptr;
350 /* If we've gotten here, we only accepted unique indexes in a range.
351 * Therefore, the list has to be completely filled. Double check anyway.
353 for (size_t i=0; i<pPhrase->fmt_count; i++)
355 if (fmt_list[i] == -1)
357 ParseWarning("Format property contains no string for index %d on line %d, phrase will be ignored.",
361 return SMCParse_Continue;
365 size_t len = strlen(key);
368 ParseWarning("Ignoring translation to invalid language \"%s\" on line %d.", key, m_CurLine);
369 return SMCParse_Continue;
373 if (!m_pTranslator->GetLanguageByCode(key, &lang))
375 /* Ignore if we don't have a language.
376 * :IDEA: issue a one-time alert?
378 return SMCParse_Continue;
381 /* See how many bytes we need for this string, then allocate.
382 * NOTE: THIS SHOULD GUARANTEE THAT WE DO NOT NEED TO NEED TO SIZE CHECK
384 len = strlen(value) + pPhrase->fmt_bytes + 1;
388 out_idx = m_pMemory->CreateMem(len, (void **)&out_buf);
391 int *fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
392 trans_t *pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
394 pTrans = &pTrans[lang];
395 pTrans->stridx = out_idx;
397 bool params[MAX_TRANSLATE_PARAMS];
399 /* Build the format order list, if necessary */
402 pTrans->fmt_order = m_pMemory->CreateMem(pPhrase->fmt_count * sizeof(int), (void **)&fmt_order);
404 for (unsigned int i=0; i<pPhrase->fmt_count; i++)
408 memset(¶ms[0], 0, sizeof(params));
411 const char *in_ptr = value;
412 char *out_ptr = out_buf;
413 unsigned int order_idx = 0;
414 while (*in_ptr != '\0')
418 switch (*(in_ptr + 1))
433 /* Copy both bytes since we don't know what's going on */
434 *out_ptr++ = *in_ptr++;
435 *out_ptr++ = *in_ptr;
438 /* Skip past the last byte read */
440 } else if (*in_ptr == '{' && fmt_list != NULL) {
441 /* Search for parameters if this is a formatted string */
442 const char *scrap_in_point = in_ptr;
443 const char *digit_start = ++in_ptr;
445 while (*in_ptr != '\0')
447 bytes = g_TextParser.GetUTF8CharBytes(in_ptr);
454 /* Did we get an index? */
455 if (in_ptr == digit_start)
460 long idx = strtol(digit_start, NULL, 10);
461 if (idx < 1 || idx > (int)pPhrase->fmt_count)
470 /* We're safe to insert the string. First mark order. */
471 fmt_order[order_idx++] = (int)idx - 1;
472 /* Now concatenate */
473 out_ptr += sprintf(out_ptr, "%s", m_pStringTab->GetString(fmt_list[idx-1]));
475 params[idx-1] = true;
482 /* Pretend none of this ever happened. Move along now! */
483 in_ptr = scrap_in_point;
484 *out_ptr++ = *in_ptr;
486 *out_ptr++ = *in_ptr;
492 pPhrase->translations++;
495 return SMCParse_Continue;
498 SMCParseResult CPhraseFile::ReadSMC_LeavingSection()
500 if (m_ParseState == PPS_InPhrase)
502 if (m_CurPhrase == -1 && m_LastPhraseString.size())
504 sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
507 m_ParseState = PPS_Phrases;
508 m_LastPhraseString.assign("");
509 } else if (m_ParseState == PPS_Phrases) {
510 m_ParseState = PPS_None;
513 return SMCParse_Continue;
516 void CPhraseFile::ReadSMC_ParseEnd(bool halted, bool failed)
518 /* Check to see if we have any dangling phrases that weren't completed, and scrap them */
519 if ((halted || failed) && m_LastPhraseString.size())
521 sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
525 const char *CPhraseFile::GetTranslation(const Translation *pTrans, int **fmt_order, unsigned int *fmt_count)
527 if (pTrans->lang_id >= m_LangCount)
533 if (!sm_trie_retrieve(m_pPhraseLookup, pTrans->szPhrase, &object))
538 phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(reinterpret_cast<int>(object));
539 trans_t *trans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
541 trans = &trans[pTrans->lang_id];
543 if (trans->stridx == -1)
548 *fmt_order = (int *)m_pMemory->GetAddress(trans->fmt_order);
549 *fmt_count = pPhrase->fmt_count;
551 return m_pStringTab->GetString(trans->stridx);
554 const char *CPhraseFile::GetFilename()
556 return m_File.c_str();
559 CTranslator::CTranslator()
561 m_pStringTab = new BaseStringTable(2048);
562 m_pLCodeLookup = sm_trie_create();
565 CTranslator::~CTranslator()
567 for (size_t i=0; i<m_Files.size(); i++)
572 for (size_t i=0; i<m_Languages.size(); i++)
574 delete m_Languages[i];
577 sm_trie_destroy(m_pLCodeLookup);
582 bool CTranslator::GetLanguageByCode(const char *code, unsigned int *index)
586 if (!sm_trie_retrieve(m_pLCodeLookup, code, &_index))
593 *index = reinterpret_cast<unsigned int>(_index);
599 unsigned int CTranslator::GetLanguageCount()
601 return (unsigned int)m_Languages.size();
604 BaseStringTable *CTranslator::GetStringTable()
609 unsigned int CTranslator::FindOrAddPhraseFile(const char *phrase_file)
611 for (size_t i=0; i<m_Files.size(); i++)
613 if (strcmp(m_Files[i]->GetFilename(), phrase_file) == 0)
615 return (unsigned int)i;
619 CPhraseFile *pFile = new CPhraseFile(this, phrase_file);
620 unsigned int idx = (unsigned int)m_Files.size();
622 m_Files.push_back(pFile);
624 pFile->ReparseFile();
629 void CTranslator::RebuildLanguageDatabase(const char *lang_header_file)
631 /* Erase everything we have */
632 sm_trie_destroy(m_pLCodeLookup);
633 m_pLCodeLookup = sm_trie_create();
634 m_pStringTab->Reset();
636 for (size_t i=0; i<m_Languages.size(); i++)
638 delete m_Languages[i];
644 unsigned int line=0, col=0;
645 if ((err=g_TextParser.ParseFile_SMC(lang_header_file, this, &line, &col)) != SMCParse_Okay)
647 const char *str_err = g_TextParser.GetSMCErrorString(err);
650 str_err = m_CustomError.c_str();
653 g_Logger.LogError("[SOURCEMOD] Failed to parse language header file: \"%s\"", lang_header_file);
654 g_Logger.LogError("[SOURCEMOD] Parse error (line %d, column %d): %s", line, col, str_err);
657 if (!m_Languages.size())
659 g_Logger.LogError("[SOURCEMOD] Fatal error, no languages found! Translation will not work.");
662 for (size_t i=0; i<m_Files.size(); i++)
664 m_Files[i]->ReparseFile();
668 void CTranslator::ReadSMC_ParseStart()
670 m_InLanguageSection = false;
671 m_CustomError.clear();
674 SMCParseResult CTranslator::ReadSMC_NewSection(const char *name, bool opt_quotes)
676 if (!m_InLanguageSection)
678 if (strcmp(name, "Languages") == 0)
680 m_InLanguageSection = true;
684 if (!m_InLanguageSection)
686 g_Logger.LogError("[SOURCEMOD] Warning: Unrecognized section \"%s\" in languages.cfg");
689 return SMCParse_Continue;
692 SMCParseResult CTranslator::ReadSMC_LeavingSection()
694 m_InLanguageSection = false;
696 return SMCParse_Continue;
699 SMCParseResult CTranslator::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
701 size_t len = strlen(key);
705 g_Logger.LogError("[SOURCEMOD] Warning encountered parsing languages.cfg file.");
706 g_Logger.LogError("[SOURCEMOD] Invalid language code \"%s\" is being ignored.");
709 Language *pLanguage = new Language;
710 unsigned int idx = m_Languages.size();
712 strcpy(pLanguage->m_code2, key);
713 pLanguage->m_FullName = m_pStringTab->AddString(value);
715 sm_trie_insert(m_pLCodeLookup, key, reinterpret_cast<void *>(idx));
717 m_Languages.push_back(pLanguage);
719 return SMCParse_Continue;
722 size_t CTranslator::Translate(char *buffer, size_t maxlength, const Translation *pTrans)
724 if (pTrans->file_id >= m_Files.size())
729 CPhraseFile *pFile = m_Files[pTrans->file_id];
734 if ((str=pFile->GetTranslation(pTrans, &order_table, &count)) == NULL)
739 void *params[MAX_TRANSLATE_PARAMS];
741 /* Rewrite the parameter order */
742 for (unsigned int i=0; i<count; i++)
744 params[i] = pTrans->params[order_table[i]];
747 return gnprintf(buffer, maxlength, str, params);