|
|
//======= 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 ); }
|