From c7418e70bad6421e572331768c5175184b641ab7 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sat, 10 May 2008 09:23:55 +0000 Subject: [PATCH] added amb1666 - rehaul over the ml api so extensions can easily use translations --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%402132 --- core/ChatTriggers.cpp | 13 +- core/ConCmdManager.cpp | 3 +- core/MenuManager.cpp | 21 +- core/PhraseCollection.cpp | 131 ++++++++++ core/PhraseCollection.h | 65 +++++ core/PlayerManager.cpp | 2 +- core/Translator.cpp | 177 ++++++++----- core/Translator.h | 72 +++--- core/msvc8/sourcemod_mm.vcproj | 12 + core/sm_stringutil.cpp | 317 +++++++++++++++++++---- core/sm_stringutil.h | 19 +- core/smn_console.cpp | 4 +- core/smn_core.cpp | 8 +- core/smn_fakenatives.cpp | 2 +- core/smn_filesystem.cpp | 10 +- core/smn_lang.cpp | 23 +- core/smn_menus.cpp | 2 +- core/smn_player.cpp | 4 +- core/systems/PluginSys.cpp | 25 +- core/systems/PluginSys.h | 17 +- public/ISourceMod.h | 2 + public/ITranslator.h | 329 ++++++++++++++++++++++++ public/mms_sample_ext/sm_sdk_config.cpp | 10 +- public/mms_sample_ext/sm_sdk_config.h | 6 + public/sample_ext/sdk/smsdk_config.h | 1 + public/sample_ext/sdk/smsdk_ext.cpp | 6 + public/sample_ext/sdk/smsdk_ext.h | 6 + 27 files changed, 1060 insertions(+), 227 deletions(-) create mode 100644 core/PhraseCollection.cpp create mode 100644 core/PhraseCollection.h create mode 100644 public/ITranslator.h diff --git a/core/ChatTriggers.cpp b/core/ChatTriggers.cpp index 4e1ec018..d2491cc2 100644 --- a/core/ChatTriggers.cpp +++ b/core/ChatTriggers.cpp @@ -216,15 +216,14 @@ void ChatTriggers::OnSayCommand_Pre() { char buffer[128]; - /* :TODO: log an error? */ - if (g_Translator.CoreTransEx(g_pFloodPhrases, - client, - buffer, + if (!CoreTranslate( + buffer, sizeof(buffer), - "Flooding the server", + "%T", + 2, NULL, - NULL) - != Trans_Okay) + "Flooding the server", + &client)) { UTIL_Format(buffer, sizeof(buffer), "You are flooding the server!"); } diff --git a/core/ConCmdManager.cpp b/core/ConCmdManager.cpp index f177dd54..fac00fa2 100644 --- a/core/ConCmdManager.cpp +++ b/core/ConCmdManager.cpp @@ -494,8 +494,7 @@ bool ConCmdManager::CheckAccess(int client, const char *cmd, AdminCmdInfo *pAdmi /* If we got here, the command failed... */ char buffer[128]; - if (g_Translator.CoreTrans(client, buffer, sizeof(buffer), "No Access", NULL, NULL) - != Trans_Okay) + if (!CoreTranslate(buffer, sizeof(buffer), "%T", 1, NULL, "No Access", &client)) { UTIL_Format(buffer, sizeof(buffer), "You do not have access to this command"); } diff --git a/core/MenuManager.cpp b/core/MenuManager.cpp index 34b83202..9121518d 100644 --- a/core/MenuManager.cpp +++ b/core/MenuManager.cpp @@ -41,6 +41,7 @@ #include "ShareSys.h" #include "HandleSys.h" #include "sourcemm_api.h" +#include "Translator.h" MenuManager g_Menus; VoteMenuHandler s_VoteHandler; @@ -585,14 +586,20 @@ skip_search: { if (exitBackButton) { - CorePlayerTranslate(client, text, sizeof(text), "Back", NULL); + if (!CoreTranslate(text, sizeof(text), "%T", 2, NULL, "Back", &client)) + { + UTIL_Format(text, sizeof(text), "Back"); + } dr.style = ITEMDRAW_CONTROL; position = panel->DrawItem(dr); slots[position].type = ItemSel_ExitBack; } else { - CorePlayerTranslate(client, text, sizeof(text), "Previous", NULL); + if (!CoreTranslate(text, sizeof(text), "%T", 2, NULL, "Previous", &client)) + { + UTIL_Format(text, sizeof(text), "Previous"); + } dr.style = (displayPrev ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL; position = panel->DrawItem(dr); slots[position].type = ItemSel_Back; @@ -610,7 +617,10 @@ skip_search: /* NEXT */ if (displayNext || canDrawDisabled) { - CorePlayerTranslate(client, text, sizeof(text), "Next", NULL); + if (!CoreTranslate(text, sizeof(text), "%T", 2, NULL, "Next", &client)) + { + UTIL_Format(text, sizeof(text), "Next"); + } dr.style = (displayNext ? 0 : ITEMDRAW_DISABLED)|ITEMDRAW_CONTROL; position = panel->DrawItem(dr); slots[position].type = ItemSel_Next; @@ -638,7 +648,10 @@ skip_search: /* EXIT */ if (exitButton) { - CorePlayerTranslate(client, text, sizeof(text), "Exit", NULL); + if (!CoreTranslate(text, sizeof(text), "%T", 2, NULL, "Exit", &client)) + { + UTIL_Format(text, sizeof(text), "Exit"); + } dr.style = ITEMDRAW_CONTROL; position = panel->DrawItem(dr); slots[position].type = ItemSel_Exit; diff --git a/core/PhraseCollection.cpp b/core/PhraseCollection.cpp new file mode 100644 index 00000000..0af77da8 --- /dev/null +++ b/core/PhraseCollection.cpp @@ -0,0 +1,131 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include "PhraseCollection.h" +#include "Translator.h" +#include "sm_stringutil.h" + +CPhraseCollection::CPhraseCollection() +{ +} + +CPhraseCollection::~CPhraseCollection() +{ +} + +void CPhraseCollection::Destroy() +{ + delete this; +} + +IPhraseFile *CPhraseCollection::AddPhraseFile(const char *filename) +{ + size_t i; + unsigned int fid; + IPhraseFile *pFile; + char full_name[PLATFORM_MAX_PATH]; + + /* No compat shim here. The user should have read the doc. */ + UTIL_Format(full_name, sizeof(full_name), "%s.txt", filename); + + fid = g_Translator.FindOrAddPhraseFile(full_name); + pFile = g_Translator.GetFileByIndex(fid); + + for (i = 0; i < m_Files.size(); i++) + { + if (m_Files[i] == pFile) + { + return pFile; + } + } + + m_Files.push_back(pFile); + + return pFile; +} + +unsigned int CPhraseCollection::GetFileCount() +{ + return (unsigned int)m_Files.size(); +} + +IPhraseFile *CPhraseCollection::GetFile(unsigned int file) +{ + if (file >= m_Files.size()) + { + return NULL; + } + + return m_Files[file]; +} + +TransError CPhraseCollection::FindTranslation(const char *key, unsigned int langid, Translation *pTrans) +{ + size_t i; + + for (i = 0; i < m_Files.size(); i++) + { + if (m_Files[i]->GetTranslation(key, langid, pTrans) == Trans_Okay) + { + return Trans_Okay; + } + } + + return Trans_BadPhrase; +} + +bool CPhraseCollection::FormatString(char *buffer, + size_t maxlength, + const char *format, + void **params, + unsigned int numparams, + size_t *pOutLength, + const char **pFailPhrase) +{ + unsigned int arg; + + arg = 0; + if (!gnprintf(buffer, maxlength, format, this, params, numparams, arg, pOutLength, pFailPhrase)) + { + return false; + } + + if (arg != numparams) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; + } + + return true; +} diff --git a/core/PhraseCollection.h b/core/PhraseCollection.h new file mode 100644 index 00000000..a43cf602 --- /dev/null +++ b/core/PhraseCollection.h @@ -0,0 +1,65 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_PHRASECOLLECTION_H_ +#define _INCLUDE_SOURCEMOD_PHRASECOLLECTION_H_ + +#include +#include +#include + +using namespace SourceHook; +using namespace SourceMod; + +class CPhraseCollection : public IPhraseCollection +{ +public: + CPhraseCollection(); + ~CPhraseCollection(); +public: + IPhraseFile *AddPhraseFile(const char *filename); + unsigned int GetFileCount(); + IPhraseFile *GetFile(unsigned int file); + void Destroy(); + TransError FindTranslation(const char *key, unsigned int langid, Translation *pTrans); + bool FormatString( + char *buffer, + size_t maxlength, + const char *format, + void **params, + unsigned int numparams, + size_t *pOutLength, + const char **pFailPhrase); +private: + CVector m_Files; +}; + +#endif //_INCLUDE_SOURCEMOD_PHRASECOLLECTION_H_ diff --git a/core/PlayerManager.cpp b/core/PlayerManager.cpp index 9e0ecc58..10e30b78 100644 --- a/core/PlayerManager.cpp +++ b/core/PlayerManager.cpp @@ -1203,7 +1203,7 @@ CPlayer::CPlayer() m_bAdminCheckSignalled = false; m_bIsInKickQueue = false; m_LastPassword.clear(); - m_LangId = LANGUAGE_ENGLISH; + m_LangId = SOURCEMOD_LANGUAGE_ENGLISH; } void CPlayer::Initialize(const char *name, const char *ip, edict_t *pEntity) diff --git a/core/Translator.cpp b/core/Translator.cpp index 917cf372..f9f4b860 100644 --- a/core/Translator.cpp +++ b/core/Translator.cpp @@ -38,9 +38,11 @@ #include "sm_stringutil.h" #include "sourcemod.h" #include "PlayerManager.h" +#include "PhraseCollection.h" +#include "ShareSys.h" Translator g_Translator; -CPhraseFile *g_pCorePhrases = NULL; +IPhraseCollection *g_pCorePhrases = NULL; unsigned int g_pCorePhraseID = 0; struct trans_t @@ -663,7 +665,7 @@ const char *CPhraseFile::GetFilename() ** MAIN TRANSLATOR CODE ** **************************/ -Translator::Translator() : m_ServerLang(LANGUAGE_ENGLISH) +Translator::Translator() : m_ServerLang(SOURCEMOD_LANGUAGE_ENGLISH) { m_pStringTab = new BaseStringTable(2048); m_pLCodeLookup = sm_trie_create(); @@ -727,11 +729,15 @@ void Translator::OnSourceModAllInitialized() { AddLanguage("en", "English"); - unsigned int id; + g_pCorePhrases = CreatePhraseCollection(); + g_pCorePhrases->AddPhraseFile("core.phrases"); - id = FindOrAddPhraseFile("core.phrases.txt"); - g_pCorePhraseID = id; - g_pCorePhrases = GetFileByIndex(id); + g_ShareSys.AddInterface(NULL, this); +} + +void Translator::OnSourceModShutdown() +{ + g_pCorePhrases->Destroy(); } bool Translator::GetLanguageByCode(const char *code, unsigned int *index) @@ -842,7 +848,7 @@ void Translator::RebuildLanguageDatabase(const char *lang_header_file) g_Logger.LogError("Server language was set to bad language \"%s\" -- reverting to English", m_InitialLang); strncopy(m_InitialLang, "en", sizeof(m_InitialLang)); - m_ServerLang = LANGUAGE_ENGLISH; + m_ServerLang = SOURCEMOD_LANGUAGE_ENGLISH; } m_ServerLang = reinterpret_cast(serverLang); @@ -936,61 +942,6 @@ CPhraseFile *Translator::GetFileByIndex(unsigned int index) return m_Files[index]; } -size_t Translator::Translate(char *buffer, size_t maxlength, void **params, const Translation *pTrans) -{ - void *new_params[MAX_TRANSLATE_PARAMS]; - - /* Rewrite the parameter order */ - for (unsigned int i=0; ifmt_count; i++) - { - new_params[i] = params[pTrans->fmt_order[i]]; - } - - return gnprintf(buffer, maxlength, pTrans->szPhrase, new_params); -} - -TransError Translator::CoreTransEx(CPhraseFile *pFile, - int client, - char *buffer, - size_t maxlength, - const char *phrase, - void **params, - size_t *outlen) -{ - Translation trans; - TransError err; - - /* Using server lang temporarily until client lang stuff is implemented */ - if ((err = pFile->GetTranslation(phrase, m_ServerLang, &trans)) != Trans_Okay) - { - return err; - } - - size_t len = Translate(buffer, maxlength, params, &trans); - - if (outlen) - { - *outlen = len; - } - - return Trans_Okay; -} - -TransError Translator::CoreTrans(int client, - char *buffer, - size_t maxlength, - const char *phrase, - void **params, - size_t *outlen) -{ - if (!g_pCorePhrases) - { - return Trans_BadPhraseFile; - } - - return CoreTransEx(g_pCorePhrases, client, buffer, maxlength, phrase, params, outlen); -} - unsigned int Translator::GetServerLanguage() { return m_ServerLang; @@ -1021,3 +972,105 @@ bool Translator::GetLanguageInfo(unsigned int number, const char **code, const c return true; } + +const char *Translator::GetInterfaceName() +{ + return SMINTERFACE_TRANSLATOR_NAME; +} + +unsigned int Translator::GetInterfaceVersion() +{ + return SMINTERFACE_TRANSLATOR_VERSION; +} + +IPhraseCollection *Translator::CreatePhraseCollection() +{ + return new CPhraseCollection(); +} + +int Translator::SetGlobalTarget(int index) +{ + return g_SourceMod.SetGlobalTarget(index); +} + +int Translator::GetGlobalTarget() const +{ + return g_SourceMod.GetGlobalTarget(); +} + +bool CoreTranslate(char *buffer, + size_t maxlength, + const char *format, + unsigned int numparams, + size_t *pOutLength, + ...) +{ + va_list ap; + unsigned int i; + const char *fail_phrase; + void *params[MAX_TRANSLATE_PARAMS]; + + if (numparams > MAX_TRANSLATE_PARAMS) + { + assert(false); + return false; + } + + va_start(ap, pOutLength); + for (i = 0; i < numparams; i++) + { + params[i] = va_arg(ap, void *); + } + va_end(ap); + + if (!g_pCorePhrases->FormatString(buffer, + maxlength, + format, + params, + numparams, + pOutLength, + &fail_phrase)) + { + if (fail_phrase != NULL) + { + g_Logger.LogError("[SM] Could not find core phrase: %s", fail_phrase); + } + else + { + g_Logger.LogError("[SM] Unknown fatal error while translating a core phrase."); + } + + return false; + } + + return true; +} + +bool Translator::FormatString(char *buffer, + size_t maxlength, + const char *format, + IPhraseCollection *pPhrases, + void **params, + unsigned int numparams, + size_t *pOutLength, + const char **pFailPhrase) +{ + unsigned int arg; + + arg = 0; + if (!gnprintf(buffer, maxlength, format, pPhrases, params, numparams, arg, pOutLength, pFailPhrase)) + { + return false; + } + + if (arg != numparams) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; + } + + return true; +} diff --git a/core/Translator.h b/core/Translator.h index c878e3e7..c003cf6a 100644 --- a/core/Translator.h +++ b/core/Translator.h @@ -38,6 +38,7 @@ #include "sm_globals.h" #include "sm_memtable.h" #include "ITextParsers.h" +#include #define MAX_TRANSLATE_PARAMS 32 #define CORELANG_ENGLISH 0 @@ -60,25 +61,9 @@ struct Language int m_FullName; }; -struct Translation -{ - const char *szPhrase; /**< Translated phrase. */ - unsigned int fmt_count; /**< Number of format parameters. */ - int *fmt_order; /**< Format phrase order. */ -}; - -#define LANGUAGE_ENGLISH 0 - -enum TransError -{ - Trans_Okay = 0, - Trans_BadLanguage = 1, - Trans_BadPhrase = 2, - Trans_BadPhraseLanguage = 3, - Trans_BadPhraseFile = 4, -}; - -class CPhraseFile : public ITextListener_SMC +class CPhraseFile : + public ITextListener_SMC, + public IPhraseFile { public: CPhraseFile(Translator *pTranslator, const char *file); @@ -112,7 +97,8 @@ private: class Translator : public ITextListener_SMC, - public SMGlobalClass + public SMGlobalClass, + public ITranslator { public: Translator(); @@ -125,6 +111,7 @@ public: // SMGlobalClass size_t maxlength); void OnSourceModAllInitialized(); void OnSourceModLevelChange(const char *mapName); + void OnSourceModShutdown(); public: // ITextListener_SMC void ReadSMC_ParseStart(); SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name); @@ -138,23 +125,30 @@ public: bool GetLanguageInfo(unsigned int number, const char **code, const char **name); bool GetLanguageByCode(const char *code, unsigned int *index); bool GetLanguageByName(const char *name, unsigned int *index); - size_t Translate(char *buffer, size_t maxlength, void **params, const Translation *pTrans); CPhraseFile *GetFileByIndex(unsigned int index); - TransError CoreTransEx(CPhraseFile *pFile, - int client, - char *buffer, - size_t maxlength, - const char *phrase, - void **params, - size_t *outlen=NULL); - TransError CoreTrans(int client, - char *buffer, - size_t maxlength, - const char *phrase, - void **params, - size_t *outlen=NULL); +public: //ITranslator unsigned int GetServerLanguage(); unsigned int GetClientLanguage(int client); + const char *GetInterfaceName(); + unsigned int GetInterfaceVersion(); + IPhraseCollection *CreatePhraseCollection(); + int SetGlobalTarget(int index); + int GetGlobalTarget() const; + size_t FormatString( + char *buffer, + size_t maxlength, + SourcePawn::IPluginContext *pContext, + const cell_t *params, + unsigned int param); + bool FormatString( + char *buffer, + size_t maxlength, + const char *format, + IPhraseCollection *pPhrases, + void **params, + unsigned int numparams, + size_t *pOutLength, + const char **pFailPhrase); private: bool AddLanguage(const char *langcode, const char *description); private: @@ -168,7 +162,15 @@ private: char m_InitialLang[3]; }; -extern CPhraseFile *g_pCorePhrases; +/* Nice little wrapper to handle error logging and whatnot */ +bool CoreTranslate(char *buffer, + size_t maxlength, + const char *format, + unsigned int numparams, + size_t *pOutLength, + ...); + +extern IPhraseCollection *g_pCorePhrases; extern unsigned int g_pCorePhraseID; extern Translator g_Translator; diff --git a/core/msvc8/sourcemod_mm.vcproj b/core/msvc8/sourcemod_mm.vcproj index a6921478..f61857de 100644 --- a/core/msvc8/sourcemod_mm.vcproj +++ b/core/msvc8/sourcemod_mm.vcproj @@ -901,6 +901,10 @@ RelativePath="..\MenuVoting.cpp" > + + @@ -1063,6 +1067,10 @@ RelativePath="..\MenuVoting.h" > + + @@ -1232,6 +1240,10 @@ RelativePath="..\..\public\ITimerSystem.h" > + + diff --git a/core/sm_stringutil.cpp b/core/sm_stringutil.cpp index f12bba8b..020a87eb 100644 --- a/core/sm_stringutil.cpp +++ b/core/sm_stringutil.cpp @@ -50,43 +50,6 @@ return 0; \ } -size_t CorePlayerTranslate(int client, char *buffer, size_t maxlength, const char *phrase, void **params) -{ - Translation pTrans; - TransError err; - - err = g_pCorePhrases->GetTranslation(phrase, g_Translator.GetClientLanguage(client), &pTrans); - if (err != Trans_Okay) - { - err = g_pCorePhrases->GetTranslation(phrase, g_Translator.GetServerLanguage(), &pTrans); - if (err != Trans_Okay && g_Translator.GetServerLanguage() != CORELANG_ENGLISH) - { - err = g_pCorePhrases->GetTranslation(phrase, CORELANG_ENGLISH, &pTrans); - } - } - - if (err != Trans_Okay) - { - return UTIL_Format(buffer, maxlength, "%s", phrase); - } - - return g_Translator.Translate(buffer, maxlength, params, &pTrans); -} - -inline bool TryTranslation(CPlugin *pl, const char *key, unsigned int langid, unsigned int langcount, Translation *pTrans) -{ - TransError err = Trans_BadLanguage; - CPhraseFile *phrfl; - - for (size_t i=0; iGetLangFileByIndex(i)); - err = phrfl->GetTranslation(key, langid, pTrans); - } - - return (err == Trans_Okay) ? true : false; -} - inline void ReorderTranslationParams(const Translation *pTrans, cell_t *params) { cell_t new_params[MAX_TRANSLATE_PARAMS]; @@ -110,11 +73,13 @@ size_t Translate(char *buffer, *error = false; Translation pTrans; CPlugin *pl = (CPlugin *)g_PluginSys.FindPluginByContext(pCtx->GetContext()); - size_t langcount = pl->GetLangFileCount(); unsigned int max_params = 0; + IPhraseCollection *pPhrases; + + pPhrases = pl->GetPhrases(); try_serverlang: - if (target == LANG_SERVER) + if (target == SOURCEMOD_SERVER_LANGUAGE) { langid = g_Translator.GetServerLanguage(); } @@ -128,16 +93,16 @@ try_serverlang: goto error_out; } - if (!TryTranslation(pl, key, langid, langcount, &pTrans)) + if (pPhrases->FindTranslation(key, langid, &pTrans) != Trans_Okay) { - if (target != LANG_SERVER && langid != g_Translator.GetServerLanguage()) + if (target != SOURCEMOD_SERVER_LANGUAGE && langid != g_Translator.GetServerLanguage()) { - target = LANG_SERVER; + target = SOURCEMOD_SERVER_LANGUAGE; goto try_serverlang; } - else if (langid != LANGUAGE_ENGLISH) + else if (langid != SOURCEMOD_LANGUAGE_ENGLISH) { - if (!TryTranslation(pl, key, LANGUAGE_ENGLISH, langcount, &pTrans)) + if (!pPhrases->FindTranslation(key, SOURCEMOD_LANGUAGE_ENGLISH, &pTrans)) { pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Language phrase \"%s\" not found", key); goto error_out; @@ -575,11 +540,32 @@ void AddHex(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags *buf_p = buf; } -size_t gnprintf(char *buffer, size_t maxlen, const char *format, void **args) +bool gnprintf(char *buffer, + size_t maxlen, + const char *format, + IPhraseCollection *pPhrases, + void **params, + unsigned int numparams, + unsigned int &curparam, + size_t *pOutLength, + const char **pFailPhrase) { if (!buffer || !maxlen) { - return 0; + if (pOutLength != NULL) + { + *pOutLength = 0; + } + return true; + } + + if (numparams > MAX_TRANSLATE_PARAMS) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; } int arg = 0; @@ -668,7 +654,16 @@ reswitch: { goto done; } - char *c = (char *)args[arg]; + if (curparam >= numparams) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; + } + char *c = (char *)params[curparam]; + curparam++; *buf_p++ = *c; llen--; arg++; @@ -676,7 +671,16 @@ reswitch: } case 'b': { - int *value = (int *)args[arg]; + if (curparam >= numparams) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; + } + int *value = (int *)params[curparam]; + curparam++; AddBinary(&buf_p, llen, *value, width, flags); arg++; break; @@ -684,35 +688,224 @@ reswitch: case 'd': case 'i': { - int *value = (int *)args[arg]; + if (curparam >= numparams) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; + } + int *value = (int *)params[curparam]; + curparam++; AddInt(&buf_p, llen, *value, width, flags); arg++; break; } case 'u': { - unsigned int *value = (unsigned int *)args[arg]; + if (curparam >= numparams) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; + } + unsigned int *value = (unsigned int *)params[curparam]; + curparam++; AddUInt(&buf_p, llen, *value, width, flags); arg++; break; } case 'f': { - float *value = (float *)args[arg]; + if (curparam >= numparams) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; + } + float *value = (float *)params[curparam]; + curparam++; AddFloat(&buf_p, llen, *value, width, prec, flags); arg++; break; } case 's': { - const char *str = (const char *)args[arg]; + if (curparam >= numparams) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; + } + const char *str = (const char *)params[curparam]; + curparam++; AddString(&buf_p, llen, str, width, prec); arg++; + break; + } + case 'T': + case 't': + { + int target; + const char *key; + size_t out_length; + Translation trans; + unsigned int lang_id; + + if (curparam >= numparams) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; + } + key = (const char *)(params[curparam]); + curparam++; + + if (ch == 'T') + { + if (curparam >= numparams) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; + } + target = *((int *)(params[curparam])); + curparam++; + } + else + { + target = g_Translator.GetGlobalTarget(); + } + +try_again: + if (target == SOURCEMOD_SERVER_LANGUAGE) + { + lang_id = g_Translator.GetServerLanguage(); + } + else if (target >= 1 && target <= g_Players.GetMaxClients()) + { + lang_id = g_Translator.GetClientLanguage(target); + } + else + { + lang_id = g_Translator.GetServerLanguage(); + } + + if (pPhrases == NULL) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = key; + } + return false; + } + + if (pPhrases->FindTranslation(key, lang_id, &trans) != Trans_Okay) + { + if (target != SOURCEMOD_SERVER_LANGUAGE && lang_id != g_Translator.GetServerLanguage()) + { + target = SOURCEMOD_SERVER_LANGUAGE; + goto try_again; + } + else if (lang_id != SOURCEMOD_LANGUAGE_ENGLISH) + { + if (pPhrases->FindTranslation(key, SOURCEMOD_LANGUAGE_ENGLISH, &trans) != Trans_Okay) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = key; + } + return false; + } + } + else + { + if (pFailPhrase != NULL) + { + *pFailPhrase = key; + } + return false; + } + } + + if (trans.fmt_count) + { + unsigned int i; + void *new_params[MAX_TRANSLATE_PARAMS]; + + if (curparam + trans.fmt_count > numparams) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; + } + + /* Copy the array and re-order the stack */ + memcpy(new_params, params, sizeof(void *) * numparams); + for (i = 0; i < trans.fmt_count; i++) + { + new_params[i] = const_cast(params[curparam + trans.fmt_order[i]]); + } + + if (!gnprintf(buf_p, + llen, + trans.szPhrase, + pPhrases, + new_params, + numparams, + curparam, + &out_length, + pFailPhrase)) + { + return false; + } + } + else + { + if (!gnprintf(buf_p, + llen, + trans.szPhrase, + pPhrases, + params, + numparams, + curparam, + &out_length, + pFailPhrase)) + { + return false; + } + } + + buf_p += out_length; + llen -= out_length; + break; } case 'X': { - unsigned int *value = (unsigned int *)args[arg]; + if (curparam >= numparams) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; + } + unsigned int *value = (unsigned int *)params[curparam]; + curparam++; flags |= UPPERDIGITS; AddHex(&buf_p, llen, *value, width, flags); arg++; @@ -720,7 +913,16 @@ reswitch: } case 'x': { - unsigned int *value = (unsigned int *)args[arg]; + if (curparam >= numparams) + { + if (pFailPhrase != NULL) + { + *pFailPhrase = NULL; + } + return false; + } + unsigned int *value = (unsigned int *)params[curparam]; + curparam++; AddHex(&buf_p, llen, *value, width, flags); arg++; break; @@ -761,7 +963,12 @@ reswitch: done: *buf_p = '\0'; - return (maxlen - llen - 1); + if (pOutLength != NULL) + { + *pOutLength = (maxlen - llen - 1); + } + + return true; } size_t atcprintf(char *buffer, size_t maxlen, const char *format, IPluginContext *pCtx, const cell_t *params, int *param) diff --git a/core/sm_stringutil.h b/core/sm_stringutil.h index 12868046..8c3a7f24 100644 --- a/core/sm_stringutil.h +++ b/core/sm_stringutil.h @@ -33,23 +33,30 @@ #define _INCLUDE_SOURCEMOD_STRINGUTIL_H_ #include -#include "sp_vm_api.h" -#include "sp_typeutil.h" +#include +#include +#include using namespace SourcePawn; - -#define LANG_SERVER 0 +using namespace SourceMod; #define IS_STR_FILLED(var) (var[0] != '\0') size_t atcprintf(char *buffer, size_t maxlen, const char *format, IPluginContext *pCtx, const cell_t *params, int *param); const char *stristr(const char *str, const char *substr); unsigned int strncopy(char *dest, const char *src, size_t count); -size_t gnprintf(char *buffer, size_t maxlen, const char *format, void **args); +bool gnprintf(char *buffer, + size_t maxlen, + const char *format, + IPhraseCollection *pPhrases, + void **params, + unsigned int numparams, + unsigned int &curparam, + size_t *pOutLength, + const char **pFailPhrase); size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...); size_t UTIL_FormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list ap); char *sm_strdup(const char *str); -size_t CorePlayerTranslate(int client, char *buffer, size_t maxlength, const char *phrase, void **params); unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace); char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t searchLen, const char *replace, size_t replaceLen); char *UTIL_TrimWhitespace(char *str, size_t &len); diff --git a/core/smn_console.cpp b/core/smn_console.cpp index 8f2a4eb7..c635a339 100644 --- a/core/smn_console.cpp +++ b/core/smn_console.cpp @@ -828,7 +828,7 @@ static cell_t sm_PrintToConsole(IPluginContext *pCtx, const cell_t *params) static cell_t sm_ServerCommand(IPluginContext *pContext, const cell_t *params) { - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[1024]; size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 1); @@ -849,7 +849,7 @@ static cell_t sm_ServerCommand(IPluginContext *pContext, const cell_t *params) static cell_t sm_InsertServerCommand(IPluginContext *pContext, const cell_t *params) { - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[1024]; size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 1); diff --git a/core/smn_core.cpp b/core/smn_core.cpp index 93feb99d..78d18ebd 100644 --- a/core/smn_core.cpp +++ b/core/smn_core.cpp @@ -126,7 +126,7 @@ static cell_t ThrowError(IPluginContext *pContext, const cell_t *params) { char buffer[512]; - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 1); @@ -502,7 +502,7 @@ static cell_t LibraryExists(IPluginContext *pContext, const cell_t *params) static cell_t sm_LogAction(IPluginContext *pContext, const cell_t *params) { char buffer[2048]; - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 3); if (pContext->GetContext()->n_err != SP_ERROR_NONE) @@ -532,7 +532,7 @@ static cell_t LogToFile(IPluginContext *pContext, const cell_t *params) } char buffer[2048]; - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); if (pContext->GetContext()->n_err != SP_ERROR_NONE) @@ -565,7 +565,7 @@ static cell_t LogToFileEx(IPluginContext *pContext, const cell_t *params) } char buffer[2048]; - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); if (pContext->GetContext()->n_err != SP_ERROR_NONE) diff --git a/core/smn_fakenatives.cpp b/core/smn_fakenatives.cpp index 35697216..76f865f3 100644 --- a/core/smn_fakenatives.cpp +++ b/core/smn_fakenatives.cpp @@ -136,7 +136,7 @@ static cell_t ThrowNativeError(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeError("Not called from inside a native function"); } - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[512]; diff --git a/core/smn_filesystem.cpp b/core/smn_filesystem.cpp index 8c19e4af..9870d6a6 100644 --- a/core/smn_filesystem.cpp +++ b/core/smn_filesystem.cpp @@ -568,7 +568,7 @@ static cell_t sm_BuildPath(IPluginContext *pContext, const cell_t *params) static cell_t sm_LogToGame(IPluginContext *pContext, const cell_t *params) { - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[1024]; size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 1); @@ -594,7 +594,7 @@ static cell_t sm_LogToGame(IPluginContext *pContext, const cell_t *params) static cell_t sm_LogMessage(IPluginContext *pContext, const cell_t *params) { - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[1024]; g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 1); @@ -612,7 +612,7 @@ static cell_t sm_LogMessage(IPluginContext *pContext, const cell_t *params) static cell_t sm_LogError(IPluginContext *pContext, const cell_t *params) { - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[1024]; g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 1); @@ -667,7 +667,7 @@ static cell_t sm_LogToOpenFile(IPluginContext *pContext, const cell_t *params) } char buffer[2048]; - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); if (pContext->GetContext()->n_err != SP_ERROR_NONE) @@ -698,7 +698,7 @@ static cell_t sm_LogToOpenFileEx(IPluginContext *pContext, const cell_t *params) } char buffer[2048]; - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); if (pContext->GetContext()->n_err != SP_ERROR_NONE) diff --git a/core/smn_lang.cpp b/core/smn_lang.cpp index 56e6b6fa..dff43339 100644 --- a/core/smn_lang.cpp +++ b/core/smn_lang.cpp @@ -37,24 +37,25 @@ static cell_t sm_LoadTranslations(IPluginContext *pCtx, const cell_t *params) { - char *filename; - unsigned int index; + char *filename, *ext; + char buffer[PLATFORM_MAX_PATH]; CPlugin *pl = (CPlugin *)g_PluginSys.FindPluginByContext(pCtx->GetContext()); pCtx->LocalToString(params[1], &filename); + UTIL_Format(buffer, sizeof(buffer), "%s", filename); - /* Check if there is no extension */ - const char *ext = g_LibSys.GetFileExtension(filename); - if (!ext || (strcmp(ext, "cfg") && strcmp(ext, "txt"))) + /* Make sure there is no extension */ + if ((ext = strstr(buffer, ".txt")) != NULL + || (ext = strstr(buffer, ".cfg")) != NULL) { - /* Append one */ - static char new_file[PLATFORM_MAX_PATH]; - UTIL_Format(new_file, sizeof(new_file), "%s.txt", filename); - filename = new_file; + /* Simple heuristic -- just see if it's at the end and terminate if so */ + if (ext - buffer == strlen(buffer) - 4) + { + *ext = '\0'; + } } - index = g_Translator.FindOrAddPhraseFile(filename); - pl->AddLangFile(index); + pl->GetPhrases()->AddPhraseFile(buffer); return 1; } diff --git a/core/smn_menus.cpp b/core/smn_menus.cpp index 72628c07..efaaaf63 100644 --- a/core/smn_menus.cpp +++ b/core/smn_menus.cpp @@ -838,7 +838,7 @@ static cell_t SetMenuTitle(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeError("Menu handle %x is invalid (error %d)", hndl, err); } - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); char buffer[1024]; g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2); diff --git a/core/smn_player.cpp b/core/smn_player.cpp index 688f87ed..979e8a86 100644 --- a/core/smn_player.cpp +++ b/core/smn_player.cpp @@ -928,7 +928,7 @@ static cell_t _ShowActivity(IPluginContext *pContext, } else { - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); if (pContext->GetContext()->n_err != SP_ERROR_NONE) @@ -1065,7 +1065,7 @@ static cell_t _ShowActivity2(IPluginContext *pContext, } else { - g_SourceMod.SetGlobalTarget(LANG_SERVER); + g_SourceMod.SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE); g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); if (pContext->GetContext()->n_err != SP_ERROR_NONE) diff --git a/core/systems/PluginSys.cpp b/core/systems/PluginSys.cpp index 7b2dfcab..30b3bf6f 100644 --- a/core/systems/PluginSys.cpp +++ b/core/systems/PluginSys.cpp @@ -67,6 +67,7 @@ CPlugin::CPlugin(const char *file) m_FakeNativesMissing = false; m_LibraryMissing = false; m_bGotAllLoaded = false; + m_pPhrases = g_Translator.CreatePhraseCollection(); } CPlugin::~CPlugin() @@ -111,6 +112,11 @@ CPlugin::~CPlugin() delete m_configs[i]; } m_configs.clear(); + if (m_pPhrases != NULL) + { + m_pPhrases->Destroy(); + m_pPhrases = NULL; + } } void CPlugin::InitIdentity() @@ -128,7 +134,6 @@ unsigned int CPlugin::CalcMemUsage() unsigned int base_size = sizeof(CPlugin) + sizeof(IdentityToken_t) - + (m_PhraseFiles.size() * sizeof(unsigned int)) + (m_dependents.size() * sizeof(CPlugin *)) + (m_dependsOn.size() * sizeof(CPlugin *)) + (m_fakeNatives.size() * (sizeof(FakeNative *) + sizeof(FakeNative))) @@ -710,19 +715,9 @@ void CPlugin::SetTimeStamp(time_t t) m_LastAccess = t; } -void CPlugin::AddLangFile(unsigned int index) +IPhraseCollection *CPlugin::GetPhrases() { - m_PhraseFiles.push_back(index); -} - -size_t CPlugin::GetLangFileCount() -{ - return m_PhraseFiles.size(); -} - -unsigned int CPlugin::GetLangFileByIndex(unsigned int index) -{ - return m_PhraseFiles.at(index); + return m_pPhrases; } void CPlugin::DependencyDropped(CPlugin *pOwner) @@ -1488,8 +1483,8 @@ bool CPluginManager::RunSecondPass(CPlugin *pPlugin, char *error, size_t maxleng OnLibraryAction((*s_iter).c_str(), true, false); } - /* Finally, add the core language file */ - pPlugin->AddLangFile(g_pCorePhraseID); + /* :TODO: optimize? does this even matter? */ + pPlugin->GetPhrases()->AddPhraseFile("core.phrases"); return true; } diff --git a/core/systems/PluginSys.h b/core/systems/PluginSys.h index d7dc8e65..a1aee68a 100644 --- a/core/systems/PluginSys.h +++ b/core/systems/PluginSys.h @@ -53,6 +53,7 @@ #else #include "convar_sm.h" #endif +#include "ITranslator.h" using namespace SourceHook; @@ -244,19 +245,9 @@ public: bool IsRunnable(); /** - * Adds a language file index to the plugin's list. + * Get languages info. */ - void AddLangFile(unsigned int index); - - /** - * Get language file count for this plugin. - */ - size_t GetLangFileCount(); - - /** - * Get language file index based on the vector index. - */ - unsigned int GetLangFileByIndex(unsigned int index); + IPhraseCollection *GetPhrases(); public: /** * Returns the modification time during last plugin load. @@ -300,7 +291,7 @@ private: IdentityToken_t *m_ident; Handle_t m_handle; bool m_WasRunning; - CVector m_PhraseFiles; + IPhraseCollection *m_pPhrases; List m_dependents; List m_dependsOn; List m_fakeNatives; diff --git a/public/ISourceMod.h b/public/ISourceMod.h index 3fb97263..80fa1d05 100644 --- a/public/ISourceMod.h +++ b/public/ISourceMod.h @@ -221,6 +221,7 @@ namespace SourceMod * translations (that is, %t). * * @param index Client index. + * @deprecated Use ITranslator::GetGlobalTarget() instead. * @return Old global client value. */ virtual unsigned int SetGlobalTarget(unsigned int index) =0; @@ -229,6 +230,7 @@ namespace SourceMod * @brief Returns the global client SourceMod is currently using * for assisted translations (that is, %t). * + * @deprecated Use ITranslator::GetGlobalTarget() instead. * @return Global client value. */ virtual unsigned int GetGlobalTarget() const =0; diff --git a/public/ITranslator.h b/public/ITranslator.h new file mode 100644 index 00000000..cdd0e8f9 --- /dev/null +++ b/public/ITranslator.h @@ -0,0 +1,329 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_TRANSLATOR_INTERFACE_H_ +#define _INCLUDE_SOURCEMOD_TRANSLATOR_INTERFACE_H_ + +#include + +#define SMINTERFACE_TRANSLATOR_NAME "ITranslator" +#define SMINTERFACE_TRANSLATOR_VERSION 1 + +/** + * @file ITranslator.h + * @brief Defines interfaces related to translation files. + */ + +namespace SourceMod +{ + /** + * @brief SourceMod hardcodes the English language (default) to ID 0. + * This cannot be changed and languages.cfg should never have it as anything + * other than the first index. + */ + #define SOURCEMOD_LANGUAGE_ENGLISH 0 + + /** + * @brief For %T formats, specifies that the language should be that of the + * server and not a specific client. + */ + #define SOURCEMOD_SERVER_LANGUAGE 0 + + /** + * @brief Translation error codes. + */ + enum TransError + { + Trans_Okay = 0, /**< Translation succeeded. */ + Trans_BadLanguage = 1, /**< Bad language ID. */ + Trans_BadPhrase = 2, /**< Phrase not found. */ + Trans_BadPhraseLanguage = 3, /**< Phrase not found in the given language. */ + Trans_BadPhraseFile = 4, /**< Phrase file was unreadable. */ + }; + + /** + * @brief Contains information about a translation phrase. + */ + struct Translation + { + const char *szPhrase; /**< Translated phrase. */ + unsigned int fmt_count; /**< Number of format parameters. */ + int *fmt_order; /**< Array of size fmt_count where each + element is the numerical order of + parameter insertion, starting from + 0. + */ + }; + + /** + * @brief Represents a phrase file from SourceMod's "translations" folder. + */ + class IPhraseFile + { + public: + /** + * @brief Attempts to find a translation phrase in a phrase file. + * + * @param szPhrase String containing the phrase name. + * @param lang_id Language ID. + * @param pTrans Buffer to store translation info. + * @return Translation error code indicating success + * (pTrans is filled) or failure (pTrans + * contents is undefined). + */ + virtual TransError GetTranslation( + const char *szPhrase, + unsigned int lang_id, + Translation *pTrans) =0; + + /** + * @brief Returns the file name of this translation file. + * + * @return File name. + */ + virtual const char *GetFilename() =0; + }; + + /** + * Represents a collection of phrase files. + */ + class IPhraseCollection + { + public: + /** + * @brief Adds a phrase file to the collection, using a cached one + * if already found. The return value is provided for informational + * purposes and does not need to be saved. The life time of the + * return pointer is equal to the life time of the collection. + * + * This function will internally ignore dupliate additions but still + * return a valid pointer. + * + * @param filename File name, without the ".txt" extension, of + * the phrase file in the translations folder. + * @return An IPhraseFile pointer, even if the file does + * not exist. + */ + virtual IPhraseFile *AddPhraseFile(const char *filename) =0; + + /** + * @brief Returns the number of contained phrase files. + * + * @return Number of contained phrase files. + */ + virtual unsigned int GetFileCount() =0; + + /** + * @brief Returns the pointer to a contained phrase file. + * + * @param file File index, from 0 to GetFileCount()-1. + * @return IPhraseFile pointer, or NULL if out of + * range. + */ + virtual IPhraseFile *GetFile(unsigned int file) =0; + + /** + * @brief Destroys the phrase collection, freeing all internal + * resources and invalidating the object. + */ + virtual void Destroy() =0; + + /** + * @brief Attempts a translation across a given language. All + * contained files are searched for an appropriate match; the + * first valid match is returned. + * + * @param key String containing the phrase name. + * @param langid Language ID to translate to. + * @param pTrans Translation buffer. + * @return Translation error code; on success, + * pTrans is valid. On failure, the + * contents of pTrans is undefined. + */ + virtual TransError FindTranslation( + const char *key, + unsigned int langid, + Translation *pTrans) =0; + + /** + * @brief Formats a phrase given a parameter stack. The parameter + * stack size must exactly match the expected parameter count. If + * this count is too small or too large, the format fails. + * + * @param buffer Buffer to store formatted text. + * @param maxlength Maximum length of the buffer. + * @param format String containing format information. + * This is equivalent to SourceMod's Format() + * native, and sub-translations are acceptable. + * @param params An array of pointers to each parameter. + * Integer parameters must have a pointer to the integer. + * Float parameters must have a pointer to a float. + * String parameters must be a string pointer. + * Char parameters must be a pointer to a char. + * Translation parameters fill multiple indexes in the + * array. For %T translations, the expected stack is: + * [phrase string pointer] [int target id pointer] [...] + * Where [...] is the required parameters for the translation, + * in the order expected by the phrase, not the phrase's + * translation. For example, say the format is: + * "%d %T" and the phrase's format is {1:s,2:f}, then the + * parameter stack should be: + * int *, const char *, int *, const char *, float * + * The %t modifier is the same except the target id pointer + * would be removed: + * int *, const char *, const char *, float * + * @param numparams Number of parameters in the params array. + * @param pOutLength Optional pointer filled with output length on success. + * @param pFailPhrase Optional pointer; on failure, is filled with NULL if the + * failure was not due to a failed translation phrase. + * Otherwise, it is filled with the given phrase name pointer + * from the parameter stack. Undefined on success. + * @return True on success. False if the parameter stack was not + * exactly the right length, or if a translation phrase + * could not be found. + */ + virtual bool FormatString( + char *buffer, + size_t maxlength, + const char *format, + void **params, + unsigned int numparams, + size_t *pOutLength, + const char **pFailPhrase) =0; + }; + + /** + * @brief Provides functions for translation. + */ + class ITranslator : public SMInterface + { + public: + virtual const char *GetInterfaceName() =0; + virtual unsigned int GetInterfaceVersion() =0; + public: + /** + * @brief Creates a new phrase collection object. + * + * @return A new phrase collection object, which must be + * destroyed via IPhraseCollection::Destroy() when + * no longer needed. + */ + virtual IPhraseCollection *CreatePhraseCollection() =0; + + /** + * @brief Returns the server language. + * + * @return Server language index. + */ + virtual unsigned int GetServerLanguage() =0; + + /** + * @brief Returns a client's language. + * + * @param client Client index. + * @return Client language index, or server's if client's is + * not known. + */ + virtual unsigned int GetClientLanguage(int client) =0; + + /** + * @brief Sets the global client SourceMod will use for assisted + * translations (that is, %t). + * + * @param index Client index (0 for server). + * @return Old global client value. + */ + virtual int SetGlobalTarget(int index) =0; + + /** + * @brief Returns the global client SourceMod is currently using + * for assisted translations (that is, %t). + * + * @return Global client index (0 for server). + */ + virtual int GetGlobalTarget() const =0; + + /** + * @brief Formats a phrase given a parameter stack. The parameter + * stack size must exactly match the expected parameter count. If + * this count is too small or too large, the format fails. + * + * Note: This is the same as IPhraseCollection::FormatString(), except + * that the IPhraseCollection parameter is explicit instead of implicit. + * + * @param buffer Buffer to store formatted text. + * @param maxlength Maximum length of the buffer. + * @param format String containing format information. + * This is equivalent to SourceMod's Format() + * native, and sub-translations are acceptable. + * @param pPhrases Optional phrase collection pointer to search for + * phrases. + * @param params An array of pointers to each parameter. + * Integer parameters must have a pointer to the integer. + * Float parameters must have a pointer to a float. + * String parameters must be a string pointer. + * Char parameters must be a pointer to a char. + * Translation parameters fill multiple indexes in the + * array. For %T translations, the expected stack is: + * [phrase string pointer] [int target id pointer] [...] + * Where [...] is the required parameters for the translation, + * in the order expected by the phrase, not the phrase's + * translation. For example, say the format is: + * "%d %T" and the phrase's format is {1:s,2:f}, then the + * parameter stack should be: + * int *, const char *, int *, const char *, float * + * The %t modifier is the same except the target id pointer + * would be removed: + * int *, const char *, const char *, float * + * @param numparams Number of parameters in the params array. + * @param pOutLength Optional pointer filled with output length on success. + * @param pFailPhrase Optional pointer; on failure, is filled with NULL if the + * failure was not due to a failed translation phrase. + * Otherwise, it is filled with the given phrase name pointer + * from the parameter stack. Undefined on success. + * @return True on success. False if the parameter stack was not + * exactly the right length, or if a translation phrase + * could not be found. + */ + virtual bool FormatString( + char *buffer, + size_t maxlength, + const char *format, + IPhraseCollection *pPhrases, + void **params, + unsigned int numparams, + size_t *pOutLength, + const char **pFailPhrase) =0; + }; +} + +#endif //_INCLUDE_SOURCEMOD_TRANSLATOR_INTERFACE_H_ + diff --git a/public/mms_sample_ext/sm_sdk_config.cpp b/public/mms_sample_ext/sm_sdk_config.cpp index 6c5f377a..56107441 100644 --- a/public/mms_sample_ext/sm_sdk_config.cpp +++ b/public/mms_sample_ext/sm_sdk_config.cpp @@ -79,6 +79,9 @@ bool SM_AcquireInterfaces(char *error, size_t maxlength) #if defined SMEXT_ENABLE_TEXTPARSERS SM_FIND_IFACE_OR_FAIL(TEXTPARSERS, sm_text, error, maxlength); #endif +#if defined SMEXT_ENABLE_TRANSLATOR + SM_FIND_IFACE_OR_FAIL(TRANSLATOR, sm_translator, error, maxlength); +#endif return true; } @@ -131,6 +134,9 @@ void SM_UnsetInterfaces() #if defined SMEXT_ENABLE_TEXTPARSERS sm_text = NULL; #endif +#if defined SMEXT_ENABLE_TRANSLATOR + sm_translator = NULL; +#endif } IExtension *myself = NULL; @@ -179,4 +185,6 @@ SourceMod::IAdminSystem *sm_adminsys = NULL; #if defined SMEXT_ENABLE_TEXTPARSERS SourceMod::ITextParsers *sm_text = NULL; #endif - +#if defined SMEXT_ENABLE_TRANSLATOR +SourceMod::ITranslator *sm_translator = NULL; +#endif diff --git a/public/mms_sample_ext/sm_sdk_config.h b/public/mms_sample_ext/sm_sdk_config.h index 8cd53d3e..65a3d1d9 100644 --- a/public/mms_sample_ext/sm_sdk_config.h +++ b/public/mms_sample_ext/sm_sdk_config.h @@ -70,6 +70,7 @@ void SM_UnsetInterfaces(); //#define SMEXT_ENABLE_PLUGINSYS //#define SMEXT_ENABLE_ADMINSYS //#define SMEXT_ENABLE_TEXTPARSERS +//#define SMEXT_ENABLE_TRANSLATOR /** @@ -155,5 +156,10 @@ extern SourceMod::IAdminSystem *sm_adminsys; extern SourceMod::ITextParsers *sm_text; #endif +#if defined SMEXT_ENABLE_TRANSLATOR +#include +extern SourceMod::ITranslator *sm_translator; +#endif + #endif //_INCLUDE_SOURCEMOD_CONFIG_H_ diff --git a/public/sample_ext/sdk/smsdk_config.h b/public/sample_ext/sdk/smsdk_config.h index 9421f5aa..3d4f60cc 100644 --- a/public/sample_ext/sdk/smsdk_config.h +++ b/public/sample_ext/sdk/smsdk_config.h @@ -75,5 +75,6 @@ //#define SMEXT_ENABLE_ADMINSYS //#define SMEXT_ENABLE_TEXTPARSERS //#define SMEXT_ENABLE_USERMSGS +//#define SMEXT_ENABLE_TRANSLATOR #endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ diff --git a/public/sample_ext/sdk/smsdk_ext.cpp b/public/sample_ext/sdk/smsdk_ext.cpp index 674e04e4..865f536e 100644 --- a/public/sample_ext/sdk/smsdk_ext.cpp +++ b/public/sample_ext/sdk/smsdk_ext.cpp @@ -94,6 +94,9 @@ ITextParsers *textparsers = NULL; #if defined SMEXT_ENABLE_USERMSGS IUserMessages *usermsgs = NULL; #endif +#if defined SMEXT_ENABLE_TRANSLATOR +ITranslator *translator = NULL; +#endif /** Exports the main interface */ PLATFORM_EXTERN_C IExtensionInterface *GetSMExtAPI() @@ -179,6 +182,9 @@ bool SDKExtension::OnExtensionLoad(IExtension *me, IShareSys *sys, char *error, #if defined SMEXT_ENABLE_USERMSGS SM_GET_IFACE(USERMSGS, usermsgs); #endif +#if defined SMEXT_ENABLE_TRANSLATOR + SM_GET_IFACE(TRANSLATOR, translator); +#endif if (SDK_OnLoad(error, maxlength, late)) { diff --git a/public/sample_ext/sdk/smsdk_ext.h b/public/sample_ext/sdk/smsdk_ext.h index 0ee0dfa2..115fc800 100644 --- a/public/sample_ext/sdk/smsdk_ext.h +++ b/public/sample_ext/sdk/smsdk_ext.h @@ -88,6 +88,9 @@ #if defined SMEXT_ENABLE_USERMSGS #include #endif +#if defined SMEXT_ENABLE_TRANSLATOR +#include +#endif #if defined SMEXT_CONF_METAMOD #include @@ -283,6 +286,9 @@ extern IAdminSystem *adminsys; #if defined SMEXT_ENABLE_USERMSGS extern IUserMessages *usermsgs; #endif +#if defined SMEXT_ENABLE_TRANSLATOR +extern ITranslator *translator; +#endif #if defined SMEXT_CONF_METAMOD PLUGIN_GLOBALVARS();