csgo-2018-source/localize/localize.cpp
2021-07-24 21:11:47 -07:00

1606 lines
48 KiB
C++

//======= Copyright 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#pragma warning( disable: 4018 ) // '==' : signed/unsigned mismatch in rbtree
#if defined( WIN32 ) && !defined( _X360 )
#include <windows.h>
#include <vadefs.h>
#elif defined( _PS3 )
#elif defined( POSIX )
#include <iconv.h>
#endif
#include <wchar.h>
#include "filesystem.h"
#include "localize/ilocalize.h"
#include "tier1/utlvector.h"
#include "tier1/utlrbtree.h"
#include "tier1/utlsymbol.h"
#include "tier1/utlstring.h"
#include "UnicodeFileHelpers.h"
#include "tier0/icommandline.h"
#include "byteswap.h"
#include "exprevaluator.h"
#include "iregistry.h"
#include <vstdlib/vstrtools.h>
#include "vgui/ISystem.h"
#include "vgui_controls/Controls.h"
#if defined( _X360 )
#include "xbox/xbox_win32stubs.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define MAX_LOCALIZED_CHARS 4096
//-----------------------------------------------------------------------------
//
// Internal implementation
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Maps token names to localized unicode strings
//-----------------------------------------------------------------------------
class CLocalize : public CTier2AppSystem< ILocalize >
{
typedef CTier2AppSystem< ILocalize > BaseClass;
// Methods of IAppSystem
public:
virtual InitReturnVal_t Init();
// ILocalize overrides
public:
virtual bool AddFile( const char *fileName, const char *pPathID, bool bIncludeFallbackSearchPaths );
virtual void RemoveAll();
virtual wchar_t *Find(const char *pName);
virtual const wchar_t *FindSafe(const char *tokenName);
virtual int ConvertANSIToUnicode(const char *ansi, wchar_t *unicode, int unicodeBufferSizeInBytes);
virtual int ConvertUnicodeToANSI(const wchar_t *unicode, char *ansi, int ansiBufferSize);
virtual LocalizeStringIndex_t FindIndex(const char *pName);
virtual const char *GetNameByIndex(LocalizeStringIndex_t index);
virtual wchar_t *GetValueByIndex(LocalizeStringIndex_t index);
virtual LocalizeStringIndex_t GetFirstStringIndex();
virtual LocalizeStringIndex_t GetNextStringIndex(LocalizeStringIndex_t index);
virtual void AddString(const char *tokenName, wchar_t *unicodeString, const char *fileName);
virtual void SetValueByIndex(LocalizeStringIndex_t index, wchar_t *newValue);
virtual bool SaveToFile( const char *fileName );
virtual int GetLocalizationFileCount();
virtual const char *GetLocalizationFileName(int index);
virtual const char *GetFileNameByIndex(LocalizeStringIndex_t index);
virtual void ReloadLocalizationFiles( );
virtual void ConstructString(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const char *tokenName, KeyValues *dialogVariables);
virtual void ConstructString(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, LocalizeStringIndex_t unlocalizedTextSymbol, KeyValues *dialogVariables);
virtual void SetTextQuery( ILocalizeTextQuery *pQuery );
virtual void InstallChangeCallback( ILocalizationChangeCallback *pCallback );
virtual void RemoveChangeCallback( ILocalizationChangeCallback *pCallback );
virtual const char *FindAsUTF8( const char *pchTokenName );
virtual wchar_t* GetAsianFrequencySequence( const char * pLanguage );
protected:
// internal "interface"
virtual void ConstructStringVArgsInternal(char *unicodeOutput, int unicodeBufferSizeInBytes, const char *formatString, int numFormatParameters, va_list argList);
virtual void ConstructStringVArgsInternal(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const wchar_t *formatString, int numFormatParameters, va_list argList);
virtual void ConstructStringKeyValuesInternal(char *unicodeOutput, int unicodeBufferSizeInBytes, const char *formatString, KeyValues *localizationVariables);
virtual void ConstructStringKeyValuesInternal(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const wchar_t *formatString, KeyValues *localizationVariables);
// Other public methods
public:
CLocalize();
virtual ~CLocalize();
// returns whether a file has already been loaded
bool LocalizationFileIsLoaded( const char *name );
private:
struct localizedstring_t
{
LocalizeStringIndex_t nameIndex;
// nameIndex == LOCALIZE_INVALID_STRING_INDEX is used only for searches and implies
// that pszValueString will be used from union fields.
union
{
LocalizeStringIndex_t valueIndex; // Used when nameIndex != LOCALIZE_INVALID_STRING_INDEX
const char * pszValueString; // Used only if nameIndex == LOCALIZE_INVALID_STRING_INDEX
};
CUtlSymbol filename;
};
struct LocalizationFileInfo_t
{
CUtlSymbol symName;
CUtlSymbol symPathID;
bool bIncludeFallbacks;
static bool LessFunc( const LocalizationFileInfo_t& lhs, const LocalizationFileInfo_t& rhs )
{
int iresult = Q_stricmp( lhs.symPathID.String(), rhs.symPathID.String() );
if ( iresult != 0 )
{
return iresult == -1;
}
return Q_stricmp( lhs.symName.String(), rhs.symName.String() ) < 0;
}
};
struct fastvalue_t
{
int valueindex;
const wchar_t *search;
static CLocalize *s_pTable;
};
private:
bool AddAllLanguageFiles( const char *baseFileName );
void BuildFastValueLookup();
void DiscardFastValueLookup();
int FindExistingValueIndex( const wchar_t *value );
bool ReadLocalizationFile( const char *pRelativePath, const char *pPathID );
void InvokeChangeCallbacks( );
virtual int ConvertANSIToUCS2(const char *ansi, OUT_Z_BYTECAP(unicodeBufferSizeInBytes) ucs2 *unicode, int unicodeBufferSizeInBytes);
virtual int ConvertUCS2ToANSI(const ucs2 *unicode, OUT_Z_BYTECAP(ansiBufferSize) char *ansi, int ansiBufferSize);
#if defined ( POSIX ) && !defined( _PS3 )
virtual void AddString(const char *tokenName, ucs2 *unicodeString, const char *fileName);
#endif
char m_szLanguage[64];
bool m_bUseOnlyLongestLanguageString;
bool m_bSuppressChangeCallbacks;
bool m_bQueuedChangeCallback;
// Stores the symbol lookup
CUtlRBTree<localizedstring_t, LocalizeStringIndex_t> m_Lookup;
// stores the string data
CUtlVector<char> m_Names;
CUtlVector<wchar_t> m_Values;
CUtlSymbol m_CurrentFile;
CUtlVector< LocalizationFileInfo_t > m_LocalizationFiles;
CUtlRBTree< fastvalue_t, int > m_FastValueLookup;
ILocalizeTextQuery *m_pQuery;
static CLocalize *s_pTable;
CUtlVector< ILocalizationChangeCallback* > m_ChangeCallbacks;
CUtlBuffer m_bufAsianFrequencySequence;
bool m_bAsianFrequencySequenceLoaded;
// Less function, for sorting strings
static bool SymLess( localizedstring_t const& i1, localizedstring_t const& i2 );
static bool FastValueLessFunc( const fastvalue_t& lhs, const fastvalue_t& rhs );
};
// global instance of table
static CLocalize s_Localize;
// expose the interface
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CLocalize, ILocalize, LOCALIZE_INTERFACE_VERSION, s_Localize);
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CLocalize::CLocalize() :
m_Lookup( 0, 0, SymLess ), m_Names( 1024 ), m_Values( 2048 ), m_FastValueLookup( 0, 0, FastValueLessFunc )
{
m_bUseOnlyLongestLanguageString = false;
m_bSuppressChangeCallbacks = false;
m_bQueuedChangeCallback = false;
m_pQuery = NULL;
m_bAsianFrequencySequenceLoaded = false;
}
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CLocalize::~CLocalize()
{
m_Names.Purge();
m_Values.Purge();
m_LocalizationFiles.Purge();
}
//-----------------------------------------------------------------------------
// Init
//-----------------------------------------------------------------------------
InitReturnVal_t CLocalize::Init()
{
InitReturnVal_t nRetVal = BaseClass::Init();
if ( nRetVal != INIT_OK )
return nRetVal;
m_bUseOnlyLongestLanguageString = ( CommandLine()->FindParm("-all_languages") > 0 );
return INIT_OK;
}
//-----------------------------------------------------------------------------
// Sets the callback used to check length of a localization string
//-----------------------------------------------------------------------------
void CLocalize::SetTextQuery( ILocalizeTextQuery *pQuery )
{
m_pQuery = pQuery;
}
//-----------------------------------------------------------------------------
// Add, remove, invoke localization string change callbacks
//-----------------------------------------------------------------------------
void CLocalize::InstallChangeCallback( ILocalizationChangeCallback *pCallback )
{
if ( m_ChangeCallbacks.Find( pCallback ) != m_ChangeCallbacks.InvalidIndex() )
{
Warning( "CLocalize::InstallChangeCallback: Attempted to add the same callback twice!\n" );
return;
}
m_ChangeCallbacks.AddToTail( pCallback );
}
void CLocalize::RemoveChangeCallback( ILocalizationChangeCallback *pCallback )
{
m_ChangeCallbacks.FindAndRemove( pCallback );
}
//-----------------------------------------------------------------------------
// Purpose: Finds a string in the table
//-----------------------------------------------------------------------------
const char *CLocalize::FindAsUTF8( const char *pchTokenName )
{
wchar_t *pwch = Find( pchTokenName );
if ( !pwch )
return pchTokenName;
static char rgchT[2048];
Q_UnicodeToUTF8( pwch, rgchT, sizeof( rgchT ) );
return rgchT;
}
void CLocalize::InvokeChangeCallbacks( )
{
// This is to prevent a ton of change callbacks while loading using -all_languages
if ( m_bSuppressChangeCallbacks )
{
m_bQueuedChangeCallback = true;
return;
}
int nCount = m_ChangeCallbacks.Count();
for ( int i = 0; i < nCount; ++i )
{
m_ChangeCallbacks[i]->OnLocalizationChanged();
}
}
int DistanceToEndOfLine( ucs2 *start )
{
int nResult = 0;
if ( !*start )
{
return nResult;
}
while ( *start )
{
if ( *start == 0x0D || *start== 0x0A )
{
break;
}
start++;
nResult++;
}
while ( *start == 0x0D || *start== 0x0A )
{
start++;
nResult++;
}
return nResult;
}
//-----------------------------------------------------------------------------
// Purpose:Reads the contents of a file
//-----------------------------------------------------------------------------
bool CLocalize::ReadLocalizationFile( const char *pRelativePath, const char *pPathID )
{
FileHandle_t file = g_pFullFileSystem->Open( pRelativePath, "rb", pPathID );
if ( FILESYSTEM_INVALID_HANDLE == file )
return false;
// this is an optimization so that the filename string doesn't have to get converted to a symbol for each key/value
m_CurrentFile = pRelativePath;
// read into a memory block
int fileSize = g_pFullFileSystem->Size(file);
int bufferSize = g_pFullFileSystem->GetOptimalReadSize( file, fileSize + sizeof(wchar_t) );
ucs2 *memBlock = (ucs2 *)g_pFullFileSystem->AllocOptimalReadBuffer(file, bufferSize);
bool bReadOK = ( g_pFullFileSystem->ReadEx(memBlock, bufferSize, fileSize, file) != 0 );
// finished with file
g_pFullFileSystem->Close(file);
// null-terminate the stream
memBlock[fileSize / sizeof(ucs2)] = 0x0000;
// check the first character, make sure this a little-endian unicode file
ucs2 *data = memBlock;
ucs2 signature = LittleShort( data[0] );
if ( !bReadOK || signature != 0xFEFF )
{
Msg( "Ignoring non-unicode close caption file %s\n", pRelativePath );
g_pFullFileSystem->FreeOptimalReadBuffer( memBlock );
m_CurrentFile = UTL_INVAL_SYMBOL;
return false;
}
// ensure little-endian unicode reads correctly on all platforms
CByteswap byteSwap;
byteSwap.SetTargetBigEndian( false );
byteSwap.SwapBufferToTargetEndian( data, data, fileSize / sizeof(ucs2) );
// skip past signature
data++;
// parse out a token at a time
enum states_e
{
STATE_BASE, // looking for base settings
STATE_TOKENS, // reading in unicode tokens
};
bool bQuoted;
bool bEnglishFile = false;
if ( Q_stristr(pRelativePath, "_english.txt") )
{
bEnglishFile = true;
}
bool spew = false;
if ( CommandLine()->FindParm( "-ccsyntax" ) )
{
spew = true;
}
BuildFastValueLookup();
CExpressionEvaluator ExpressionHandler;
states_e state = STATE_BASE;
while (1)
{
// read the key and the value
ucs2 keytoken[128];
data = ReadUnicodeToken(data, keytoken, 128, bQuoted);
if (!keytoken[0])
break; // we've hit the null terminator
// convert the token to a string
char key[128];
ConvertUCS2ToANSI(keytoken, key, sizeof(key));
// if we have a C++ style comment, read to end of line and continue
if (!strnicmp(key, "//", 2))
{
data = ReadToEndOfLine(data);
continue;
}
if ( spew )
{
Msg( "%s\n", key );
}
ucs2 valuetoken[ MAX_LOCALIZED_CHARS ];
bool bEnoughCapacity = true;
if ( DistanceToEndOfLine( data ) > ( MAX_LOCALIZED_CHARS - 1 ) )
{
Warning( "Error: Localization key value exceeds MAX_LOCALIZED_CHARS. Problem key: %s\n", key );
bEnoughCapacity = false;
}
data = ReadUnicodeToken(data, valuetoken, MAX_LOCALIZED_CHARS, bQuoted);
if (!valuetoken[0] && !bQuoted)
break; // we've hit the null terminator
if (state == STATE_BASE)
{
if (!stricmp(key, "Language"))
{
// copy out our language setting
char value[MAX_LOCALIZED_CHARS];
ConvertUCS2ToANSI(valuetoken, value, sizeof(value));
strncpy(m_szLanguage, value, sizeof(m_szLanguage) - 1);
}
else if (!stricmp(key, "Tokens"))
{
state = STATE_TOKENS;
}
else if (!stricmp(key, "}"))
{
// we've hit the end
break;
}
}
else if (state == STATE_TOKENS)
{
if (!stricmp(key, "}"))
{
// end of tokens
state = STATE_BASE;
}
else
{
// skip our [english] beginnings (in non-english files)
if ( (bEnglishFile) || (!bEnglishFile && strnicmp(key, "[english]", 9)))
{
// Check for a conditional tag
bool bAccepted = true;
ucs2 conditional[ MAX_LOCALIZED_CHARS ];
ucs2 *tempData = ReadUnicodeToken(data, conditional, MAX_LOCALIZED_CHARS, bQuoted);
char cond[MAX_LOCALIZED_CHARS];
V_UCS2ToUTF8( conditional, cond, sizeof(cond) );
if ( !bQuoted && (strstr( cond, "[$" )||strstr( cond, "[!$" )) )
{
// Evaluate the conditional tag
char cond[MAX_LOCALIZED_CHARS];
ConvertUCS2ToANSI( conditional, cond, sizeof( cond ) );
ExpressionHandler.Evaluate( bAccepted, cond );
data = tempData;
}
if ( bAccepted && bEnoughCapacity )
{
// add the string to the table
AddString(key, valuetoken, NULL);
}
}
}
}
}
g_pFullFileSystem->FreeOptimalReadBuffer( memBlock );
m_CurrentFile = UTL_INVAL_SYMBOL;
DiscardFastValueLookup();
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Adds the contents of a file
//-----------------------------------------------------------------------------
bool CLocalize::AddFile( const char *szFileName, const char *pPathID, bool bIncludeFallbackSearchPaths )
{
// use the correct file based on the chosen language
static const char *const LANGUAGE_STRING = "%language%";
static const char *const ENGLISH_STRING = "english";
static const int MAX_LANGUAGE_NAME_LENGTH = 64;
int offs = 0;
bool success = false;
char language[MAX_LANGUAGE_NAME_LENGTH];
memset( language, 0, sizeof(language) );
if ( Q_IsAbsolutePath( szFileName ) )
{
Warning( "Full paths not allowed in localization file specificaton %s\n", szFileName );
return false;
}
const char *langptr = strstr(szFileName, LANGUAGE_STRING);
if (langptr)
{
// LOAD THE ENGLISH FILE FIRST
// always load the file to make sure we're not missing any strings
// copy out the initial part of the string
offs = langptr - szFileName;
char fileName[MAX_PATH];
strncpy(fileName, szFileName, offs);
fileName[offs] = 0;
if ( m_bUseOnlyLongestLanguageString )
{
return AddAllLanguageFiles( fileName );
}
// append "english" as our default language
Q_strncat(fileName, ENGLISH_STRING, sizeof( fileName ), COPY_ALL_CHARACTERS );
// append the end of the initial string
offs += strlen(LANGUAGE_STRING);
Q_strncat(fileName, szFileName + offs, sizeof( fileName ), COPY_ALL_CHARACTERS);
success = AddFile( fileName, pPathID, bIncludeFallbackSearchPaths );
bool bValid = true;
if ( IsPC() )
{
if ( CommandLine()->CheckParm( "-language" ) )
{
Q_strncpy( language, CommandLine()->ParmValue( "-language", "english" ), sizeof( language ) );
bValid = true;
}
else
{
bValid = vgui::system()->GetRegistryString( "HKEY_CURRENT_USER\\Software\\Valve\\Steam\\Language", language, sizeof(language)-1 );
}
if ( bValid && !Q_stricmp( language, "unknown" ) )
{
// Fall back to english
bValid = false;
}
}
else
{
#ifdef _GAMECONSOLE
Q_strncpy( language, XBX_GetLanguageString(), sizeof( language ) );
#endif
}
// LOAD THE LOCALIZED FILE IF IT'S NOT ENGLISH
// append the language
if ( bValid )
{
if ( strlen(language) != 0 && stricmp(language, ENGLISH_STRING) != 0 )
{
// copy out the initial part of the string
offs = langptr - szFileName;
strncpy(fileName, szFileName, offs);
fileName[offs] = 0;
Q_strncat(fileName, language, sizeof( fileName ), COPY_ALL_CHARACTERS);
// append the end of the initial string
offs += strlen(LANGUAGE_STRING);
Q_strncat(fileName, szFileName + offs, sizeof( fileName ), COPY_ALL_CHARACTERS );
success &= AddFile( fileName, pPathID, bIncludeFallbackSearchPaths );
}
}
return success;
}
// store the localization file name if it doesn't already exist
LocalizationFileInfo_t search;
search.symName = szFileName;
search.symPathID = pPathID ? pPathID : "";
search.bIncludeFallbacks = false;
int lfc = m_LocalizationFiles.Count();
for ( int lf = 0; lf < lfc; ++lf )
{
LocalizationFileInfo_t& entry = m_LocalizationFiles[ lf ];
if ( !Q_stricmp( entry.symName.String(), szFileName ) )
{
m_LocalizationFiles.Remove( lf );
break;
}
}
m_LocalizationFiles.AddToTail( search );
bool bOk = ReadLocalizationFile( szFileName, pPathID );
if ( !bOk )
{
DevWarning( "ILocalize::AddFile() failed to load file \"%s\".\n", szFileName );
}
return bOk;
}
//-----------------------------------------------------------------------------
// Purpose: Load all the localized language strings, and uses the longest string from each language
//-----------------------------------------------------------------------------
bool CLocalize::AddAllLanguageFiles( const char *baseFileName )
{
bool bSuccess = true;
// Each new language load could potentially change the string value
// This will suppress callbacks until we're done.
m_bSuppressChangeCallbacks = true;
if ( IsX360() )
{
#ifdef _X360
// xbox cannot support FindFirst/FindNext due to zips
const char *pLanguageString = NULL;
while ( 1 )
{
pLanguageString = XBX_GetNextSupportedLanguage( pLanguageString, NULL );
if ( !pLanguageString )
{
// end of list
break;
}
// re-add in the search path
char szFile[MAX_PATH];
V_snprintf( szFile, sizeof( szFile ), "%s%s.txt", baseFileName, pLanguageString );
// add the file
bSuccess &= AddFile( szFile, NULL, true );
}
#endif
}
else
{
// work out the path the files are in
char szFilePath[MAX_PATH];
Q_strncpy( szFilePath, baseFileName, sizeof(szFilePath) );
char *pLastSlash = strrchr( szFilePath, '\\' );
if ( !pLastSlash )
{
pLastSlash = strrchr( szFilePath, '/' );
}
if ( pLastSlash )
{
pLastSlash[1] = 0;
}
else
{
szFilePath[0] = 0;
}
// iterate through and add all the languages (for development)
// the longest string out of all the languages will be used
char szSearchPath[MAX_PATH];
Q_snprintf( szSearchPath, sizeof(szSearchPath), "%s*.txt", baseFileName );
FileFindHandle_t hFind = NULL;
const char *file = g_pFullFileSystem->FindFirst( szSearchPath, &hFind );
while ( file )
{
// re-add in the search path
char szFile[MAX_PATH];
V_snprintf( szFile, sizeof(szFile), "%s%s", szFilePath, file );
// add the file
bSuccess &= AddFile( szFile, NULL, true );
// next file
file = g_pFullFileSystem->FindNext( hFind );
}
g_pFullFileSystem->FindClose( hFind );
}
m_bSuppressChangeCallbacks = false;
if ( m_bQueuedChangeCallback )
{
m_bQueuedChangeCallback = false;
InvokeChangeCallbacks();
}
return bSuccess;
}
//-----------------------------------------------------------------------------
// Purpose: saves the entire contents of the token tree to the file
//-----------------------------------------------------------------------------
bool CLocalize::SaveToFile( const char *szFileName )
{
// parse out the file
FileHandle_t file = g_pFullFileSystem->Open(szFileName, "wb");
if (!file)
return false;
// only save the symbols relevant to this file
CUtlSymbol fileName = szFileName;
// write litte-endian unicode marker
unsigned short marker = 0xFEFF;
marker = LittleShort( marker );
g_pFullFileSystem->Write(&marker, sizeof( marker ), file);
const char *startStr = "\"lang\"\r\n{\r\n\"Language\" \"English\"\r\n\"Tokens\"\r\n{\r\n";
const char *endStr = "}\r\n}\r\n";
// write out the first string
static ucs2 unicodeString[1024];
int strLength = ConvertANSIToUCS2(startStr, unicodeString, sizeof(unicodeString));
if (!strLength)
return false;
g_pFullFileSystem->Write(unicodeString, strlen(startStr) * sizeof(ucs2), file);
// convert our spacing characters to unicode
// wchar_t unicodeSpace = L' ';
ucs2 unicodeQuote = L'\"';
ucs2 unicodeCR = L'\r';
ucs2 unicodeNewline = L'\n';
ucs2 unicodeTab = L'\t';
// write out all the key/value pairs
for (LocalizeStringIndex_t idx = GetFirstStringIndex(); idx != LOCALIZE_INVALID_STRING_INDEX; idx = GetNextStringIndex(idx))
{
// only write strings that belong in this file
if (fileName != m_Lookup[idx].filename)
continue;
const char *name = GetNameByIndex(idx);
wchar_t *value = GetValueByIndex(idx);
// convert the name to a unicode string
ConvertANSIToUCS2(name, unicodeString, sizeof(unicodeString));
g_pFullFileSystem->Write(&unicodeTab, sizeof(ucs2), file);
// write out
g_pFullFileSystem->Write(&unicodeQuote, sizeof(ucs2), file);
g_pFullFileSystem->Write(unicodeString, strlen(name) * sizeof(ucs2), file);
g_pFullFileSystem->Write(&unicodeQuote, sizeof(ucs2), file);
g_pFullFileSystem->Write(&unicodeTab, sizeof(ucs2), file);
g_pFullFileSystem->Write(&unicodeTab, sizeof(ucs2), file);
g_pFullFileSystem->Write(&unicodeQuote, sizeof(ucs2), file);
#ifdef POSIX
ucs2 ucs2Value[MAX_LOCALIZED_CHARS];
V_UnicodeToUCS2( value, wcslen(value)*sizeof(wchar_t), (char *)ucs2Value, sizeof(ucs2Value) );
g_pFullFileSystem->Write(ucs2Value, wcslen(value) * sizeof(ucs2), file);
#else
g_pFullFileSystem->Write(value, wcslen(value) * sizeof(ucs2), file);
#endif
g_pFullFileSystem->Write(&unicodeQuote, sizeof(ucs2), file);
g_pFullFileSystem->Write(&unicodeCR, sizeof(ucs2), file);
g_pFullFileSystem->Write(&unicodeNewline, sizeof(ucs2), file);
}
// write end string
strLength = ConvertANSIToUCS2(endStr, unicodeString, sizeof(unicodeString));
g_pFullFileSystem->Write(unicodeString, strLength * sizeof(ucs2), file);
g_pFullFileSystem->Close(file);
return true;
}
//-----------------------------------------------------------------------------
// Purpose: for development, reloads localization files
//-----------------------------------------------------------------------------
void CLocalize::ReloadLocalizationFiles( )
{
// re-add all the localization files
for (int i = 0; i < m_LocalizationFiles.Count(); i++)
{
LocalizationFileInfo_t& entry = m_LocalizationFiles[ i ];
AddFile
(
entry.symName.String(),
entry.symPathID.String()[0] ? entry.symPathID.String() : NULL,
entry.bIncludeFallbacks
);
}
}
//-----------------------------------------------------------------------------
// Purpose: Used to sort strings
//-----------------------------------------------------------------------------
bool CLocalize::SymLess(localizedstring_t const &i1, localizedstring_t const &i2)
{
const char *str1 = (i1.nameIndex == LOCALIZE_INVALID_STRING_INDEX) ? i1.pszValueString :
&s_Localize.m_Names[i1.nameIndex];
const char *str2 = (i2.nameIndex == LOCALIZE_INVALID_STRING_INDEX) ? i2.pszValueString :
&s_Localize.m_Names[i2.nameIndex];
return stricmp(str1, str2) < 0;
}
//-----------------------------------------------------------------------------
// Purpose: Finds a string in the table
//-----------------------------------------------------------------------------
wchar_t *CLocalize::Find(const char *pName)
{
LocalizeStringIndex_t idx = FindIndex(pName);
if (idx == LOCALIZE_INVALID_STRING_INDEX)
return NULL;
return &m_Values[m_Lookup[idx].valueIndex];
}
// Like Find(), but as a failsafe, returns an error message instead of NULL if the string isn't found.
const wchar_t *CLocalize::FindSafe(const char *pName)
{
#ifdef _CERT
const wchar_t *failsafe = L"";
#else
const wchar_t *failsafe = L"#FIXME_LOCALIZATION_FAIL_MISSING_STRING";
#endif
const wchar_t *locstr = Find( pName );
if ( !locstr )
{
DevMsg( "CLocalize::FindSafe failed to localize: %s\n", pName );
return failsafe;
}
else
{
return locstr;
}
}
//-----------------------------------------------------------------------------
// Purpose: finds the index of a token by token name
//-----------------------------------------------------------------------------
LocalizeStringIndex_t CLocalize::FindIndex(const char *pName)
{
if (!pName)
return LOCALIZE_INVALID_STRING_INDEX;
// strip the pound character (which is used elsewhere to indicate that it's a string that should be translated)
if (pName[0] == '#')
{
pName++;
}
// Passing this special invalid symbol makes the comparison function
// use the string passed in the context
localizedstring_t invalidItem;
invalidItem.nameIndex = LOCALIZE_INVALID_STRING_INDEX;
invalidItem.pszValueString = pName;
return m_Lookup.Find( invalidItem );
}
#if defined( POSIX ) && !defined( _PS3 )
void CLocalize::AddString(const char *pString, ucs2 *pUCS2Value, const char *fileName)
{
if (!pString || !pUCS2Value )
return;
wchar_t pValue[2048];
V_UCS2ToUnicode( pUCS2Value, pValue, sizeof(pValue) );
AddString( pString, pValue, fileName );
}
#endif
//-----------------------------------------------------------------------------
// Finds and/or creates a symbol based on the string
//-----------------------------------------------------------------------------
void CLocalize::AddString(const char *pString, wchar_t *pValue, const char *fileName)
{
if (!pString)
return;
MEM_ALLOC_CREDIT();
// see if the value is already in our string table
int valueIndex = FindExistingValueIndex( pValue );
if ( valueIndex == LOCALIZE_INVALID_STRING_INDEX )
{
int len = wcslen( pValue ) + 1;
valueIndex = m_Values.AddMultipleToTail( len );
memcpy( &m_Values[valueIndex], pValue, len * sizeof(wchar_t) );
}
// see if the key is already in the table
LocalizeStringIndex_t stridx = FindIndex( pString );
localizedstring_t item;
item.nameIndex = stridx;
if ( stridx == LOCALIZE_INVALID_STRING_INDEX )
{
// didn't find, insert the string into the vector.
int len = strlen(pString) + 1;
stridx = m_Names.AddMultipleToTail( len );
memcpy( &m_Names[stridx], pString, len * sizeof(char) );
item.nameIndex = stridx;
item.valueIndex = valueIndex;
item.filename = fileName ? fileName : m_CurrentFile;
m_Lookup.Insert( item );
}
else
{
// it's already in the table
if ( m_bUseOnlyLongestLanguageString )
{
// check which string is longer
wchar_t *newValue = pValue;
wchar_t *oldValue = GetValueByIndex( stridx );
// get the width of the string, using just the first font
if ( m_pQuery )
{
int newWide = m_pQuery->ComputeTextWidth( newValue );
int oldWide = m_pQuery->ComputeTextWidth( oldValue );
// if the new one is shorter, don't let it be added
if (newWide < oldWide)
return;
}
}
// replace the current item
item.nameIndex = GetNameByIndex( stridx ) - &m_Names[ 0 ];
item.valueIndex = valueIndex;
item.filename = fileName ? fileName : m_CurrentFile;
m_Lookup[ stridx ] = item;
InvokeChangeCallbacks();
}
}
//-----------------------------------------------------------------------------
// Remove all symbols in the table.
//-----------------------------------------------------------------------------
void CLocalize::RemoveAll()
{
m_Lookup.RemoveAll();
m_Names.RemoveAll();
m_Values.RemoveAll();
m_LocalizationFiles.RemoveAll();
}
//-----------------------------------------------------------------------------
// Purpose: iteration functions
//-----------------------------------------------------------------------------
LocalizeStringIndex_t CLocalize::GetFirstStringIndex()
{
return m_Lookup.FirstInorder();
}
//-----------------------------------------------------------------------------
// Purpose: returns the next index, or INVALID_STRING_INDEX if no more strings available
//-----------------------------------------------------------------------------
LocalizeStringIndex_t CLocalize::GetNextStringIndex(LocalizeStringIndex_t index)
{
LocalizeStringIndex_t idx = m_Lookup.NextInorder(index);
if (idx == m_Lookup.InvalidIndex())
return LOCALIZE_INVALID_STRING_INDEX;
return idx;
}
//-----------------------------------------------------------------------------
// Purpose: gets the name of the localization string by index
//-----------------------------------------------------------------------------
const char *CLocalize::GetNameByIndex(LocalizeStringIndex_t index)
{
localizedstring_t &lstr = m_Lookup[index];
return &m_Names[lstr.nameIndex];
}
//-----------------------------------------------------------------------------
// Purpose: gets the localized string value by index
//-----------------------------------------------------------------------------
wchar_t *CLocalize::GetValueByIndex(LocalizeStringIndex_t index)
{
if (index == LOCALIZE_INVALID_STRING_INDEX)
return NULL;
localizedstring_t &lstr = m_Lookup[index];
return &m_Values[lstr.valueIndex];
}
CLocalize *CLocalize::s_pTable = NULL;
bool CLocalize::FastValueLessFunc( const fastvalue_t& lhs, const fastvalue_t& rhs )
{
Assert( s_pTable );
const wchar_t *w1 = lhs.search ? lhs.search : &s_pTable->m_Values[ lhs.valueindex ];
const wchar_t *w2 = rhs.search ? rhs.search : &s_pTable->m_Values[ rhs.valueindex ];
return ( wcscmp( w1, w2 ) < 0 ) ? true : false;
}
void CLocalize::BuildFastValueLookup()
{
m_FastValueLookup.RemoveAll();
s_pTable = this;
// Build it
int c = m_Lookup.Count();
for ( int i = 0; i < c; ++i )
{
fastvalue_t val;
val.valueindex = m_Lookup[ i ].valueIndex;
val.search = NULL;
m_FastValueLookup.Insert( val );
}
}
void CLocalize::DiscardFastValueLookup()
{
m_FastValueLookup.RemoveAll();
s_pTable = NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CLocalize::FindExistingValueIndex( const wchar_t *value )
{
if ( !s_pTable )
return (int)LOCALIZE_INVALID_STRING_INDEX;
fastvalue_t val;
val.valueindex = -1;
val.search = value;
int idx = m_FastValueLookup.Find( val );
if ( idx != m_FastValueLookup.InvalidIndex() )
{
return m_FastValueLookup[ idx ].valueindex;
}
return (int)LOCALIZE_INVALID_STRING_INDEX;
}
//-----------------------------------------------------------------------------
// Purpose: returns which file a string was loaded from
//-----------------------------------------------------------------------------
const char *CLocalize::GetFileNameByIndex(LocalizeStringIndex_t index)
{
localizedstring_t &lstr = m_Lookup[index];
return lstr.filename.String();
}
//-----------------------------------------------------------------------------
// Purpose: sets the value in the index
//-----------------------------------------------------------------------------
void CLocalize::SetValueByIndex(LocalizeStringIndex_t index, wchar_t *newValue)
{
// get the existing string
localizedstring_t &lstr = m_Lookup[index];
wchar_t *wstr = &m_Values[lstr.valueIndex];
// see if the new string will fit within the old memory
int newLen = wcslen(newValue);
int oldLen = wcslen(wstr);
if (newLen > oldLen)
{
// it won't fit, so allocate new memory - this is wasteful, but only happens in edit mode
lstr.valueIndex = m_Values.AddMultipleToTail(newLen + 1);
memcpy(&m_Values[lstr.valueIndex], newValue, (newLen + 1) * sizeof(wchar_t));
}
else
{
// copy the string into the old position
wcscpy(wstr, newValue);
}
InvokeChangeCallbacks();
}
//-----------------------------------------------------------------------------
// Purpose: returns number of localization files currently loaded
//-----------------------------------------------------------------------------
int CLocalize::GetLocalizationFileCount()
{
return m_LocalizationFiles.Count();
}
//-----------------------------------------------------------------------------
// Purpose: returns localization filename by index
//-----------------------------------------------------------------------------
const char *CLocalize::GetLocalizationFileName(int index)
{
return m_LocalizationFiles[index].symName.String();
}
//-----------------------------------------------------------------------------
// Purpose: returns whether a localization file has been loaded already
//-----------------------------------------------------------------------------
bool CLocalize::LocalizationFileIsLoaded(const char *name)
{
int c = m_LocalizationFiles.Count();
for ( int i = 0; i < c; ++i )
{
if ( !Q_stricmp( m_LocalizationFiles[ i ].symName.String(), name ) )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: converts an english string to unicode
//-----------------------------------------------------------------------------
int CLocalize::ConvertANSIToUnicode(const char *ansi, wchar_t *unicode, int unicodeBufferSizeInBytes)
{
#ifdef POSIX
return V_UTF8ToUnicode(ansi, unicode, unicodeBufferSizeInBytes);
#else
int chars = ::MultiByteToWideChar(CP_UTF8, 0, ansi, -1, unicode, unicodeBufferSizeInBytes / sizeof(wchar_t));
unicode[(unicodeBufferSizeInBytes / sizeof(wchar_t)) - 1] = 0;
return chars;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: converts an unicode string to an english string
//-----------------------------------------------------------------------------
int CLocalize::ConvertUnicodeToANSI(const wchar_t *unicode, char *ansi, int ansiBufferSize)
{
#ifdef POSIX
return V_UnicodeToUTF8(unicode, ansi, ansiBufferSize);
#else
int result = ::WideCharToMultiByte(CP_UTF8, 0, unicode, -1, ansi, ansiBufferSize, NULL, NULL);
ansi[ansiBufferSize - 1] = 0;
return result;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: converts an english string to unicode
//-----------------------------------------------------------------------------
int CLocalize::ConvertANSIToUCS2(const char *ansi, OUT_Z_BYTECAP(unicodeBufferSizeInBytes) ucs2 *unicode, int unicodeBufferSizeInBytes)
{
#ifdef POSIX
return V_UTF8ToUCS2(ansi, strlen(ansi)*sizeof(char), unicode, unicodeBufferSizeInBytes);
#else
int chars = ::MultiByteToWideChar(CP_UTF8, 0, ansi, -1, unicode, unicodeBufferSizeInBytes / sizeof(wchar_t));
unicode[(unicodeBufferSizeInBytes / sizeof(wchar_t)) - 1] = 0;
return chars;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: converts an unicode string to an english string
//-----------------------------------------------------------------------------
int CLocalize::ConvertUCS2ToANSI(const ucs2 *unicode, OUT_Z_BYTECAP(ansiBufferSize) char *ansi, int ansiBufferSize)
{
#ifdef POSIX
return V_UCS2ToUTF8(unicode, ansi, ansiBufferSize);
#else
int result = ::WideCharToMultiByte(CP_UTF8, 0, unicode, -1, ansi, ansiBufferSize, NULL, NULL);
ansi[ansiBufferSize - 1] = 0;
return result;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Constructs a string, inserting variables where necessary
//-----------------------------------------------------------------------------
void CLocalize::ConstructString(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const char *tokenName, KeyValues *localizationVariables)
{
LocalizeStringIndex_t index = FindIndex(tokenName);
if (index != LOCALIZE_INVALID_STRING_INDEX)
{
ConstructString(unicodeOutput, unicodeBufferSizeInBytes, index, localizationVariables);
}
else
{
// string not found, just return the token name
ConvertANSIToUnicode(tokenName, unicodeOutput, unicodeBufferSizeInBytes);
}
}
//-----------------------------------------------------------------------------
// Purpose: Constructs a string, inserting variables where necessary
//-----------------------------------------------------------------------------
void CLocalize::ConstructString(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, LocalizeStringIndex_t unlocalizedTextSymbol, KeyValues *localizationVariables)
{
if (unicodeBufferSizeInBytes < 1)
return;
unicodeOutput[0] = 0;
const wchar_t *searchPos = GetValueByIndex(unlocalizedTextSymbol);
if (!searchPos)
{
wcsncpy(unicodeOutput, L"[unknown string]", unicodeBufferSizeInBytes / sizeof(wchar_t));
return;
}
wchar_t *outputPos = unicodeOutput;
//assumes we can't have %s10
//assume both are 0 terminated?
int unicodeBufferSize = unicodeBufferSizeInBytes / sizeof(wchar_t);
while ( *searchPos != '\0' && unicodeBufferSize > 0 )
{
bool shouldAdvance = true;
if ( *searchPos == '%' )
{
// this is an escape sequence that specifies a variable name
if ( searchPos[1] == 's' && searchPos[2] >= '0' && searchPos[2] <= '9' )
{
shouldAdvance = false;
char variableName[3];
variableName[0] = searchPos[1];
variableName[1] = searchPos[2];
variableName[2] = 0;
// Handle this as a valid, fixed substitution string
// look up the variable name
const wchar_t *value = localizationVariables->GetWString( variableName, L"[unknown]" );
int paramSize = wcslen(value);
if (paramSize >= unicodeBufferSize)
{
paramSize = MAX( 0, unicodeBufferSize - 1 );
}
wcsncpy(outputPos, value, paramSize);
unicodeBufferSize -= paramSize;
outputPos += paramSize;
searchPos += 3;
}
else if ( searchPos[1] == '%' )
{
// just a '%' char, just write the second one
searchPos++;
}
else if ( localizationVariables )
{
// get out the variable name
const wchar_t *varStart = searchPos + 1;
// first letter of a valid variable MUST be alphanumeric, otherwise this isn't a variable
if ( iswalnum(*varStart) )
{
const wchar_t *varEnd = wcschr( varStart, '%' );
if ( varEnd && *varEnd == '%' )
{
shouldAdvance = false;
// assume variable names must be ascii, do a quick convert
char variableName[32];
char *vset = variableName;
for ( const wchar_t *pws = varStart; pws < varEnd && (vset < variableName + sizeof(variableName) - 1); ++pws, ++vset )
{
*vset = (char)*pws;
}
*vset = 0;
// look up the variable name
const wchar_t *value = localizationVariables->GetWString( variableName, L"[unknown]" );
int paramSize = wcslen(value);
if (paramSize >= unicodeBufferSize)
{
paramSize = MAX( 0, unicodeBufferSize - 1 );
}
wcsncpy(outputPos, value, paramSize);
unicodeBufferSize -= paramSize;
outputPos += paramSize;
searchPos = varEnd + 1;
}
}
}
}
if (shouldAdvance)
{
//copy it over, char by char
*outputPos = *searchPos;
outputPos++;
unicodeBufferSize--;
searchPos++;
}
}
// ensure null termination
*outputPos = '\0';
}
wchar_t* CLocalize::GetAsianFrequencySequence( const char * pLanguage )
{
if( !m_bAsianFrequencySequenceLoaded )
{
m_bAsianFrequencySequenceLoaded = true;
char szFileName[128];
V_snprintf( szFileName, sizeof( szFileName ), "resource/%s_frequency.txt", pLanguage );
g_pFullFileSystem->ReadFile( szFileName, "GAME", m_bufAsianFrequencySequence );
uint nSize = m_bufAsianFrequencySequence.TellPut() / sizeof( wchar_t );
m_bufAsianFrequencySequence.PutUnsignedShort( 0 ); // 0-terminate
wchar_t * pAsianFrequencySequence = (wchar_t*) m_bufAsianFrequencySequence.Base();
// transcode from LT Unicode to GT Unicode
if( pAsianFrequencySequence[0] == 0xFFFE )
{
// switch from little-endian
for( uint i = 0; i < nSize; ++i )
{
wchar_t &refChar = pAsianFrequencySequence[i];
refChar = ( refChar >> 8 ) | ( refChar << 8 );
}
}
}
if( m_bufAsianFrequencySequence.TellPut() > 2 )
{
wchar_t * pAsianFrequencySequence = (wchar_t*) m_bufAsianFrequencySequence.Base();
if( pAsianFrequencySequence[0] == 0xFEFF )
{
return pAsianFrequencySequence + 1;
}
return pAsianFrequencySequence;
}
return NULL;
}
#if defined( GNUC ) || defined( _WIN64 )
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(intp) - 1) & ~(sizeof(intp) - 1))
#endif
#define va_argByIndex(ap,t,i) ( *(t *)(ap + i * _INTSIZEOF(t)) )
//-----------------------------------------------------------------------------
// Purpose: construct string helper
//-----------------------------------------------------------------------------
template < typename T >
void ConstructStringVArgsInternal_Impl(T *unicodeOutput, int unicodeBufferSizeInBytes, const T *formatString, int numFormatParameters, va_list argList)
{
// Safety check
if ( unicodeOutput == NULL || unicodeBufferSizeInBytes < 1 )
{
return;
}
if (!formatString)
{
unicodeOutput[0] = 0;
return;
}
int unicodeBufferSize = unicodeBufferSizeInBytes / sizeof(T);
const T *searchPos = formatString;
T *outputPos = unicodeOutput;
//assumes we can't have %s10
//assume both are 0 terminated?
int formatLength = StringFuncs<T>::Length( formatString );
#ifdef PLATFORM_64BITS
// On 64 bits, va_list does not just point to a contiguous blob of parameters
// so extract into an array here.
// TODO: this code is probably fast enough and efficient enough to use
// on all platforms, so consider enabling it everywhere.
T** arguments = (T**)stackalloc( sizeof(T*)*numFormatParameters );
if ( IsPC() )
{
for ( int i = 0; i < numFormatParameters; ++i )
{
arguments[i] = va_arg( argList, T* );
}
}
#endif
#ifdef _DEBUG
int curArgIdx = 0;
#endif
while ( searchPos[0] != '\0' && unicodeBufferSize > 1 )
{
if ( formatLength >= 3 && searchPos[0] == '%' && searchPos[1] == 's' )
{
//this is an escape sequence - %s1, %s2 etc, up to %s9
int argindex = ( searchPos[2] ) - '0' - 1;
if ( argindex < 0 || argindex > 9 )
{
Warning( "Bad format string in CLocalizeStringTable::ConstructString\n" );
*outputPos = '\0';
return;
}
if ( argindex < numFormatParameters )
{
T *param = NULL;
if ( IsPC() )
{
#if !defined( _PS3 )
#ifdef PLATFORM_64BITS
param = arguments[ argindex ];
#else
param = va_argByIndex( argList, T *, argindex );
#endif
#endif // !_PS3
}
else
{
// X360TBD: convert string to new %var% format if this assert hits
Assert( argindex == curArgIdx++ );
param = va_arg( argList, T* );
}
if (!param)
{
Assert( !("ConstructStringVArgsInternal_Impl() - Found a %s# escape sequence whose index was more than the number of args.") );
*outputPos = '\0';
return;
}
int paramSize = StringFuncs<T>::Length(param);
if (paramSize >= unicodeBufferSize)
{
paramSize = unicodeBufferSize - 1;
}
memcpy(outputPos, param, paramSize * sizeof(T));
unicodeBufferSize -= paramSize;
outputPos += paramSize;
searchPos += 3;
formatLength -= 3;
}
else
{
//copy it over, char by char
*outputPos = *searchPos;
outputPos++;
unicodeBufferSize--;
searchPos++;
formatLength--;
}
}
else
{
//copy it over, char by char
*outputPos = *searchPos;
outputPos++;
unicodeBufferSize--;
searchPos++;
formatLength--;
}
}
// ensure null termination
Assert( outputPos - unicodeOutput < unicodeBufferSizeInBytes/sizeof(T) );
*outputPos = L'\0';
}
void CLocalize::ConstructStringVArgsInternal(char *unicodeOutput, int unicodeBufferSizeInBytes, const char *formatString, int numFormatParameters, va_list argList)
{
ConstructStringVArgsInternal_Impl<char>( unicodeOutput, unicodeBufferSizeInBytes, formatString, numFormatParameters, argList );
}
void CLocalize::ConstructStringVArgsInternal(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const wchar_t *formatString, int numFormatParameters, va_list argList)
{
ConstructStringVArgsInternal_Impl<wchar_t>( unicodeOutput, unicodeBufferSizeInBytes, formatString, numFormatParameters, argList );
}
//-----------------------------------------------------------------------------
// Purpose: construct string helper
//-----------------------------------------------------------------------------
template < typename T >
const T *GetTypedKeyValuesString( KeyValues *pKeyValues, const char *pKeyName );
template < >
const char *GetTypedKeyValuesString<char>( KeyValues *pKeyValues, const char *pKeyName )
{
return pKeyValues->GetString( pKeyName, "[unknown]" );
}
template < >
const wchar_t *GetTypedKeyValuesString<wchar_t>( KeyValues *pKeyValues, const char *pKeyName )
{
return pKeyValues->GetWString( pKeyName, L"[unknown]" );
}
template < typename T >
void ConstructStringKeyValuesInternal_Impl( T *unicodeOutput, int unicodeBufferSizeInBytes, const T *formatString, KeyValues *localizationVariables )
{
T *outputPos = unicodeOutput;
//assumes we can't have %s10
//assume both are 0 terminated?
int unicodeBufferSize = unicodeBufferSizeInBytes / sizeof(T);
while ( *formatString != '\0' && unicodeBufferSize > 0 )
{
bool shouldAdvance = true;
if ( *formatString == '%' )
{
// this is an escape sequence that specifies a variable name
if ( formatString[1] == 's' && formatString[2] >= '0' && formatString[2] <= '9' )
{
// old style escape sequence, ignore
}
else if ( formatString[1] == '%' )
{
// just a '%' char, just write the second one
formatString++;
}
else if ( localizationVariables )
{
// get out the variable name
const T *varStart = formatString + 1;
const T *varEnd = StringFuncs<T>::FindChar( varStart, '%' );
if ( varEnd && *varEnd == '%' )
{
shouldAdvance = false;
// assume variable names must be ascii, do a quick convert
char variableName[32];
char *vset = variableName;
for ( const T *pws = varStart; pws < varEnd && (vset < variableName + sizeof(variableName) - 1); ++pws, ++vset )
{
*vset = (char)*pws;
}
*vset = 0;
// look up the variable name
const T *value = GetTypedKeyValuesString<T>( localizationVariables, variableName );
int paramSize = StringFuncs<T>::Length( value );
if (paramSize >= unicodeBufferSize)
{
paramSize = MAX( 0, unicodeBufferSize - 1 );
}
StringFuncs<T>::Copy( outputPos, value, paramSize );
unicodeBufferSize -= paramSize;
outputPos += paramSize;
formatString = varEnd + 1;
}
}
}
if (shouldAdvance)
{
//copy it over, char by char
*outputPos = *formatString;
outputPos++;
unicodeBufferSize--;
formatString++;
}
}
// ensure null termination
*outputPos = '\0';
}
void CLocalize::ConstructStringKeyValuesInternal(char *unicodeOutput, int unicodeBufferSizeInBytes, const char *formatString, KeyValues *localizationVariables)
{
ConstructStringKeyValuesInternal_Impl<char>( unicodeOutput, unicodeBufferSizeInBytes, formatString, localizationVariables );
}
void CLocalize::ConstructStringKeyValuesInternal(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const wchar_t *formatString, KeyValues *localizationVariables)
{
ConstructStringKeyValuesInternal_Impl<wchar_t>( unicodeOutput, unicodeBufferSizeInBytes, formatString, localizationVariables );
}