dvander@591: /** ds@653: * vim: set ts=4 : ds@1330: * ============================================================================= dvander@1231: * SourceMod ds@1947: * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. ds@1330: * ============================================================================= dvander@591: * ds@1330: * This program is free software; you can redistribute it and/or modify it under ds@1330: * the terms of the GNU General Public License, version 3.0, as published by the ds@1330: * Free Software Foundation. dvander@1231: * ds@1330: * This program is distributed in the hope that it will be useful, but WITHOUT ds@1330: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ds@1330: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ds@1330: * details. dvander@1231: * ds@1330: * You should have received a copy of the GNU General Public License along with ds@1330: * this program. If not, see . dvander@1231: * ds@1330: * As a special exception, AlliedModders LLC gives you permission to link the ds@1330: * code of this program (as well as its derivative works) to "Half-Life 2," the ds@1330: * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software ds@1330: * by the Valve Corporation. You must obey the GNU General Public License in ds@1330: * all respects for all other code used. Additionally, AlliedModders LLC grants ds@1330: * this exception to all derivative works. AlliedModders LLC defines further ds@1330: * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), ds@1330: * or . dvander@591: * dvander@591: * Version: $Id$ dvander@591: */ dvander@591: dvander@3004: #include "common_logic.h" dvander@591: #include dvander@591: #include dvander@591: #include dvander@3007: #include dvander@591: #include "Translator.h" dvander@3004: #include dvander@3004: #include dvander@3004: #include dvander@2042: #include "PhraseCollection.h" dvander@3004: #include "stringutil.h" dvander@591: dvander@616: Translator g_Translator; dvander@2042: IPhraseCollection *g_pCorePhrases = NULL; dvander@1621: unsigned int g_pCorePhraseID = 0; dvander@591: dvander@3004: using namespace SourceMod; dvander@3004: dvander@591: struct trans_t dvander@591: { dvander@591: int stridx; dvander@591: int fmt_order; dvander@591: }; dvander@591: dvander@591: struct phrase_t dvander@591: { dvander@591: int fmt_list; dvander@591: unsigned int fmt_count; dvander@591: unsigned int fmt_bytes; dvander@591: int trans_tbl; dvander@591: unsigned int translations; dvander@591: }; dvander@591: dvander@616: CPhraseFile::CPhraseFile(Translator *pTranslator, const char *file) dvander@591: { dvander@591: m_pStringTab = pTranslator->GetStringTable(); dvander@591: m_pMemory = m_pStringTab->GetMemTable(); dvander@591: m_LangCount = pTranslator->GetLanguageCount(); dvander@591: m_File.assign(file); dvander@591: m_pTranslator = pTranslator; dvander@591: } dvander@591: dvander@591: CPhraseFile::~CPhraseFile() dvander@591: { dvander@591: } dvander@591: dvander@591: void CPhraseFile::ParseError(const char *message, ...) dvander@591: { dvander@591: char buffer[1024]; dvander@591: va_list ap; dvander@591: va_start(ap, message); dvander@591: vsnprintf(buffer, sizeof(buffer), message, ap); dvander@591: va_end(ap); dvander@591: dvander@591: m_ParseError.assign(buffer); dvander@591: } dvander@591: dvander@591: void CPhraseFile::ParseWarning(const char *message, ...) dvander@591: { dvander@591: char buffer[1024]; dvander@591: va_list ap; dvander@591: va_start(ap, message); dvander@591: vsnprintf(buffer, sizeof(buffer), message, ap); dvander@591: va_end(ap); dvander@591: dvander@591: if (!m_FileLogged) dvander@591: { dvander@3004: smcore.LogError("[SM] Warning(s) encountered in translation file \"%s\"", m_File.c_str()); dvander@591: m_FileLogged = true; dvander@591: } dvander@591: dvander@3004: smcore.LogError("[SM] %s", buffer); dvander@591: } dvander@591: dvander@591: void CPhraseFile::ReparseFile() dvander@591: { dvander@3004: m_PhraseLookup.clear(); dvander@591: dvander@591: m_LangCount = m_pTranslator->GetLanguageCount(); dvander@591: dvander@591: if (!m_LangCount) dvander@591: { dvander@591: return; dvander@591: } dvander@591: dvander@1656: SMCError err; ds@652: char path[PLATFORM_MAX_PATH]; dvander@3004: g_pSM->BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "translations/%s", m_File.c_str()); dvander@1012: dvander@1013: //backwards compatibility shim dvander@1013: /* :HACKHACK: Change .cfg/.txt and vice versa for compatibility */ dvander@3004: if (!libsys->PathExists(path)) dvander@1012: { dvander@1013: if (m_File.compare("common.cfg") == 0) dvander@1013: { dvander@3005: UTIL_ReplaceAll(path, sizeof(path), "common.cfg", "common.phrases.txt", true); dvander@1013: } else if (strstr(path, ".cfg")) { dvander@3005: UTIL_ReplaceAll(path, sizeof(path), ".cfg", ".txt", true); dvander@1013: } else if (strstr(path, ".txt")) { dvander@3005: UTIL_ReplaceAll(path, sizeof(path), ".txt", ".cfg", true); dvander@1013: } dvander@1012: } dvander@1012: dvander@1656: SMCStates states; dvander@591: dvander@1656: if ((err=textparsers->ParseFile_SMC(path, this, &states)) != SMCError_Okay) dvander@591: { dvander@1406: const char *msg = textparsers->GetSMCErrorString(err); dvander@591: if (!msg) dvander@591: { dvander@591: msg = m_ParseError.c_str(); dvander@591: } dvander@591: dvander@3004: smcore.LogError("[SM] Fatal error encountered parsing translation file \"%s\"", m_File.c_str()); dvander@3004: smcore.LogError("[SM] Error (line %d, column %d): %s", states.line, states.col, msg); dvander@591: } dvander@2340: dvander@2340: const char *code; dvander@2340: for (unsigned int i = 1; i < m_LangCount; i++) dvander@2340: { dvander@2340: if (!m_pTranslator->GetLanguageInfo(i, &code, NULL)) dvander@2340: { dvander@2340: continue; dvander@2340: } dvander@2340: dvander@3004: g_pSM->BuildPath(Path_SM, dvander@2340: path, dvander@2340: PLATFORM_MAX_PATH, dvander@2340: "translations/%s/%s", dvander@2340: code, dvander@2340: m_File.c_str()); dvander@2340: dvander@2340: /* Speculatively load these. */ dvander@3004: if (!libsys->PathExists(path)) dvander@2340: { dvander@2340: continue; dvander@2340: } dvander@2340: dvander@2340: if ((err=textparsers->ParseFile_SMC(path, this, &states)) != SMCError_Okay) dvander@2340: { dvander@2340: const char *msg = textparsers->GetSMCErrorString(err); dvander@2340: if (!msg) dvander@2340: { dvander@2340: msg = m_ParseError.c_str(); dvander@2340: } dvander@2340: dvander@3004: smcore.LogError("[SM] Fatal error encountered parsing translation file \"%s/%s\"", dvander@2340: code, dvander@2340: m_File.c_str()); dvander@3004: smcore.LogError("[SM] Error (line %d, column %d): %s", dvander@2340: states.line, dvander@2340: states.col, dvander@2340: msg); dvander@2340: } dvander@2340: } dvander@591: } dvander@591: dvander@591: void CPhraseFile::ReadSMC_ParseStart() dvander@591: { dvander@591: m_CurPhrase = -1; dvander@591: m_ParseState = PPS_None; dvander@591: m_FileLogged = false; dvander@591: m_LastPhraseString.clear(); dvander@591: } dvander@591: dvander@1656: SMCResult CPhraseFile::ReadSMC_NewSection(const SMCStates *states, const char *name) dvander@591: { dvander@591: bool recognized = false; dvander@591: if (m_ParseState == PPS_None) dvander@591: { dvander@591: if (strcmp(name, "Phrases") == 0) dvander@591: { dvander@591: m_ParseState = PPS_Phrases; dvander@591: recognized = true; dvander@591: } dvander@1656: } dvander@1656: else if (m_ParseState == PPS_Phrases) dvander@1656: { dvander@591: m_ParseState = PPS_InPhrase; dvander@591: recognized = true; dvander@591: dvander@3004: int *pvalue; dvander@3004: if ((pvalue = m_PhraseLookup.retrieve(name)) != NULL) dvander@591: { dvander@3004: m_CurPhrase = *pvalue; dvander@2340: } dvander@2340: else dvander@1656: { dvander@2340: phrase_t *pPhrase; dvander@2340: dvander@2340: m_CurPhrase = m_pMemory->CreateMem(sizeof(phrase_t), (void **)&pPhrase); dvander@3004: m_PhraseLookup.insert(name, m_CurPhrase); dvander@2340: dvander@591: /* Initialize new phrase */ dvander@591: trans_t *pTrans; dvander@2340: dvander@591: pPhrase->fmt_count = 0; dvander@591: pPhrase->fmt_list = -1; dvander@887: dvander@887: int trans_tbl = m_pMemory->CreateMem(sizeof(trans_t) * m_LangCount, (void **)&pTrans); dvander@887: pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase); dvander@887: pPhrase->trans_tbl = trans_tbl; dvander@887: dvander@591: pPhrase->translations = 0; dvander@591: pPhrase->fmt_bytes = 0; dvander@2340: dvander@591: for (unsigned int i=0; iline); dvander@591: } dvander@591: dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: dvander@1656: SMCResult CPhraseFile::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value) dvander@591: { dvander@591: /* See if we are ignoring a phrase */ dvander@933: if (m_CurPhrase == -1) dvander@591: { dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: dvander@591: phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase); dvander@2340: dvander@2340: /* Duplicate format keys get silently ignored. */ dvander@2340: if (key[0] == '#' dvander@2340: && strcmp(key, "#format") == 0 dvander@2340: && pPhrase->fmt_list == -1) dvander@591: { dvander@591: if (pPhrase->translations > 0) dvander@591: { dvander@1656: ParseWarning("#format property should come before translations on line %d, ignoring", states->line); dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: dvander@591: /* Do an initial browsing for error checking and what not */ dvander@591: enum ParseStates dvander@591: { dvander@591: Parse_None, dvander@591: Parse_Index, dvander@591: Parse_Format, dvander@591: }; dvander@591: dvander@591: ParseStates state = Parse_None; dvander@591: dvander@591: const char *old_value = value; dvander@933: const char *last_value = value; dvander@591: while (*value != '\0') dvander@591: { dvander@591: if (state == Parse_None) dvander@591: { dvander@591: if (*value == '{') dvander@591: { dvander@591: pPhrase->fmt_count++; dvander@591: state = Parse_Index; dvander@1656: } dvander@1656: else if (*value == ',') dvander@1656: { dvander@591: /* Do nothing */ dvander@1656: } dvander@1656: else dvander@1656: { dvander@1406: unsigned int bytes = textparsers->GetUTF8CharBytes(value); dvander@591: if (bytes != 1 || !isalpha(*value)) dvander@591: { dvander@1656: ParseWarning("Invalid token '%c' in #format property on line %d.", *value, states->line); dvander@591: } dvander@591: } dvander@1656: } dvander@1656: else if (state == Parse_Index) dvander@1656: { dvander@591: if (*value == ':') dvander@591: { dvander@591: state = Parse_Format; dvander@933: if (value - last_value >= 15) dvander@591: { dvander@1656: ParseWarning("Too many digits in format index on line %d, phrase will be ignored.", states->line); dvander@591: m_CurPhrase = -1; dvander@1656: return SMCResult_Continue; dvander@591: } dvander@1656: } dvander@1656: else dvander@1656: { dvander@1406: unsigned int bytes = textparsers->GetUTF8CharBytes(value); dvander@591: if (bytes != 1 || !isdigit(*value)) dvander@591: { dvander@591: ParseWarning("Token '%c' in #format property on line %d is not a digit, phrase will be ignored.", dvander@591: *value, dvander@1656: states->line); dvander@591: m_CurPhrase = -1; dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: } dvander@1656: } dvander@1656: else if (state == Parse_Format) dvander@1656: { dvander@591: if (*value == '}') dvander@591: { dvander@591: state = Parse_None; dvander@933: last_value = value + 1; dvander@591: } dvander@591: } dvander@591: value++; dvander@591: } dvander@591: dvander@591: if (state != Parse_None) dvander@591: { dvander@591: /* Moose clam cow. */ dvander@1656: ParseWarning("Unterminated format string on line %d, phrase will be ignored.", states->line); dvander@591: m_CurPhrase = -1; dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: value = old_value; dvander@591: dvander@591: /* Allocate the format table */ dvander@591: char fmt_buf[16]; dvander@591: int *fmt_list; dvander@1133: int tmp = m_pMemory->CreateMem(sizeof(int) * pPhrase->fmt_count, (void **)&fmt_list); dvander@1133: dvander@1133: /* Update the phrase pointer in case it changed */ dvander@1133: pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase); dvander@1133: pPhrase->fmt_list = tmp; dvander@591: dvander@591: /* Initialize */ dvander@591: for (size_t i=0; ifmt_count; i++) dvander@591: { dvander@591: fmt_list[i] = -1; dvander@591: } dvander@591: dvander@591: /* Now we need to read again... this time into the format buffer */ dvander@591: const char *in_ptr = value; dvander@591: const char *idx_ptr = NULL; dvander@591: char *out_ptr = NULL; dvander@591: unsigned int cur_idx = 0; dvander@591: state = Parse_None; dvander@591: while (*in_ptr != '\0') dvander@591: { dvander@591: if (state == Parse_None) dvander@591: { dvander@591: if (*in_ptr == '{') dvander@591: { dvander@591: state = Parse_Index; dvander@591: idx_ptr = NULL; dvander@591: } dvander@1656: } dvander@1656: else if (state == Parse_Index) dvander@1656: { dvander@591: if (*in_ptr == ':') dvander@591: { dvander@591: /* Check the number! */ dvander@591: if (!idx_ptr) dvander@591: { dvander@1656: ParseWarning("Format property contains unindexed format string on line %d, phrase will be ignored.", states->line); dvander@591: m_CurPhrase = -1; dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: long idx = strtol(idx_ptr, NULL, 10); dvander@591: if (idx < 1 || idx > (long)pPhrase->fmt_count) dvander@591: { dvander@1656: ParseWarning("Format property contains invalid index '%d' on line %d, phrase will be ignored.", idx, states->line); dvander@591: m_CurPhrase = -1; dvander@1656: return SMCResult_Continue; dvander@1656: } dvander@1656: else if (fmt_list[idx - 1] != -1) dvander@1656: { dvander@1656: ParseWarning("Format property contains duplicated index '%d' on line %d, phrase will be ignored.", idx, states->line); dvander@591: m_CurPhrase = -1; dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: cur_idx = (unsigned int)idx; dvander@591: state = Parse_Format; dvander@591: out_ptr = NULL; dvander@1656: } dvander@1656: else if (!idx_ptr) dvander@1656: { dvander@591: idx_ptr = in_ptr; dvander@591: } dvander@1656: } dvander@1656: else if (state == Parse_Format) dvander@1656: { dvander@591: if (*in_ptr == '}') dvander@591: { dvander@591: if (!out_ptr) dvander@591: { dvander@1656: ParseWarning("Format property contains empty format string on line %d, phrase will be ignored.", states->line); dvander@591: m_CurPhrase = -1; dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: *out_ptr = '\0'; dvander@591: state = Parse_None; dvander@591: /* Now, add this to our table */ dvander@1134: int tmp_idx = m_pStringTab->AddString(fmt_buf); dvander@1134: /* Update pointers and update necessary variables */ dvander@1133: pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase); dvander@1134: pPhrase->fmt_bytes += strlen(fmt_buf); dvander@1133: fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list); dvander@1134: fmt_list[cur_idx - 1] = tmp_idx; dvander@1656: } dvander@1656: else dvander@1656: { dvander@591: if (!out_ptr) dvander@591: { dvander@591: out_ptr = fmt_buf; dvander@591: *out_ptr++ = '%'; dvander@591: } dvander@591: /* Check length ... */ dvander@591: if ((unsigned)(out_ptr - fmt_buf) >= sizeof(fmt_buf) - 1) dvander@591: { dvander@591: ParseWarning("Format property contains format string that exceeds maximum length on line %d, phrase will be ignored.", dvander@1656: states->line); dvander@591: m_CurPhrase = -1; dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: *out_ptr++ = *in_ptr; dvander@591: } dvander@591: } dvander@591: in_ptr++; dvander@591: } dvander@591: dvander@591: /* If we've gotten here, we only accepted unique indexes in a range. dvander@591: * Therefore, the list has to be completely filled. Double check anyway. dvander@591: */ dvander@591: for (size_t i=0; ifmt_count; i++) dvander@591: { dvander@591: if (fmt_list[i] == -1) dvander@591: { dvander@591: ParseWarning("Format property contains no string for index %d on line %d, phrase will be ignored.", dvander@591: i + 1, dvander@1656: states->line); dvander@591: m_CurPhrase = -1; dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: } dvander@1656: } dvander@1656: else dvander@1656: { dvander@591: size_t len = strlen(key); dvander@2478: if (len < 2 || len > 3) dvander@591: { dvander@1656: ParseWarning("Ignoring translation to invalid language \"%s\" on line %d.", key, states->line); dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: dvander@591: unsigned int lang; dvander@591: if (!m_pTranslator->GetLanguageByCode(key, &lang)) dvander@591: { dvander@591: /* Ignore if we don't have a language. dvander@591: * :IDEA: issue a one-time alert? dvander@591: */ dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: dvander@591: /* See how many bytes we need for this string, then allocate. dvander@591: * NOTE: THIS SHOULD GUARANTEE THAT WE DO NOT NEED TO NEED TO SIZE CHECK dvander@591: */ dvander@591: len = strlen(value) + pPhrase->fmt_bytes + 1; dvander@591: char *out_buf; dvander@591: int out_idx; dvander@591: dvander@591: out_idx = m_pMemory->CreateMem(len, (void **)&out_buf); dvander@591: dvander@1133: /* Update pointer */ dvander@1133: pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase); dvander@1133: dvander@591: int *fmt_order; dvander@591: int *fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list); dvander@591: trans_t *pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl); dvander@591: dvander@591: pTrans = &pTrans[lang]; dvander@591: pTrans->stridx = out_idx; dvander@591: dvander@591: bool params[MAX_TRANSLATE_PARAMS]; dvander@591: dvander@591: /* Build the format order list, if necessary */ dvander@591: if (fmt_list) dvander@591: { dvander@1133: int tmp = m_pMemory->CreateMem(pPhrase->fmt_count * sizeof(int), (void **)&fmt_order); dvander@1133: dvander@1133: /* Update pointers */ dvander@1133: pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase); dvander@1133: pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl); dvander@1133: fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list); dvander@1624: out_buf = (char *)m_pMemory->GetAddress(out_idx); dvander@1133: pTrans = &pTrans[lang]; dvander@1133: dvander@1133: /* Now it's safe to save the index */ dvander@1133: pTrans->fmt_order = tmp; dvander@591: dvander@591: for (unsigned int i=0; ifmt_count; i++) dvander@591: { dvander@591: fmt_order[i] = -1; dvander@591: } dvander@591: memset(¶ms[0], 0, sizeof(params)); dvander@591: } dvander@591: dvander@591: const char *in_ptr = value; dvander@591: char *out_ptr = out_buf; dvander@591: unsigned int order_idx = 0; dvander@591: while (*in_ptr != '\0') dvander@591: { dvander@591: if (*in_ptr == '\\') dvander@591: { dvander@591: switch (*(in_ptr + 1)) dvander@591: { dvander@591: case '\n': dvander@591: *out_ptr++ = '\n'; dvander@591: break; dvander@591: case '\t': dvander@591: *out_ptr++ = '\t'; dvander@591: break; dvander@591: case '\r': dvander@591: *out_ptr++ = '\r'; dvander@591: break; dvander@591: case '{': dvander@591: *out_ptr++ = '{'; dvander@591: break; dvander@591: default: dvander@591: /* Copy both bytes since we don't know what's going on */ dvander@591: *out_ptr++ = *in_ptr++; dvander@591: *out_ptr++ = *in_ptr; dvander@591: break; dvander@591: } dvander@591: /* Skip past the last byte read */ dvander@591: in_ptr++; dvander@1656: } dvander@1656: else if (*in_ptr == '{' && fmt_list != NULL) dvander@1656: { dvander@591: /* Search for parameters if this is a formatted string */ dvander@591: const char *scrap_in_point = in_ptr; dvander@591: const char *digit_start = ++in_ptr; dvander@591: unsigned int bytes; dvander@591: while (*in_ptr != '\0') dvander@591: { dvander@1406: bytes = textparsers->GetUTF8CharBytes(in_ptr); dvander@591: if (bytes != 1) dvander@591: { dvander@591: goto scrap_point; dvander@591: } dvander@591: if (*in_ptr == '}') dvander@591: { dvander@591: /* Did we get an index? */ dvander@591: if (in_ptr == digit_start) dvander@591: { dvander@591: goto scrap_point; dvander@591: } dvander@591: /* Is it valid? */ dvander@591: long idx = strtol(digit_start, NULL, 10); dvander@591: if (idx < 1 || idx > (int)pPhrase->fmt_count) dvander@591: { dvander@591: goto scrap_point; dvander@591: } dvander@591: if (params[idx-1]) dvander@591: { dvander@591: goto scrap_point; dvander@591: } dvander@591: dvander@591: /* We're safe to insert the string. First mark order. */ dvander@591: fmt_order[order_idx++] = (int)idx - 1; dvander@591: /* Now concatenate */ dvander@591: out_ptr += sprintf(out_ptr, "%s", m_pStringTab->GetString(fmt_list[idx-1])); dvander@591: /* Mark as used */ dvander@591: params[idx-1] = true; dvander@591: dvander@591: goto cont_loop; dvander@591: } dvander@591: in_ptr++; dvander@591: } dvander@591: scrap_point: dvander@591: /* Pretend none of this ever happened. Move along now! */ dvander@591: in_ptr = scrap_in_point; dvander@591: *out_ptr++ = *in_ptr; dvander@591: } else { dvander@591: *out_ptr++ = *in_ptr; dvander@591: } dvander@591: cont_loop: dvander@591: in_ptr++; dvander@591: } dvander@591: *out_ptr = '\0'; dvander@591: pPhrase->translations++; dvander@591: } dvander@591: dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: dvander@1656: SMCResult CPhraseFile::ReadSMC_LeavingSection(const SMCStates *states) dvander@591: { dvander@591: if (m_ParseState == PPS_InPhrase) dvander@591: { dvander@591: if (m_CurPhrase == -1 && m_LastPhraseString.size()) dvander@3004: m_PhraseLookup.remove(m_LastPhraseString.c_str()); dvander@591: m_CurPhrase = -1; dvander@591: m_ParseState = PPS_Phrases; dvander@591: m_LastPhraseString.assign(""); dvander@1656: } dvander@1656: else if (m_ParseState == PPS_Phrases) dvander@1656: { dvander@591: m_ParseState = PPS_None; dvander@591: } dvander@591: dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: dvander@591: void CPhraseFile::ReadSMC_ParseEnd(bool halted, bool failed) dvander@591: { dvander@591: /* Check to see if we have any dangling phrases that weren't completed, and scrap them */ dvander@591: if ((halted || failed) && m_LastPhraseString.size()) dvander@3004: m_PhraseLookup.remove(m_LastPhraseString.c_str()); dvander@591: } dvander@591: dvander@591: TransError CPhraseFile::GetTranslation(const char *szPhrase, unsigned int lang_id, Translation *pTrans) dvander@591: { dvander@591: if (lang_id >= m_LangCount) dvander@591: { dvander@591: return Trans_BadLanguage; dvander@591: } dvander@591: dvander@3004: int *pvalue; dvander@3004: if ((pvalue = m_PhraseLookup.retrieve(szPhrase)) == NULL) dvander@591: return Trans_BadPhrase; dvander@591: dvander@3004: phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(*pvalue); dvander@591: trans_t *trans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl); dvander@591: dvander@591: trans = &trans[lang_id]; dvander@591: dvander@591: if (trans->stridx == -1) dvander@591: { dvander@591: return Trans_BadPhraseLanguage; dvander@591: } dvander@591: dvander@591: pTrans->fmt_count = pPhrase->fmt_count; dvander@1656: pTrans->fmt_order = pTrans->fmt_count > 0 ? (int *)m_pMemory->GetAddress(trans->fmt_order) : NULL; dvander@591: pTrans->szPhrase = m_pStringTab->GetString(trans->stridx); dvander@591: dvander@591: return Trans_Okay; dvander@591: } dvander@591: dvander@591: const char *CPhraseFile::GetFilename() dvander@591: { dvander@591: return m_File.c_str(); dvander@591: } dvander@591: dvander@591: /************************** dvander@591: ** MAIN TRANSLATOR CODE ** dvander@591: **************************/ dvander@591: dvander@2042: Translator::Translator() : m_ServerLang(SOURCEMOD_LANGUAGE_ENGLISH) dvander@591: { dvander@591: m_pStringTab = new BaseStringTable(2048); ds@669: strncopy(m_InitialLang, "en", sizeof(m_InitialLang)); dvander@591: } dvander@591: dvander@616: Translator::~Translator() dvander@591: { dvander@591: for (size_t i=0; iBuildPath(Path_SM, path, sizeof(path), "configs/languages.cfg"); dvander@673: RebuildLanguageDatabase(path); dvander@673: } dvander@673: dvander@616: void Translator::OnSourceModAllInitialized() dvander@591: { dvander@591: AddLanguage("en", "English"); dvander@1012: dvander@2042: g_pCorePhrases = CreatePhraseCollection(); dvander@2042: g_pCorePhrases->AddPhraseFile("core.phrases"); dvander@1012: dvander@3004: sharesys->AddInterface(NULL, this); dvander@2042: } dvander@2042: dvander@2042: void Translator::OnSourceModShutdown() dvander@2042: { dvander@2042: g_pCorePhrases->Destroy(); dvander@591: } dvander@591: dvander@616: bool Translator::GetLanguageByCode(const char *code, unsigned int *index) dvander@591: { dvander@3004: unsigned int *pindex; dvander@591: dvander@3004: if ((pindex = m_LCodeLookup.retrieve(code)) == NULL) dvander@591: return false; dvander@591: dvander@591: if (index) dvander@3004: *index = *pindex; dvander@591: dvander@591: return true; dvander@591: } dvander@591: faluco@1352: bool Translator::GetLanguageByName(const char *name, unsigned int *index) faluco@1352: { faluco@1352: CVector::iterator iter; faluco@1352: unsigned int id = 0; faluco@1352: faluco@1352: for (iter=m_Languages.begin(); iter!=m_Languages.end(); iter++, id++) faluco@1352: { faluco@1352: if (strcasecmp(m_pStringTab->GetString((*iter)->m_FullName), name) == 0) faluco@1352: { faluco@1352: break; faluco@1352: } faluco@1352: } faluco@1352: faluco@1352: if (iter == m_Languages.end()) faluco@1352: { faluco@1352: return false; faluco@1352: } faluco@1352: faluco@1352: if (index) faluco@1352: { faluco@1352: *index = id; faluco@1352: } faluco@1352: faluco@1352: return true; faluco@1352: } faluco@1352: dvander@616: unsigned int Translator::GetLanguageCount() dvander@591: { dvander@591: return (unsigned int)m_Languages.size(); dvander@591: } dvander@591: dvander@616: BaseStringTable *Translator::GetStringTable() dvander@591: { dvander@591: return m_pStringTab; dvander@591: } dvander@591: dvander@616: unsigned int Translator::FindOrAddPhraseFile(const char *phrase_file) dvander@591: { dvander@591: for (size_t i=0; iGetFilename(), phrase_file) == 0) dvander@591: { dvander@591: return (unsigned int)i; dvander@591: } dvander@591: } dvander@591: dvander@591: CPhraseFile *pFile = new CPhraseFile(this, phrase_file); dvander@591: unsigned int idx = (unsigned int)m_Files.size(); dvander@591: dvander@591: m_Files.push_back(pFile); dvander@591: dvander@591: pFile->ReparseFile(); dvander@591: dvander@591: return idx; dvander@591: } dvander@591: dvander@616: void Translator::RebuildLanguageDatabase(const char *lang_header_file) dvander@591: { dvander@591: /* Erase everything we have */ dvander@3004: m_LCodeLookup.clear(); dvander@591: m_pStringTab->Reset(); dvander@591: dvander@591: for (size_t i=0; iParseFile_SMC(lang_header_file, this, &states)) != SMCError_Okay) dvander@591: { dvander@1406: const char *str_err = textparsers->GetSMCErrorString(err); dvander@591: if (!str_err) dvander@591: { dvander@591: str_err = m_CustomError.c_str(); dvander@591: } dvander@591: dvander@3004: smcore.LogError("[SM] Failed to parse language header file: \"%s\"", lang_header_file); dvander@3004: smcore.LogError("[SM] Parse error (line %d, column %d): %s", states.line, states.col, str_err); dvander@591: } dvander@591: dvander@3004: unsigned int *pServerLang; ds@669: dvander@3004: if ((pServerLang = m_LCodeLookup.retrieve(m_InitialLang)) == NULL) ds@669: { dvander@3004: smcore.LogError("Server language was set to bad language \"%s\" -- reverting to English", m_InitialLang); ds@669: dvander@3004: smcore.strncopy(m_InitialLang, "en", sizeof(m_InitialLang)); dvander@2042: m_ServerLang = SOURCEMOD_LANGUAGE_ENGLISH; ds@669: } fyrenmoo@3274: else fyrenmoo@3274: { fyrenmoo@3274: m_ServerLang = *pServerLang; fyrenmoo@3274: } ds@669: dvander@591: if (!m_Languages.size()) dvander@591: { dvander@3004: smcore.LogError("[SM] Fatal error, no languages found! Translation will not work."); dvander@591: } dvander@591: dvander@591: for (size_t i=0; iReparseFile(); dvander@591: } dvander@591: } dvander@591: dvander@616: void Translator::ReadSMC_ParseStart() dvander@591: { dvander@591: m_InLanguageSection = false; dvander@591: m_CustomError.clear(); dvander@591: } dvander@591: dvander@1656: SMCResult Translator::ReadSMC_NewSection(const SMCStates *states, const char *name) dvander@591: { dvander@591: if (!m_InLanguageSection) dvander@591: { dvander@591: if (strcmp(name, "Languages") == 0) dvander@591: { dvander@591: m_InLanguageSection = true; dvander@591: } dvander@591: } dvander@591: dvander@591: if (!m_InLanguageSection) dvander@591: { dvander@3004: smcore.LogError("[SM] Warning: Unrecognized section \"%s\" in languages.cfg", name); dvander@591: } dvander@591: dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: dvander@1656: SMCResult Translator::ReadSMC_LeavingSection(const SMCStates *states) dvander@591: { dvander@591: m_InLanguageSection = false; dvander@591: dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: dvander@1656: SMCResult Translator::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value) dvander@591: { dvander@591: size_t len = strlen(key); dvander@591: dvander@2478: if (len < 2 || len > 3) dvander@591: { dvander@3004: smcore.LogError("[SM] Warning encountered parsing languages.cfg file."); dvander@3004: smcore.LogError("[SM] Invalid language code \"%s\" is being ignored.", key); dvander@591: } dvander@591: dvander@591: AddLanguage(key, value); dvander@591: dvander@1656: return SMCResult_Continue; dvander@591: } dvander@591: dvander@616: bool Translator::AddLanguage(const char *langcode, const char *description) dvander@591: { dvander@3004: if (m_LCodeLookup.retrieve(langcode) != NULL) dvander@591: return false; dvander@591: dvander@591: Language *pLanguage = new Language; dvander@591: unsigned int idx = m_Languages.size(); dvander@591: dvander@3004: smcore.Format(pLanguage->m_code2, sizeof(pLanguage->m_code2), "%s", langcode); dvander@591: pLanguage->m_FullName = m_pStringTab->AddString(description); dvander@591: dvander@3004: m_LCodeLookup.insert(langcode, idx); dvander@591: dvander@591: m_Languages.push_back(pLanguage); dvander@591: dvander@591: return true; dvander@591: } dvander@591: dvander@616: CPhraseFile *Translator::GetFileByIndex(unsigned int index) dvander@591: { dvander@591: if (index >= m_Files.size()) dvander@591: { dvander@591: return NULL; dvander@591: } dvander@591: dvander@591: return m_Files[index]; dvander@591: } dvander@591: ds@669: unsigned int Translator::GetServerLanguage() ds@661: { ds@669: return m_ServerLang; ds@661: } dvander@1105: dvander@1105: unsigned int Translator::GetClientLanguage(int client) dvander@1105: { dvander@3004: IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(client); faluco@1352: return pPlayer->GetLanguageId(); dvander@1105: } dvander@1105: dvander@1105: bool Translator::GetLanguageInfo(unsigned int number, const char **code, const char **name) dvander@1105: { dvander@1105: if (number >= GetLanguageCount()) dvander@1105: { dvander@1105: return false; dvander@1105: } dvander@1105: dvander@1105: Language *l = m_Languages[number]; dvander@1105: if (code) dvander@1105: { dvander@1105: *code = l->m_code2; dvander@1105: } dvander@1105: if (name) dvander@1105: { dvander@1105: *name = m_pStringTab->GetString(l->m_FullName); dvander@1105: } dvander@1105: dvander@1105: return true; dvander@1105: } dvander@2042: dvander@2042: const char *Translator::GetInterfaceName() dvander@2042: { dvander@2042: return SMINTERFACE_TRANSLATOR_NAME; dvander@2042: } dvander@2042: dvander@2042: unsigned int Translator::GetInterfaceVersion() dvander@2042: { dvander@2042: return SMINTERFACE_TRANSLATOR_VERSION; dvander@2042: } dvander@2042: dvander@2042: IPhraseCollection *Translator::CreatePhraseCollection() dvander@2042: { dvander@2042: return new CPhraseCollection(); dvander@2042: } dvander@2042: dvander@2042: int Translator::SetGlobalTarget(int index) dvander@2042: { dvander@3004: return g_pSM->SetGlobalTarget(index); dvander@2042: } dvander@2042: dvander@2042: int Translator::GetGlobalTarget() const dvander@2042: { dvander@3004: return g_pSM->GetGlobalTarget(); dvander@2042: } dvander@2042: dvander@3004: bool CoreTranslate(char *buffer, size_t maxlength, const char *format, unsigned int numparams, dvander@3004: size_t *pOutLength, ...) dvander@2042: { dvander@2042: va_list ap; dvander@2042: unsigned int i; dvander@2042: const char *fail_phrase; dvander@2042: void *params[MAX_TRANSLATE_PARAMS]; dvander@2042: dvander@2042: if (numparams > MAX_TRANSLATE_PARAMS) dvander@2042: { dvander@2042: assert(false); dvander@2042: return false; dvander@2042: } dvander@2042: dvander@2042: va_start(ap, pOutLength); dvander@2042: for (i = 0; i < numparams; i++) dvander@2042: { dvander@2042: params[i] = va_arg(ap, void *); dvander@2042: } dvander@2042: va_end(ap); dvander@2042: dvander@2042: if (!g_pCorePhrases->FormatString(buffer, dvander@2042: maxlength, dvander@2042: format, dvander@2042: params, dvander@2042: numparams, dvander@2042: pOutLength, dvander@2042: &fail_phrase)) dvander@2042: { dvander@2042: if (fail_phrase != NULL) dvander@2042: { dvander@3004: smcore.LogError("[SM] Could not find core phrase: %s", fail_phrase); dvander@2042: } dvander@2042: else dvander@2042: { dvander@3004: smcore.LogError("[SM] Unknown fatal error while translating a core phrase."); dvander@2042: } dvander@2042: dvander@2042: return false; dvander@2042: } dvander@2042: dvander@2042: return true; dvander@2042: } dvander@2042: dvander@2042: bool Translator::FormatString(char *buffer, dvander@2042: size_t maxlength, dvander@2042: const char *format, dvander@2042: IPhraseCollection *pPhrases, dvander@2042: void **params, dvander@2042: unsigned int numparams, dvander@2042: size_t *pOutLength, dvander@2042: const char **pFailPhrase) dvander@2042: { dvander@2042: unsigned int arg; dvander@2042: dvander@2042: arg = 0; dvander@3004: if (!smcore.gnprintf(buffer, maxlength, format, pPhrases, params, numparams, arg, pOutLength, pFailPhrase)) dvander@2042: { dvander@2042: return false; dvander@2042: } dvander@2042: dvander@2042: if (arg != numparams) dvander@2042: { dvander@2042: if (pFailPhrase != NULL) dvander@2042: { dvander@2042: *pFailPhrase = NULL; dvander@2042: } dvander@2042: return false; dvander@2042: } dvander@2042: dvander@2042: return true; dvander@2042: }