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@591: #include
dvander@591: #include
dvander@591: #include
dvander@591: #include "Translator.h"
dvander@592: #include "Logger.h"
dvander@591: #include "LibrarySys.h"
dvander@591: #include "sm_stringutil.h"
dvander@591: #include "sourcemod.h"
faluco@1352: #include "PlayerManager.h"
dvander@2042: #include "PhraseCollection.h"
dvander@2042: #include "ShareSys.h"
dvander@591:
dvander@616: Translator g_Translator;
dvander@2042: IPhraseCollection *g_pCorePhrases = NULL;
dvander@1621: unsigned int g_pCorePhraseID = 0;
dvander@591:
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: m_pPhraseLookup = NULL;
dvander@591: }
dvander@591:
dvander@591: CPhraseFile::~CPhraseFile()
dvander@591: {
dvander@591: if (m_pPhraseLookup)
dvander@591: {
dvander@591: sm_trie_destroy(m_pPhraseLookup);
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@591: g_Logger.LogError("[SM] Warning(s) encountered in translation file \"%s\"", m_File.c_str());
dvander@591: m_FileLogged = true;
dvander@591: }
dvander@591:
pred@2279: g_Logger.LogError("[SM] %s", buffer);
dvander@591: }
dvander@591:
dvander@591: void CPhraseFile::ReparseFile()
dvander@591: {
dvander@591: if (m_pPhraseLookup)
dvander@591: {
dvander@591: sm_trie_destroy(m_pPhraseLookup);
dvander@591: }
dvander@591: m_pPhraseLookup = sm_trie_create();
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@591: g_SourceMod.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@1012: if (!g_LibSys.PathExists(path))
dvander@1012: {
dvander@1013: if (m_File.compare("common.cfg") == 0)
dvander@1013: {
dvander@1013: UTIL_ReplaceAll(path, sizeof(path), "common.cfg", "common.phrases.txt");
dvander@1013: } else if (strstr(path, ".cfg")) {
dvander@1013: UTIL_ReplaceAll(path, sizeof(path), ".cfg", ".txt");
dvander@1013: } else if (strstr(path, ".txt")) {
dvander@1013: UTIL_ReplaceAll(path, sizeof(path), ".txt", ".cfg");
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@591: g_Logger.LogError("[SM] Fatal error encountered parsing translation file \"%s\"", m_File.c_str());
dvander@1656: g_Logger.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@2340: g_SourceMod.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@2340: if (!g_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@2340: g_Logger.LogError("[SM] Fatal error encountered parsing translation file \"%s/%s\"",
dvander@2340: code,
dvander@2340: m_File.c_str());
dvander@2340: g_Logger.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@2340: void *value;
dvander@2340: if (sm_trie_retrieve(m_pPhraseLookup, name, &value))
dvander@591: {
dvander@2340: m_CurPhrase = reinterpret_cast(value);
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@2340: sm_trie_insert(m_pPhraseLookup, name, reinterpret_cast(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@591: if (len != 2)
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@591: {
dvander@591: sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
dvander@591: }
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@591: {
dvander@591: sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
dvander@591: }
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@591: void *object;
dvander@591: if (!sm_trie_retrieve(m_pPhraseLookup, szPhrase, &object))
dvander@591: {
dvander@591: return Trans_BadPhrase;
dvander@591: }
dvander@591:
dvander@591: phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(reinterpret_cast(object));
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);
dvander@591: m_pLCodeLookup = sm_trie_create();
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; iAddPhraseFile("core.phrases");
dvander@1012:
dvander@2042: g_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@591: void *_index;
dvander@591:
dvander@591: if (!sm_trie_retrieve(m_pLCodeLookup, code, &_index))
dvander@591: {
dvander@591: return false;
dvander@591: }
dvander@591:
dvander@591: if (index)
dvander@591: {
dvander@591: *index = reinterpret_cast(_index);
dvander@591: }
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@591: sm_trie_destroy(m_pLCodeLookup);
dvander@591: m_pLCodeLookup = sm_trie_create();
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@591: g_Logger.LogError("[SM] Failed to parse language header file: \"%s\"", lang_header_file);
dvander@1656: g_Logger.LogError("[SM] Parse error (line %d, column %d): %s", states.line, states.col, str_err);
dvander@591: }
dvander@591:
ds@669: void *serverLang;
ds@669:
ds@669: if (!sm_trie_retrieve(m_pLCodeLookup, m_InitialLang, &serverLang))
ds@669: {
ds@669: g_Logger.LogError("Server language was set to bad language \"%s\" -- reverting to English", m_InitialLang);
ds@669:
ds@669: strncopy(m_InitialLang, "en", sizeof(m_InitialLang));
dvander@2042: m_ServerLang = SOURCEMOD_LANGUAGE_ENGLISH;
ds@669: }
ds@669:
ds@669: m_ServerLang = reinterpret_cast(serverLang);
ds@669:
dvander@591: if (!m_Languages.size())
dvander@591: {
dvander@591: g_Logger.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@591: g_Logger.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@591: if (len != 2)
dvander@591: {
dvander@591: g_Logger.LogError("[SM] Warning encountered parsing languages.cfg file.");
dvander@591: g_Logger.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@591: if (sm_trie_retrieve(m_pLCodeLookup, langcode, NULL))
dvander@591: {
dvander@591: return false;
dvander@591: }
dvander@591:
dvander@591: Language *pLanguage = new Language;
dvander@591: unsigned int idx = m_Languages.size();
dvander@591:
dvander@591: pLanguage->m_code2[0] = langcode[0];
dvander@591: pLanguage->m_code2[1] = langcode[1];
dvander@591: pLanguage->m_code2[2] = langcode[2];
dvander@591: pLanguage->m_FullName = m_pStringTab->AddString(description);
dvander@591:
dvander@591: sm_trie_insert(m_pLCodeLookup, langcode, reinterpret_cast(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: {
faluco@1352: CPlayer *pPlayer = g_Players.GetPlayerByIndex(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@2042: return g_SourceMod.SetGlobalTarget(index);
dvander@2042: }
dvander@2042:
dvander@2042: int Translator::GetGlobalTarget() const
dvander@2042: {
dvander@2042: return g_SourceMod.GetGlobalTarget();
dvander@2042: }
dvander@2042:
dvander@2042: bool CoreTranslate(char *buffer,
dvander@2042: size_t maxlength,
dvander@2042: const char *format,
dvander@2042: unsigned int numparams,
dvander@2042: size_t *pOutLength,
dvander@2042: ...)
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@2042: g_Logger.LogError("[SM] Could not find core phrase: %s", fail_phrase);
dvander@2042: }
dvander@2042: else
dvander@2042: {
dvander@2042: g_Logger.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@2042: if (!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: }