fixed a bug where the database manager removed itself from the plugins listener
3 * ===============================================================
4 * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
5 * ===============================================================
7 * This file is not open source and may not be copied without explicit
8 * written permission of AlliedModders LLC. This file may not be redistributed
9 * in whole or significant part.
10 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
18 #include "Translator.h"
20 #include "TextParsers.h"
21 #include "LibrarySys.h"
22 #include "sm_stringutil.h"
23 #include "sourcemod.h"
25 Translator g_Translator;
26 CPhraseFile *g_pCorePhrases = NULL;
37 unsigned int fmt_count;
38 unsigned int fmt_bytes;
40 unsigned int translations;
43 CPhraseFile::CPhraseFile(Translator *pTranslator, const char *file)
45 m_pStringTab = pTranslator->GetStringTable();
46 m_pMemory = m_pStringTab->GetMemTable();
47 m_LangCount = pTranslator->GetLanguageCount();
49 m_pTranslator = pTranslator;
50 m_pPhraseLookup = NULL;
53 CPhraseFile::~CPhraseFile()
57 sm_trie_destroy(m_pPhraseLookup);
61 void CPhraseFile::ParseError(const char *message, ...)
65 va_start(ap, message);
66 vsnprintf(buffer, sizeof(buffer), message, ap);
69 m_ParseError.assign(buffer);
72 void CPhraseFile::ParseWarning(const char *message, ...)
76 va_start(ap, message);
77 vsnprintf(buffer, sizeof(buffer), message, ap);
82 g_Logger.LogError("[SM] Warning(s) encountered in translation file \"%s\"", m_File.c_str());
86 g_Logger.LogError("[SM] %s", message);
89 void CPhraseFile::ReparseFile()
93 sm_trie_destroy(m_pPhraseLookup);
95 m_pPhraseLookup = sm_trie_create();
97 m_LangCount = m_pTranslator->GetLanguageCount();
105 char path[PLATFORM_MAX_PATH];
106 g_SourceMod.BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "translations/%s", m_File.c_str());
108 //backwards compatibility shim
109 /* :HACKHACK: Change .cfg/.txt and vice versa for compatibility */
110 if (!g_LibSys.PathExists(path))
112 if (m_File.compare("common.cfg") == 0)
114 UTIL_ReplaceAll(path, sizeof(path), "common.cfg", "common.phrases.txt");
115 } else if (strstr(path, ".cfg")) {
116 UTIL_ReplaceAll(path, sizeof(path), ".cfg", ".txt");
117 } else if (strstr(path, ".txt")) {
118 UTIL_ReplaceAll(path, sizeof(path), ".txt", ".cfg");
122 unsigned int line=0, col=0;
124 if ((err=g_TextParser.ParseFile_SMC(path, this, &line, &col)) != SMCParse_Okay)
126 const char *msg = g_TextParser.GetSMCErrorString(err);
129 msg = m_ParseError.c_str();
132 g_Logger.LogError("[SM] Fatal error encountered parsing translation file \"%s\"", m_File.c_str());
133 g_Logger.LogError("[SM] Error (line %d, column %d): %s", line, col, msg);
137 void CPhraseFile::ReadSMC_ParseStart()
140 m_ParseState = PPS_None;
142 m_FileLogged = false;
143 m_LastPhraseString.clear();
146 SMCParseResult CPhraseFile::ReadSMC_RawLine(const char *line, unsigned int curline)
150 return SMCParse_Continue;
153 SMCParseResult CPhraseFile::ReadSMC_NewSection(const char *name, bool opt_quotes)
155 bool recognized = false;
156 if (m_ParseState == PPS_None)
158 if (strcmp(name, "Phrases") == 0)
160 m_ParseState = PPS_Phrases;
163 } else if (m_ParseState == PPS_Phrases) {
164 m_ParseState = PPS_InPhrase;
168 m_CurPhrase = m_pMemory->CreateMem(sizeof(phrase_t), (void **)&pPhrase);
170 /* Create the reverse lookup */
171 if (!sm_trie_insert(m_pPhraseLookup, name, reinterpret_cast<void *>(m_CurPhrase)))
173 ParseWarning("Skipping duplicate phrase \"%s\" on line %d.", name, m_CurLine);
174 /* :IDEA: prevent memory waste by seeking backwards in memtable? */
177 /* Initialize new phrase */
180 pPhrase->fmt_count = 0;
181 pPhrase->fmt_list = -1;
183 int trans_tbl = m_pMemory->CreateMem(sizeof(trans_t) * m_LangCount, (void **)&pTrans);
184 pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
185 pPhrase->trans_tbl = trans_tbl;
187 pPhrase->translations = 0;
188 pPhrase->fmt_bytes = 0;
190 for (unsigned int i=0; i<m_LangCount; i++)
192 pTrans[i].stridx = -1;
194 m_LastPhraseString.assign(name);
196 } else if (m_ParseState == PPS_InPhrase) {
197 ParseError("Phrase sections may not have sub-sections");
198 return SMCParse_HaltFail;
203 ParseWarning("Ignoring invalid section \"%s\" on line %d.", name, m_CurLine);
206 return SMCParse_Continue;
209 SMCParseResult CPhraseFile::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
211 /* See if we are ignoring a phrase */
212 if (m_CurPhrase == -1)
214 return SMCParse_Continue;
217 phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
219 if (key[0] == '#' && strcmp(key, "#format") == 0)
221 if (pPhrase->fmt_list != -1)
223 ParseWarning("Ignoring duplicated #format property on line %d", m_CurLine);
224 return SMCParse_Continue;
227 if (pPhrase->translations > 0)
229 ParseWarning("#format property should come before translations on line %d, ignoring", m_CurLine);
230 return SMCParse_Continue;
233 /* Do an initial browsing for error checking and what not */
241 ParseStates state = Parse_None;
243 const char *old_value = value;
244 const char *last_value = value;
245 while (*value != '\0')
247 if (state == Parse_None)
251 pPhrase->fmt_count++;
253 } else if (*value == ',') {
256 unsigned int bytes = g_TextParser.GetUTF8CharBytes(value);
257 if (bytes != 1 || !isalpha(*value))
259 ParseWarning("Invalid token '%c' in #format property on line %d.", *value, m_CurLine);
262 } else if (state == Parse_Index) {
265 state = Parse_Format;
266 if (value - last_value >= 15)
268 ParseWarning("Too many digits in format index on line %d, phrase will be ignored.", m_CurLine);
270 return SMCParse_Continue;
273 unsigned int bytes = g_TextParser.GetUTF8CharBytes(value);
274 if (bytes != 1 || !isdigit(*value))
276 ParseWarning("Token '%c' in #format property on line %d is not a digit, phrase will be ignored.",
280 return SMCParse_Continue;
283 } else if (state == Parse_Format) {
287 last_value = value + 1;
293 if (state != Parse_None)
295 /* Moose clam cow. */
296 ParseWarning("Unterminated format string on line %d, phrase will be ignored.", m_CurLine);
298 return SMCParse_Continue;
302 /* Allocate the format table */
305 int tmp = m_pMemory->CreateMem(sizeof(int) * pPhrase->fmt_count, (void **)&fmt_list);
307 /* Update the phrase pointer in case it changed */
308 pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
309 pPhrase->fmt_list = tmp;
312 for (size_t i=0; i<pPhrase->fmt_count; i++)
317 /* Now we need to read again... this time into the format buffer */
318 const char *in_ptr = value;
319 const char *idx_ptr = NULL;
320 char *out_ptr = NULL;
321 unsigned int cur_idx = 0;
323 while (*in_ptr != '\0')
325 if (state == Parse_None)
332 } else if (state == Parse_Index) {
335 /* Check the number! */
338 ParseWarning("Format property contains unindexed format string on line %d, phrase will be ignored.", m_CurLine);
340 return SMCParse_Continue;
342 long idx = strtol(idx_ptr, NULL, 10);
343 if (idx < 1 || idx > (long)pPhrase->fmt_count)
345 ParseWarning("Format property contains invalid index '%d' on line %d, phrase will be ignored.", idx, m_CurLine);
347 return SMCParse_Continue;
348 } else if (fmt_list[idx - 1] != -1) {
349 ParseWarning("Format property contains duplicated index '%d' on line %d, phrase will be ignored.", idx, m_CurLine);
351 return SMCParse_Continue;
353 cur_idx = (unsigned int)idx;
354 state = Parse_Format;
356 } else if (!idx_ptr) {
359 } else if (state == Parse_Format) {
364 ParseWarning("Format property contains empty format string on line %d, phrase will be ignored.", m_CurLine);
366 return SMCParse_Continue;
370 /* Now, add this to our table */
371 int tmp_idx = m_pStringTab->AddString(fmt_buf);
372 /* Update pointers and update necessary variables */
373 pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
374 pPhrase->fmt_bytes += strlen(fmt_buf);
375 fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
376 fmt_list[cur_idx - 1] = tmp_idx;
383 /* Check length ... */
384 if ((unsigned)(out_ptr - fmt_buf) >= sizeof(fmt_buf) - 1)
386 ParseWarning("Format property contains format string that exceeds maximum length on line %d, phrase will be ignored.",
389 return SMCParse_Continue;
391 *out_ptr++ = *in_ptr;
397 /* If we've gotten here, we only accepted unique indexes in a range.
398 * Therefore, the list has to be completely filled. Double check anyway.
400 for (size_t i=0; i<pPhrase->fmt_count; i++)
402 if (fmt_list[i] == -1)
404 ParseWarning("Format property contains no string for index %d on line %d, phrase will be ignored.",
408 return SMCParse_Continue;
412 size_t len = strlen(key);
415 ParseWarning("Ignoring translation to invalid language \"%s\" on line %d.", key, m_CurLine);
416 return SMCParse_Continue;
420 if (!m_pTranslator->GetLanguageByCode(key, &lang))
422 /* Ignore if we don't have a language.
423 * :IDEA: issue a one-time alert?
425 return SMCParse_Continue;
428 /* See how many bytes we need for this string, then allocate.
429 * NOTE: THIS SHOULD GUARANTEE THAT WE DO NOT NEED TO NEED TO SIZE CHECK
431 len = strlen(value) + pPhrase->fmt_bytes + 1;
435 out_idx = m_pMemory->CreateMem(len, (void **)&out_buf);
438 pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
441 int *fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
442 trans_t *pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
444 pTrans = &pTrans[lang];
445 pTrans->stridx = out_idx;
447 bool params[MAX_TRANSLATE_PARAMS];
449 /* Build the format order list, if necessary */
452 int tmp = m_pMemory->CreateMem(pPhrase->fmt_count * sizeof(int), (void **)&fmt_order);
454 /* Update pointers */
455 pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
456 pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
457 fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
458 pTrans = &pTrans[lang];
460 /* Now it's safe to save the index */
461 pTrans->fmt_order = tmp;
463 for (unsigned int i=0; i<pPhrase->fmt_count; i++)
467 memset(¶ms[0], 0, sizeof(params));
470 const char *in_ptr = value;
471 char *out_ptr = out_buf;
472 unsigned int order_idx = 0;
473 while (*in_ptr != '\0')
477 switch (*(in_ptr + 1))
492 /* Copy both bytes since we don't know what's going on */
493 *out_ptr++ = *in_ptr++;
494 *out_ptr++ = *in_ptr;
497 /* Skip past the last byte read */
499 } else if (*in_ptr == '{' && fmt_list != NULL) {
500 /* Search for parameters if this is a formatted string */
501 const char *scrap_in_point = in_ptr;
502 const char *digit_start = ++in_ptr;
504 while (*in_ptr != '\0')
506 bytes = g_TextParser.GetUTF8CharBytes(in_ptr);
513 /* Did we get an index? */
514 if (in_ptr == digit_start)
519 long idx = strtol(digit_start, NULL, 10);
520 if (idx < 1 || idx > (int)pPhrase->fmt_count)
529 /* We're safe to insert the string. First mark order. */
530 fmt_order[order_idx++] = (int)idx - 1;
531 /* Now concatenate */
532 out_ptr += sprintf(out_ptr, "%s", m_pStringTab->GetString(fmt_list[idx-1]));
534 params[idx-1] = true;
541 /* Pretend none of this ever happened. Move along now! */
542 in_ptr = scrap_in_point;
543 *out_ptr++ = *in_ptr;
545 *out_ptr++ = *in_ptr;
551 pPhrase->translations++;
554 return SMCParse_Continue;
557 SMCParseResult CPhraseFile::ReadSMC_LeavingSection()
559 if (m_ParseState == PPS_InPhrase)
561 if (m_CurPhrase == -1 && m_LastPhraseString.size())
563 sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
566 m_ParseState = PPS_Phrases;
567 m_LastPhraseString.assign("");
568 } else if (m_ParseState == PPS_Phrases) {
569 m_ParseState = PPS_None;
572 return SMCParse_Continue;
575 void CPhraseFile::ReadSMC_ParseEnd(bool halted, bool failed)
577 /* Check to see if we have any dangling phrases that weren't completed, and scrap them */
578 if ((halted || failed) && m_LastPhraseString.size())
580 sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
584 TransError CPhraseFile::GetTranslation(const char *szPhrase, unsigned int lang_id, Translation *pTrans)
586 if (lang_id >= m_LangCount)
588 return Trans_BadLanguage;
592 if (!sm_trie_retrieve(m_pPhraseLookup, szPhrase, &object))
594 return Trans_BadPhrase;
597 phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(reinterpret_cast<int>(object));
598 trans_t *trans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
600 trans = &trans[lang_id];
602 if (trans->stridx == -1)
604 return Trans_BadPhraseLanguage;
607 pTrans->fmt_order = (int *)m_pMemory->GetAddress(trans->fmt_order);
608 pTrans->fmt_count = pPhrase->fmt_count;
610 pTrans->szPhrase = m_pStringTab->GetString(trans->stridx);
615 const char *CPhraseFile::GetFilename()
617 return m_File.c_str();
620 /**************************
621 ** MAIN TRANSLATOR CODE **
622 **************************/
624 Translator::Translator() : m_ServerLang(LANGUAGE_ENGLISH)
626 m_pStringTab = new BaseStringTable(2048);
627 m_pLCodeLookup = sm_trie_create();
628 strncopy(m_InitialLang, "en", sizeof(m_InitialLang));
631 Translator::~Translator()
633 for (size_t i=0; i<m_Files.size(); i++)
638 for (size_t i=0; i<m_Languages.size(); i++)
640 delete m_Languages[i];
643 sm_trie_destroy(m_pLCodeLookup);
648 ConfigResult Translator::OnSourceModConfigChanged(const char *key,
654 if (strcasecmp(key, "ServerLang") == 0)
656 if (source == ConfigSource_Console)
659 if (!GetLanguageByCode(value, &index))
661 UTIL_Format(error, maxlength, "Language code \"%s\" is not registered", value);
662 return ConfigResult_Reject;
665 m_ServerLang = index;
667 strncopy(m_InitialLang, value, sizeof(m_InitialLang));
670 return ConfigResult_Accept;
673 return ConfigResult_Ignore;
676 void Translator::OnSourceModLevelChange(const char *mapName)
678 /* Refresh language stuff */
679 char path[PLATFORM_MAX_PATH];
680 g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "configs/languages.cfg");
681 RebuildLanguageDatabase(path);
684 void Translator::OnSourceModAllInitialized()
686 AddLanguage("en", "English");
690 //backwards compatibility shim
691 /* hack -- if core.cfg exists, exec it instead. */
692 char path[PLATFORM_MAX_PATH];
693 g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "translations/core.cfg");
695 if (g_LibSys.PathExists(path))
697 id = FindOrAddPhraseFile("core.cfg");
699 id = FindOrAddPhraseFile("core.phrases.txt");
702 g_pCorePhrases = GetFileByIndex(id);
705 bool Translator::GetLanguageByCode(const char *code, unsigned int *index)
709 if (!sm_trie_retrieve(m_pLCodeLookup, code, &_index))
716 *index = reinterpret_cast<unsigned int>(_index);
722 unsigned int Translator::GetLanguageCount()
724 return (unsigned int)m_Languages.size();
727 BaseStringTable *Translator::GetStringTable()
732 unsigned int Translator::FindOrAddPhraseFile(const char *phrase_file)
734 for (size_t i=0; i<m_Files.size(); i++)
736 if (strcmp(m_Files[i]->GetFilename(), phrase_file) == 0)
738 return (unsigned int)i;
742 CPhraseFile *pFile = new CPhraseFile(this, phrase_file);
743 unsigned int idx = (unsigned int)m_Files.size();
745 m_Files.push_back(pFile);
747 pFile->ReparseFile();
752 void Translator::RebuildLanguageDatabase(const char *lang_header_file)
754 /* Erase everything we have */
755 sm_trie_destroy(m_pLCodeLookup);
756 m_pLCodeLookup = sm_trie_create();
757 m_pStringTab->Reset();
759 for (size_t i=0; i<m_Languages.size(); i++)
761 delete m_Languages[i];
767 unsigned int line=0, col=0;
768 if ((err=g_TextParser.ParseFile_SMC(lang_header_file, this, &line, &col)) != SMCParse_Okay)
770 const char *str_err = g_TextParser.GetSMCErrorString(err);
773 str_err = m_CustomError.c_str();
776 g_Logger.LogError("[SM] Failed to parse language header file: \"%s\"", lang_header_file);
777 g_Logger.LogError("[SM] Parse error (line %d, column %d): %s", line, col, str_err);
782 if (!sm_trie_retrieve(m_pLCodeLookup, m_InitialLang, &serverLang))
784 g_Logger.LogError("Server language was set to bad language \"%s\" -- reverting to English", m_InitialLang);
786 strncopy(m_InitialLang, "en", sizeof(m_InitialLang));
787 m_ServerLang = LANGUAGE_ENGLISH;
790 m_ServerLang = reinterpret_cast<unsigned int>(serverLang);
792 if (!m_Languages.size())
794 g_Logger.LogError("[SM] Fatal error, no languages found! Translation will not work.");
797 for (size_t i=0; i<m_Files.size(); i++)
799 m_Files[i]->ReparseFile();
803 void Translator::ReadSMC_ParseStart()
805 m_InLanguageSection = false;
806 m_CustomError.clear();
809 SMCParseResult Translator::ReadSMC_NewSection(const char *name, bool opt_quotes)
811 if (!m_InLanguageSection)
813 if (strcmp(name, "Languages") == 0)
815 m_InLanguageSection = true;
819 if (!m_InLanguageSection)
821 g_Logger.LogError("[SM] Warning: Unrecognized section \"%s\" in languages.cfg", name);
824 return SMCParse_Continue;
827 SMCParseResult Translator::ReadSMC_LeavingSection()
829 m_InLanguageSection = false;
831 return SMCParse_Continue;
834 SMCParseResult Translator::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
836 size_t len = strlen(key);
840 g_Logger.LogError("[SM] Warning encountered parsing languages.cfg file.");
841 g_Logger.LogError("[SM] Invalid language code \"%s\" is being ignored.", key);
844 AddLanguage(key, value);
846 return SMCParse_Continue;
849 bool Translator::AddLanguage(const char *langcode, const char *description)
851 if (sm_trie_retrieve(m_pLCodeLookup, langcode, NULL))
856 Language *pLanguage = new Language;
857 unsigned int idx = m_Languages.size();
859 pLanguage->m_code2[0] = langcode[0];
860 pLanguage->m_code2[1] = langcode[1];
861 pLanguage->m_code2[2] = langcode[2];
862 pLanguage->m_FullName = m_pStringTab->AddString(description);
864 sm_trie_insert(m_pLCodeLookup, langcode, reinterpret_cast<void *>(idx));
866 m_Languages.push_back(pLanguage);
871 CPhraseFile *Translator::GetFileByIndex(unsigned int index)
873 if (index >= m_Files.size())
878 return m_Files[index];
881 size_t Translator::Translate(char *buffer, size_t maxlength, void **params, const Translation *pTrans)
883 void *new_params[MAX_TRANSLATE_PARAMS];
885 /* Rewrite the parameter order */
886 for (unsigned int i=0; i<pTrans->fmt_count; i++)
888 new_params[i] = params[pTrans->fmt_order[i]];
891 return gnprintf(buffer, maxlength, pTrans->szPhrase, new_params);
894 TransError Translator::CoreTrans(int client,
901 /* :TODO: do language stuff here */
905 return Trans_BadPhraseFile;
911 /* Using server lang temporarily until client lang stuff is implemented */
912 if ((err=g_pCorePhrases->GetTranslation(phrase, m_ServerLang, &trans)) != Trans_Okay)
917 size_t len = Translate(buffer, maxlength, params, &trans);
927 unsigned int Translator::GetServerLanguage()
932 unsigned int Translator::GetClientLanguage(int client)
934 return GetServerLanguage();
937 bool Translator::GetLanguageInfo(unsigned int number, const char **code, const char **name)
939 if (number >= GetLanguageCount())
944 Language *l = m_Languages[number];
951 *name = m_pStringTab->GetString(l->m_FullName);