Made this more consistent or something...
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 "Translator.h"
19 #include "TextParsers.h"
20 #include "LibrarySys.h"
21 #include "sm_stringutil.h"
22 #include "sourcemod.h"
24 Translator g_Translator;
25 CPhraseFile *g_pCorePhrases = NULL;
36 unsigned int fmt_count;
37 unsigned int fmt_bytes;
39 unsigned int translations;
42 CPhraseFile::CPhraseFile(Translator *pTranslator, const char *file)
44 m_pStringTab = pTranslator->GetStringTable();
45 m_pMemory = m_pStringTab->GetMemTable();
46 m_LangCount = pTranslator->GetLanguageCount();
48 m_pTranslator = pTranslator;
49 m_pPhraseLookup = NULL;
52 CPhraseFile::~CPhraseFile()
56 sm_trie_destroy(m_pPhraseLookup);
60 void CPhraseFile::ParseError(const char *message, ...)
64 va_start(ap, message);
65 vsnprintf(buffer, sizeof(buffer), message, ap);
68 m_ParseError.assign(buffer);
71 void CPhraseFile::ParseWarning(const char *message, ...)
75 va_start(ap, message);
76 vsnprintf(buffer, sizeof(buffer), message, ap);
81 g_Logger.LogError("[SM] Warning(s) encountered in translation file \"%s\"", m_File.c_str());
85 g_Logger.LogError("[SM] %s", message);
88 void CPhraseFile::ReparseFile()
92 sm_trie_destroy(m_pPhraseLookup);
94 m_pPhraseLookup = sm_trie_create();
96 m_LangCount = m_pTranslator->GetLanguageCount();
104 char path[PLATFORM_MAX_PATH];
105 g_SourceMod.BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "translations/%s", m_File.c_str());
106 unsigned int line=0, col=0;
108 if ((err=g_TextParser.ParseFile_SMC(path, this, &line, &col)) != SMCParse_Okay)
110 const char *msg = g_TextParser.GetSMCErrorString(err);
113 msg = m_ParseError.c_str();
116 g_Logger.LogError("[SM] Fatal error encountered parsing translation file \"%s\"", m_File.c_str());
117 g_Logger.LogError("[SM] Error (line %d, column %d): %s", line, col, msg);
121 void CPhraseFile::ReadSMC_ParseStart()
124 m_ParseState = PPS_None;
126 m_FileLogged = false;
127 m_LastPhraseString.clear();
130 SMCParseResult CPhraseFile::ReadSMC_RawLine(const char *line, unsigned int curline)
134 return SMCParse_Continue;
137 SMCParseResult CPhraseFile::ReadSMC_NewSection(const char *name, bool opt_quotes)
139 bool recognized = false;
140 if (m_ParseState == PPS_None)
142 if (strcmp(name, "Phrases") == 0)
144 m_ParseState = PPS_Phrases;
147 } else if (m_ParseState == PPS_Phrases) {
148 m_ParseState = PPS_InPhrase;
152 m_CurPhrase = m_pMemory->CreateMem(sizeof(phrase_t), (void **)&pPhrase);
154 /* Create the reverse lookup */
155 if (!sm_trie_insert(m_pPhraseLookup, name, reinterpret_cast<void *>(m_CurPhrase)))
157 ParseWarning("Skipping duplicate phrase \"%s\" on line %d.", name, m_CurLine);
158 /* :IDEA: prevent memory waste by seeking backwards in memtable? */
161 /* Initialize new phrase */
164 pPhrase->fmt_count = 0;
165 pPhrase->fmt_list = -1;
166 pPhrase->trans_tbl = m_pMemory->CreateMem(sizeof(trans_t) * m_LangCount, (void **)&pTrans);
167 pPhrase->translations = 0;
168 pPhrase->fmt_bytes = 0;
170 for (unsigned int i=0; i<m_LangCount; i++)
172 pTrans[i].stridx = -1;
174 m_LastPhraseString.assign(name);
176 } else if (m_ParseState == PPS_InPhrase) {
177 ParseError("Phrase sections may not have sub-sections");
178 return SMCParse_HaltFail;
183 ParseWarning("Ignoring invalid section \"%s\" on line %d.", name, m_CurLine);
186 return SMCParse_Continue;
189 SMCParseResult CPhraseFile::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
191 /* See if we are ignoring a phrase */
194 return SMCParse_Continue;
197 phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
199 if (key[0] == '#' && strcmp(key, "#format") == 0)
201 if (pPhrase->fmt_list != -1)
203 ParseWarning("Ignoring duplicated #format property on line %d", m_CurLine);
204 return SMCParse_Continue;
207 if (pPhrase->translations > 0)
209 ParseWarning("#format property should come before translations on line %d, ignoring", m_CurLine);
210 return SMCParse_Continue;
213 /* Do an initial browsing for error checking and what not */
221 ParseStates state = Parse_None;
223 const char *old_value = value;
224 while (*value != '\0')
226 if (state == Parse_None)
230 pPhrase->fmt_count++;
232 } else if (*value == ',') {
235 unsigned int bytes = g_TextParser.GetUTF8CharBytes(value);
236 if (bytes != 1 || !isalpha(*value))
238 ParseWarning("Invalid token '%c' in #format property on line %d.", *value, m_CurLine);
241 } else if (state == Parse_Index) {
244 state = Parse_Format;
245 if (value - old_value >= 15)
247 ParseWarning("Too many digits in format index on line %d, phrase will be ignored.", m_CurLine);
249 return SMCParse_Continue;
252 unsigned int bytes = g_TextParser.GetUTF8CharBytes(value);
253 if (bytes != 1 || !isdigit(*value))
255 ParseWarning("Token '%c' in #format property on line %d is not a digit, phrase will be ignored.",
259 return SMCParse_Continue;
262 } else if (state == Parse_Format) {
271 if (state != Parse_None)
273 /* Moose clam cow. */
274 ParseWarning("Unterminated format string on line %d, phrase will be ignored.", m_CurLine);
276 return SMCParse_Continue;
280 /* Allocate the format table */
283 pPhrase->fmt_list = m_pMemory->CreateMem(sizeof(int) * pPhrase->fmt_count, (void **)&fmt_list);
286 for (size_t i=0; i<pPhrase->fmt_count; i++)
291 /* Now we need to read again... this time into the format buffer */
292 const char *in_ptr = value;
293 const char *idx_ptr = NULL;
294 char *out_ptr = NULL;
295 unsigned int cur_idx = 0;
297 while (*in_ptr != '\0')
299 if (state == Parse_None)
306 } else if (state == Parse_Index) {
309 /* Check the number! */
312 ParseWarning("Format property contains unindexed format string on line %d, phrase will be ignored.", m_CurLine);
314 return SMCParse_Continue;
316 long idx = strtol(idx_ptr, NULL, 10);
317 if (idx < 1 || idx > (long)pPhrase->fmt_count)
319 ParseWarning("Format property contains invalid index '%d' on line %d, phrase will be ignored.", idx, m_CurLine);
321 return SMCParse_Continue;
322 } else if (fmt_list[idx - 1] != -1) {
323 ParseWarning("Format property contains duplicated index '%d' on line %d, phrase will be ignored.", idx, m_CurLine);
325 return SMCParse_Continue;
327 cur_idx = (unsigned int)idx;
328 state = Parse_Format;
330 } else if (!idx_ptr) {
333 } else if (state == Parse_Format) {
338 ParseWarning("Format property contains empty format string on line %d, phrase will be ignored.", m_CurLine);
340 return SMCParse_Continue;
344 /* Now, add this to our table */
345 fmt_list[cur_idx - 1] = m_pStringTab->AddString(fmt_buf);
346 pPhrase->fmt_bytes += strlen(fmt_buf);
353 /* Check length ... */
354 if ((unsigned)(out_ptr - fmt_buf) >= sizeof(fmt_buf) - 1)
356 ParseWarning("Format property contains format string that exceeds maximum length on line %d, phrase will be ignored.",
359 return SMCParse_Continue;
361 *out_ptr++ = *in_ptr;
367 /* If we've gotten here, we only accepted unique indexes in a range.
368 * Therefore, the list has to be completely filled. Double check anyway.
370 for (size_t i=0; i<pPhrase->fmt_count; i++)
372 if (fmt_list[i] == -1)
374 ParseWarning("Format property contains no string for index %d on line %d, phrase will be ignored.",
378 return SMCParse_Continue;
382 size_t len = strlen(key);
385 ParseWarning("Ignoring translation to invalid language \"%s\" on line %d.", key, m_CurLine);
386 return SMCParse_Continue;
390 if (!m_pTranslator->GetLanguageByCode(key, &lang))
392 /* Ignore if we don't have a language.
393 * :IDEA: issue a one-time alert?
395 return SMCParse_Continue;
398 /* See how many bytes we need for this string, then allocate.
399 * NOTE: THIS SHOULD GUARANTEE THAT WE DO NOT NEED TO NEED TO SIZE CHECK
401 len = strlen(value) + pPhrase->fmt_bytes + 1;
405 out_idx = m_pMemory->CreateMem(len, (void **)&out_buf);
408 int *fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
409 trans_t *pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
411 pTrans = &pTrans[lang];
412 pTrans->stridx = out_idx;
414 bool params[MAX_TRANSLATE_PARAMS];
416 /* Build the format order list, if necessary */
419 pTrans->fmt_order = m_pMemory->CreateMem(pPhrase->fmt_count * sizeof(int), (void **)&fmt_order);
421 for (unsigned int i=0; i<pPhrase->fmt_count; i++)
425 memset(¶ms[0], 0, sizeof(params));
428 const char *in_ptr = value;
429 char *out_ptr = out_buf;
430 unsigned int order_idx = 0;
431 while (*in_ptr != '\0')
435 switch (*(in_ptr + 1))
450 /* Copy both bytes since we don't know what's going on */
451 *out_ptr++ = *in_ptr++;
452 *out_ptr++ = *in_ptr;
455 /* Skip past the last byte read */
457 } else if (*in_ptr == '{' && fmt_list != NULL) {
458 /* Search for parameters if this is a formatted string */
459 const char *scrap_in_point = in_ptr;
460 const char *digit_start = ++in_ptr;
462 while (*in_ptr != '\0')
464 bytes = g_TextParser.GetUTF8CharBytes(in_ptr);
471 /* Did we get an index? */
472 if (in_ptr == digit_start)
477 long idx = strtol(digit_start, NULL, 10);
478 if (idx < 1 || idx > (int)pPhrase->fmt_count)
487 /* We're safe to insert the string. First mark order. */
488 fmt_order[order_idx++] = (int)idx - 1;
489 /* Now concatenate */
490 out_ptr += sprintf(out_ptr, "%s", m_pStringTab->GetString(fmt_list[idx-1]));
492 params[idx-1] = true;
499 /* Pretend none of this ever happened. Move along now! */
500 in_ptr = scrap_in_point;
501 *out_ptr++ = *in_ptr;
503 *out_ptr++ = *in_ptr;
509 pPhrase->translations++;
512 return SMCParse_Continue;
515 SMCParseResult CPhraseFile::ReadSMC_LeavingSection()
517 if (m_ParseState == PPS_InPhrase)
519 if (m_CurPhrase == -1 && m_LastPhraseString.size())
521 sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
524 m_ParseState = PPS_Phrases;
525 m_LastPhraseString.assign("");
526 } else if (m_ParseState == PPS_Phrases) {
527 m_ParseState = PPS_None;
530 return SMCParse_Continue;
533 void CPhraseFile::ReadSMC_ParseEnd(bool halted, bool failed)
535 /* Check to see if we have any dangling phrases that weren't completed, and scrap them */
536 if ((halted || failed) && m_LastPhraseString.size())
538 sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
542 TransError CPhraseFile::GetTranslation(const char *szPhrase, unsigned int lang_id, Translation *pTrans)
544 if (lang_id >= m_LangCount)
546 return Trans_BadLanguage;
550 if (!sm_trie_retrieve(m_pPhraseLookup, szPhrase, &object))
552 return Trans_BadPhrase;
555 phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(reinterpret_cast<int>(object));
556 trans_t *trans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
558 trans = &trans[lang_id];
560 if (trans->stridx == -1)
562 return Trans_BadPhraseLanguage;
565 pTrans->fmt_order = (int *)m_pMemory->GetAddress(trans->fmt_order);
566 pTrans->fmt_count = pPhrase->fmt_count;
568 pTrans->szPhrase = m_pStringTab->GetString(trans->stridx);
573 const char *CPhraseFile::GetFilename()
575 return m_File.c_str();
578 /**************************
579 ** MAIN TRANSLATOR CODE **
580 **************************/
582 Translator::Translator()
584 m_pStringTab = new BaseStringTable(2048);
585 m_pLCodeLookup = sm_trie_create();
588 Translator::~Translator()
590 for (size_t i=0; i<m_Files.size(); i++)
595 for (size_t i=0; i<m_Languages.size(); i++)
597 delete m_Languages[i];
600 sm_trie_destroy(m_pLCodeLookup);
605 void Translator::OnSourceModAllInitialized()
607 AddLanguage("en", "English");
608 unsigned int id = FindOrAddPhraseFile("core.cfg");
609 g_pCorePhrases = GetFileByIndex(id);
612 bool Translator::GetLanguageByCode(const char *code, unsigned int *index)
616 if (!sm_trie_retrieve(m_pLCodeLookup, code, &_index))
623 *index = reinterpret_cast<unsigned int>(_index);
629 unsigned int Translator::GetLanguageCount()
631 return (unsigned int)m_Languages.size();
634 BaseStringTable *Translator::GetStringTable()
639 unsigned int Translator::FindOrAddPhraseFile(const char *phrase_file)
641 for (size_t i=0; i<m_Files.size(); i++)
643 if (strcmp(m_Files[i]->GetFilename(), phrase_file) == 0)
645 return (unsigned int)i;
649 CPhraseFile *pFile = new CPhraseFile(this, phrase_file);
650 unsigned int idx = (unsigned int)m_Files.size();
652 m_Files.push_back(pFile);
654 pFile->ReparseFile();
659 void Translator::RebuildLanguageDatabase(const char *lang_header_file)
661 /* Erase everything we have */
662 sm_trie_destroy(m_pLCodeLookup);
663 m_pLCodeLookup = sm_trie_create();
664 m_pStringTab->Reset();
666 for (size_t i=0; i<m_Languages.size(); i++)
668 delete m_Languages[i];
674 unsigned int line=0, col=0;
675 if ((err=g_TextParser.ParseFile_SMC(lang_header_file, this, &line, &col)) != SMCParse_Okay)
677 const char *str_err = g_TextParser.GetSMCErrorString(err);
680 str_err = m_CustomError.c_str();
683 g_Logger.LogError("[SM] Failed to parse language header file: \"%s\"", lang_header_file);
684 g_Logger.LogError("[SM] Parse error (line %d, column %d): %s", line, col, str_err);
687 if (!m_Languages.size())
689 g_Logger.LogError("[SM] Fatal error, no languages found! Translation will not work.");
692 for (size_t i=0; i<m_Files.size(); i++)
694 m_Files[i]->ReparseFile();
698 void Translator::ReadSMC_ParseStart()
700 m_InLanguageSection = false;
701 m_CustomError.clear();
704 SMCParseResult Translator::ReadSMC_NewSection(const char *name, bool opt_quotes)
706 if (!m_InLanguageSection)
708 if (strcmp(name, "Languages") == 0)
710 m_InLanguageSection = true;
714 if (!m_InLanguageSection)
716 g_Logger.LogError("[SM] Warning: Unrecognized section \"%s\" in languages.cfg", name);
719 return SMCParse_Continue;
722 SMCParseResult Translator::ReadSMC_LeavingSection()
724 m_InLanguageSection = false;
726 return SMCParse_Continue;
729 SMCParseResult Translator::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
731 size_t len = strlen(key);
735 g_Logger.LogError("[SM] Warning encountered parsing languages.cfg file.");
736 g_Logger.LogError("[SM] Invalid language code \"%s\" is being ignored.", key);
739 AddLanguage(key, value);
741 return SMCParse_Continue;
744 bool Translator::AddLanguage(const char *langcode, const char *description)
746 if (sm_trie_retrieve(m_pLCodeLookup, langcode, NULL))
751 Language *pLanguage = new Language;
752 unsigned int idx = m_Languages.size();
754 pLanguage->m_code2[0] = langcode[0];
755 pLanguage->m_code2[1] = langcode[1];
756 pLanguage->m_code2[2] = langcode[2];
757 pLanguage->m_FullName = m_pStringTab->AddString(description);
759 sm_trie_insert(m_pLCodeLookup, langcode, reinterpret_cast<void *>(idx));
761 m_Languages.push_back(pLanguage);
766 CPhraseFile *Translator::GetFileByIndex(unsigned int index)
768 if (index >= m_Files.size())
773 return m_Files[index];
776 size_t Translator::Translate(char *buffer, size_t maxlength, void **params, const Translation *pTrans)
778 void *new_params[MAX_TRANSLATE_PARAMS];
780 /* Rewrite the parameter order */
781 for (unsigned int i=0; i<pTrans->fmt_count; i++)
783 new_params[i] = params[pTrans->fmt_order[i]];
786 return gnprintf(buffer, maxlength, pTrans->szPhrase, new_params);
789 TransError Translator::CoreTrans(int client,
796 /* :TODO: do language stuff here */
800 return Trans_BadPhraseFile;
805 if ((err=g_pCorePhrases->GetTranslation(phrase, 0, &trans)) != Trans_Okay)
810 size_t len = Translate(buffer, maxlength, params, &trans);