source-engine/vgui2/src/LocalizedStringTable.cpp

1061 lines
33 KiB
C++
Raw Normal View History

2020-04-22 12:56:21 -04:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#pragma warning( disable: 4018 ) // '==' : signed/unsigned mismatch in rbtree
#if defined( WIN32 ) && !defined( _X360 )
#include <windows.h>
#elif defined( POSIX )
#include <iconv.h>
#endif
#include <wchar.h>
#include "filesystem.h"
#include "vgui_internal.h"
#include "vgui/ILocalize.h"
#include "vgui/ISystem.h"
#include "vgui/ISurface.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"
#if defined( _X360 )
#include "xbox/xbox_win32stubs.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
using namespace vgui;
#define MAX_LOCALIZED_CHARS 4096
//-----------------------------------------------------------------------------
//
// Internal implementation
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Maps token names to localized unicode strings
//-----------------------------------------------------------------------------
class CLocalizedStringTable : public vgui::ILocalize
{
public:
CLocalizedStringTable();
~CLocalizedStringTable();
// adds the contents of a file to the localization table
virtual bool AddFile( const char *fileName, const char *pPathID, bool bIncludeFallbackSearchPaths );
// saves the entire contents of the token tree to the file
bool SaveToFile( const char *fileName );
// adds a single name/unicode string pair to the table
void AddString(const char *tokenName, wchar_t *unicodeString, const char *fileName);
// Finds the localized text for pName
wchar_t *Find(const char *pName);
// finds the index of a token by token name
StringIndex_t FindIndex(const char *pName);
// Remove all strings in the table.
void RemoveAll();
// iteration functions
StringIndex_t GetFirstStringIndex();
// returns the next index, or INVALID_LOCALIZE_STRING_INDEX if no more strings available
StringIndex_t GetNextStringIndex(StringIndex_t index);
// gets the values from the index
const char *GetNameByIndex(StringIndex_t index);
wchar_t *GetValueByIndex(StringIndex_t index);
const char *GetFileNameByIndex(StringIndex_t index);
// sets the value in the index
// has bad memory characteristics, should only be used in the editor
void SetValueByIndex(StringIndex_t index, wchar_t *newValue);
// iterates the filenames
int GetLocalizationFileCount();
const char *GetLocalizationFileName(int index);
// returns whether a file has already been loaded
bool LocalizationFileIsLoaded( const char *name );
const char *FindAsUTF8( const char *pchTokenName );
virtual void ConstructString(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const char *tokenName, KeyValues *localizationVariables);
virtual void ConstructString(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, StringIndex_t unlocalizedTextSymbol, KeyValues *localizationVariables);
private:
// for development only, reloads localization files
virtual void ReloadLocalizationFiles( );
bool AddAllLanguageFiles( const char *baseFileName );
void BuildFastValueLookup();
void DiscardFastValueLookup();
int FindExistingValueIndex( const wchar_t *value );
char m_szLanguage[64];
bool m_bUseOnlyLongestLanguageString;
struct localizedstring_t
{
StringIndex_t nameIndex;
// nameIndex == INVALID_LOCALIZE_STRING_INDEX is used only for searches and implies
// that pszValueString will be used from union fields.
union
{
StringIndex_t valueIndex; // Used when nameIndex != INVALID_LOCALIZE_STRING_INDEX
char const * pszValueString; // Used only if nameIndex == INVALID_LOCALIZE_STRING_INDEX
};
CUtlSymbol filename;
};
// Stores the symbol lookup
CUtlRBTree<localizedstring_t, StringIndex_t> m_Lookup;
// stores the string data
CUtlVector<char> m_Names;
CUtlVector<wchar_t> m_Values;
CUtlSymbol m_CurrentFile;
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;
}
};
CUtlVector< LocalizationFileInfo_t > m_LocalizationFiles;
struct fastvalue_t
{
int valueindex;
const wchar_t *search;
static CLocalizedStringTable *s_pTable;
};
CUtlRBTree< fastvalue_t, int > m_FastValueLookup;
static CLocalizedStringTable *s_pTable;
// 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
CLocalizedStringTable g_StringTable;
// expose the interface
EXPOSE_SINGLE_INTERFACE_GLOBALVAR_WITH_NAMESPACE(CLocalizedStringTable, vgui::, ILocalize, VGUI_LOCALIZE_INTERFACE_VERSION, g_StringTable);
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CLocalizedStringTable::CLocalizedStringTable() :
m_Lookup( 0, 0, SymLess ), m_Names( 1024 ), m_Values( 2048 ), m_FastValueLookup( 0, 0, FastValueLessFunc )
{
m_bUseOnlyLongestLanguageString = false;
}
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CLocalizedStringTable::~CLocalizedStringTable()
{
m_Names.Purge();
m_Values.Purge();
m_LocalizationFiles.Purge();
}
//-----------------------------------------------------------------------------
// Purpose: Adds the contents of a file
//-----------------------------------------------------------------------------
bool CLocalizedStringTable::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;
char language[MAX_LANGUAGE_NAME_LENGTH];
char fileName[MAX_PATH];
int offs = 0;
bool success = false;
memset( language, 0, sizeof(language) );
Q_strncpy( fileName, szFileName, sizeof( fileName ) );
// Lowercase the *relative* portion of the filename,
// in case people look for "Resource/file.txt" etc. We always
// use lowercase filenames for files in the game filesystem.
V_strlower( fileName );
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;
strncpy(fileName, szFileName, offs);
fileName[offs] = 0;
if ( vgui::g_pSystem->CommandLineParamExists("-all_languages") )
{
m_bUseOnlyLongestLanguageString = true;
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;
if ( IsPC() )
{
bValid = vgui::g_pSystem->GetRegistryString( "HKEY_CURRENT_USER\\Software\\Valve\\Source\\Language", language, sizeof(language)-1 );
}
else
{
Q_strncpy( language, XBX_GetLanguageString(), sizeof( language ) );
bValid = true;
}
// 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 = fileName;
search.symPathID = pPathID ? pPathID : "";
search.bIncludeFallbacks = bIncludeFallbackSearchPaths;
int lfc = m_LocalizationFiles.Count();
for ( int lf = 0; lf < lfc; ++lf )
{
LocalizationFileInfo_t& entry = m_LocalizationFiles[ lf ];
if ( !Q_stricmp( entry.symName.String(), fileName ) )
{
m_LocalizationFiles.Remove( lf );
break;
}
}
m_LocalizationFiles.AddToTail( search );
// This will give us a list of paths from highest to lowest precedence: e.g.:
// for "GAME" when running -game episodic, it'll show:
// "basedir/episodic/;basedir/hl2"
// We do this manually instead of just asking for the first match to support bIncludeFallbackSearchPaths
char searchPaths[ MAX_PATH*50 ] = { 0 }; // allow for 50 search paths
Verify( g_pFullFileSystem->GetSearchPath( pPathID, true, searchPaths, sizeof( searchPaths ) ) < sizeof(searchPaths) );
CUtlSymbolTable pathStrings;
CUtlVector< CUtlSymbol > searchList;
bool bIsFullPath = false;
if ( V_IsAbsolutePath( fileName ) )
{
bIsFullPath = true;
CUtlSymbol sym = pathStrings.AddString( fileName );
searchList.AddToHead( sym );
}
else
{
// We want to walk them in reverse order so newer files are "overrides" for older ones, so we add them to a list in reverse order
for ( char *path = strtok( searchPaths, ";" ); path; path = strtok( NULL, ";" ) )
{
if ( IsX360() && ( g_pFullFileSystem->GetDVDMode() == DVDMODE_STRICT ) && !V_stristr( path, ".zip" ) )
{
// only want zip paths
continue;
}
char fullpath[MAX_PATH];
V_strcpy_safe( fullpath, path );
V_AppendSlash( fullpath, sizeof(fullpath) );
V_strcat_safe( fullpath, fileName );
Q_FixSlashes( fullpath );
//Q_strlower( fullpath ); // NO! This screws up Linux
CUtlSymbol sym = pathStrings.AddString( fullpath );
// With bIncludeFallbackSearchPaths we iterate overriding as we go, so push them in reverse order so the
// highest precendence search paths have the highest precendence. Otherwise push them in order, as we'll
// only process the first one.
if ( !bIncludeFallbackSearchPaths )
{
searchList.AddToTail( sym );
}
else
{
searchList.AddToHead( sym );
}
}
}
bool first = true;
bool bLoadedAtLeastOne = false;
for ( int sp = 0; sp < searchList.Count(); ++sp )
{
const char *fullpath = pathStrings.String( searchList[ sp ] );
// parse out the file
FileHandle_t file = g_pFullFileSystem->Open( fullpath, "rb" );
if (!file)
{
continue;
}
if ( first )
{
first = false;
}
else if ( !bIncludeFallbackSearchPaths )
{
g_pFullFileSystem->Close(file);
break;
}
bLoadedAtLeastOne = true;
// this is an optimization so that the filename string doesn't have to get converted to a symbol for each key/value
m_CurrentFile = fullpath;
// read into a memory block
int fileSize = g_pFullFileSystem->Size(file);
int bufferSize = g_pFullFileSystem->GetOptimalReadSize( file, fileSize + sizeof(ucs2) );
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", fullpath );
g_pFullFileSystem->FreeOptimalReadBuffer( memBlock );
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 ( strstr(fullpath, "_english.txt") )
{
bEnglishFile = true;
}
bool spew = false;
if ( CommandLine()->FindParm( "-ccsyntax" ) )
{
spew = true;
}
BuildFastValueLookup();
states_e state = STATE_BASE;
while (1)
{
// read the key and the value
ucs2 keytoken[128];
ucs2 *pchNewdata = ReadUnicodeToken(data, keytoken, ARRAYSIZE(keytoken), bQuoted);
if (!keytoken[0])
break; // we've hit the null terminator
// convert the token to a string
char key[128];
V_UCS2ToUTF8(keytoken, key, static_cast<int>( sizeof(key) ));
data = pchNewdata;
// 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 ];
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];
V_UCS2ToUTF8(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);
if ( !bQuoted && conditional[0] == L'[' && conditional[1] == L'$' ) // wcsstr( conditional, L"[$" ) )
{
// Evaluate the conditional tag
char cond[MAX_LOCALIZED_CHARS];
V_UCS2ToUTF8(conditional, cond, sizeof(cond));
bAccepted = EvaluateConditional( cond );
// Robin: HACK: Cheesy support for language-based filtering. Main has much better
// support for this, in all KV files, so this will be obsoleted in post-TF2 products.
char *pszKey = &cond[2];
bool bNot = false;
if ( pszKey[0] == '!' )
{
bNot = true;
pszKey++;
}
// Trim off the ]
if ( pszKey && pszKey[0] )
{
pszKey[ V_strlen(pszKey)-1 ] = '\0';
if ( !V_stricmp( pszKey, "ENGLISH" ) ||
!V_stricmp( pszKey, "JAPANESE" ) ||
!V_stricmp( pszKey, "GERMAN" ) ||
!V_stricmp( pszKey, "FRENCH" ) ||
!V_stricmp( pszKey, "SPANISH" ) ||
!V_stricmp( pszKey, "ITALIAN" ) ||
!V_stricmp( pszKey, "KOREAN" ) ||
!V_stricmp( pszKey, "TCHINESE" ) ||
!V_stricmp( pszKey, "PORTUGUESE" ) ||
!V_stricmp( pszKey, "SCHINESE" ) ||
!V_stricmp( pszKey, "POLISH" ) ||
!V_stricmp( pszKey, "RUSSIAN" ) )
{
// the language symbols are true if we are in that language
// english is assumed when no language is present
const char *pLanguageString;
#ifdef _X360
pLanguageString = XBX_GetLanguageString();
#else
static ConVarRef cl_language( "cl_language" );
pLanguageString = cl_language.GetString();
#endif
if ( !pLanguageString || !pLanguageString[0] )
{
pLanguageString = "english";
}
bool bMatched = ( !V_stricmp( pszKey, pLanguageString ) );
bAccepted = (bMatched && !bNot) || (!bMatched && bNot);
}
}
data = tempData;
}
if ( bAccepted )
{
wchar_t fullString[MAX_LOCALIZED_CHARS+1];
int i = 0;
for ( i = 0; i < MAX_LOCALIZED_CHARS && valuetoken[i] != 0; i++ )
fullString[i] = valuetoken[i]; // explode the ucs2 into a wchar_t wide buffer
fullString[i] = 0;
// add the string to the table
AddString(key, fullString, NULL);
}
}
}
}
}
g_pFullFileSystem->FreeOptimalReadBuffer( memBlock );
}
if ( !bLoadedAtLeastOne )
{
Warning("CLocalizedStringTable::AddFile() failed to load file \"%s\".\n", szFileName );
}
DiscardFastValueLookup();
m_CurrentFile = UTL_INVAL_SYMBOL;
return bLoadedAtLeastOne;
}
//-----------------------------------------------------------------------------
// Purpose: Load all the localized language strings, and uses the longest string from each language
//-----------------------------------------------------------------------------
bool CLocalizedStringTable::AddAllLanguageFiles( const char *baseFileName )
{
bool success = true;
// work out the path the files are in
char szFilePath[MAX_PATH];
Q_strncpy( szFilePath, baseFileName, sizeof(szFilePath) );
char *lastSlash = strrchr( szFilePath, '\\' );
if (!lastSlash)
{
lastSlash = strrchr( szFilePath, '/' );
}
if (lastSlash)
{
lastSlash[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];
Q_snprintf( szFile, sizeof(szFile), "%s%s", szFilePath, file );
// add the file
success &= AddFile( szFile, NULL, true );
// next file
file = g_pFullFileSystem->FindNext( hFind );
}
g_pFullFileSystem->FindClose( hFind );
return success;
}
//-----------------------------------------------------------------------------
// Purpose: saves the entire contents of the token tree to the file
//-----------------------------------------------------------------------------
bool CLocalizedStringTable::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 wchar_t unicodeString[1024];
int strLength = ConvertANSIToUnicode(startStr, unicodeString, sizeof(unicodeString));
if (!strLength)
return false;
g_pFullFileSystem->Write(unicodeString, wcslen( unicodeString ) * sizeof(wchar_t), file);
// convert our spacing characters to unicode
// wchar_t unicodeSpace = L' ';
wchar_t unicodeQuote = L'\"';
wchar_t unicodeCR = L'\r';
wchar_t unicodeNewline = L'\n';
wchar_t unicodeTab = L'\t';
// write out all the key/value pairs
for (StringIndex_t idx = GetFirstStringIndex(); idx != INVALID_LOCALIZE_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
ConvertANSIToUnicode(name, unicodeString, sizeof(unicodeString));
g_pFullFileSystem->Write(&unicodeTab, sizeof(wchar_t), file);
// write out
g_pFullFileSystem->Write(&unicodeQuote, sizeof(wchar_t), file);
g_pFullFileSystem->Write(unicodeString, wcslen( unicodeString ) * sizeof(wchar_t), file);
g_pFullFileSystem->Write(&unicodeQuote, sizeof(wchar_t), file);
g_pFullFileSystem->Write(&unicodeTab, sizeof(wchar_t), file);
g_pFullFileSystem->Write(&unicodeTab, sizeof(wchar_t), file);
g_pFullFileSystem->Write(&unicodeQuote, sizeof(wchar_t), file);
g_pFullFileSystem->Write(value, wcslen(value) * sizeof(wchar_t), file);
g_pFullFileSystem->Write(&unicodeQuote, sizeof(wchar_t), file);
g_pFullFileSystem->Write(&unicodeCR, sizeof(wchar_t), file);
g_pFullFileSystem->Write(&unicodeNewline, sizeof(wchar_t), file);
}
// write end string
strLength = ConvertANSIToUnicode(endStr, unicodeString, sizeof(unicodeString));
g_pFullFileSystem->Write(unicodeString, strLength * sizeof(wchar_t), file);
g_pFullFileSystem->Close(file);
return true;
}
//-----------------------------------------------------------------------------
// Purpose: for development, reloads localization files
//-----------------------------------------------------------------------------
void CLocalizedStringTable::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 CLocalizedStringTable::SymLess(localizedstring_t const &i1, localizedstring_t const &i2)
{
const char *str1 = (i1.nameIndex == INVALID_LOCALIZE_STRING_INDEX) ? i1.pszValueString :
&g_StringTable.m_Names[i1.nameIndex];
const char *str2 = (i2.nameIndex == INVALID_LOCALIZE_STRING_INDEX) ? i2.pszValueString :
&g_StringTable.m_Names[i2.nameIndex];
return stricmp(str1, str2) < 0;
}
//-----------------------------------------------------------------------------
// Purpose: Finds a string in the table
//-----------------------------------------------------------------------------
wchar_t *CLocalizedStringTable::Find(const char *pName)
{
StringIndex_t idx = FindIndex(pName);
if (idx == INVALID_LOCALIZE_STRING_INDEX)
return NULL;
return &m_Values[m_Lookup[idx].valueIndex];
}
//-----------------------------------------------------------------------------
// Purpose: Finds a string in the table
//-----------------------------------------------------------------------------
const char *CLocalizedStringTable::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;
}
//-----------------------------------------------------------------------------
// Purpose: finds the index of a token by token name
//-----------------------------------------------------------------------------
StringIndex_t CLocalizedStringTable::FindIndex(const char *pName)
{
if (!pName)
return NULL;
// 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 = INVALID_LOCALIZE_STRING_INDEX;
invalidItem.pszValueString = pName;
return m_Lookup.Find( invalidItem );
}
//-----------------------------------------------------------------------------
// Finds and/or creates a symbol based on the string
//-----------------------------------------------------------------------------
void CLocalizedStringTable::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 == INVALID_LOCALIZE_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
StringIndex_t stridx = FindIndex( pString );
localizedstring_t item;
item.nameIndex = stridx;
if ( stridx == INVALID_LOCALIZE_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
int newWide, oldWide, tall;
vgui::g_pSurface->GetTextSize( 1, newValue, newWide, tall );
vgui::g_pSurface->GetTextSize( 1, oldValue, oldWide, tall );
// 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;
}
}
//-----------------------------------------------------------------------------
// Remove all symbols in the table.
//-----------------------------------------------------------------------------
void CLocalizedStringTable::RemoveAll()
{
m_Lookup.RemoveAll();
m_Names.RemoveAll();
m_Values.RemoveAll();
m_LocalizationFiles.RemoveAll();
}
//-----------------------------------------------------------------------------
// Purpose: iteration functions
//-----------------------------------------------------------------------------
StringIndex_t CLocalizedStringTable::GetFirstStringIndex()
{
return m_Lookup.FirstInorder();
}
//-----------------------------------------------------------------------------
// Purpose: returns the next index, or INVALID_LOCALIZE_STRING_INDEX if no more strings available
//-----------------------------------------------------------------------------
StringIndex_t CLocalizedStringTable::GetNextStringIndex(StringIndex_t index)
{
StringIndex_t idx = m_Lookup.NextInorder(index);
if (idx == m_Lookup.InvalidIndex())
return INVALID_LOCALIZE_STRING_INDEX;
return idx;
}
//-----------------------------------------------------------------------------
// Purpose: gets the name of the localization string by index
//-----------------------------------------------------------------------------
const char *CLocalizedStringTable::GetNameByIndex(StringIndex_t index)
{
localizedstring_t &lstr = m_Lookup[index];
return &m_Names[lstr.nameIndex];
}
//-----------------------------------------------------------------------------
// Purpose: gets the localized string value by index
//-----------------------------------------------------------------------------
wchar_t *CLocalizedStringTable::GetValueByIndex(StringIndex_t index)
{
if (index == INVALID_LOCALIZE_STRING_INDEX)
return NULL;
localizedstring_t &lstr = m_Lookup[index];
return &m_Values[lstr.valueIndex];
}
CLocalizedStringTable *CLocalizedStringTable::s_pTable = NULL;
bool CLocalizedStringTable::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 CLocalizedStringTable::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 CLocalizedStringTable::DiscardFastValueLookup()
{
m_FastValueLookup.RemoveAll();
s_pTable = NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CLocalizedStringTable::FindExistingValueIndex( const wchar_t *value )
{
if ( !s_pTable )
return INVALID_LOCALIZE_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 INVALID_LOCALIZE_STRING_INDEX;
}
//-----------------------------------------------------------------------------
// Purpose: returns which file a string was loaded from
//-----------------------------------------------------------------------------
const char *CLocalizedStringTable::GetFileNameByIndex(StringIndex_t index)
{
localizedstring_t &lstr = m_Lookup[index];
return lstr.filename.String();
}
//-----------------------------------------------------------------------------
// Purpose: sets the value in the index
//-----------------------------------------------------------------------------
void CLocalizedStringTable::SetValueByIndex(StringIndex_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);
}
}
//-----------------------------------------------------------------------------
// Purpose: returns number of localization files currently loaded
//-----------------------------------------------------------------------------
int CLocalizedStringTable::GetLocalizationFileCount()
{
return m_LocalizationFiles.Count();
}
//-----------------------------------------------------------------------------
// Purpose: returns localization filename by index
//-----------------------------------------------------------------------------
const char *CLocalizedStringTable::GetLocalizationFileName(int index)
{
return m_LocalizationFiles[index].symName.String();
}
//-----------------------------------------------------------------------------
// Purpose: returns whether a localization file has been loaded already
//-----------------------------------------------------------------------------
bool CLocalizedStringTable::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: Constructs a string, inserting variables where necessary
//-----------------------------------------------------------------------------
void CLocalizedStringTable::ConstructString(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const char *tokenName, KeyValues *localizationVariables)
{
StringIndex_t index = FindIndex(tokenName);
if (index != INVALID_LOCALIZE_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 CLocalizedStringTable::ConstructString(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, StringIndex_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;
}
ILocalize::ConstructString( unicodeOutput, unicodeBufferSizeInBytes, searchPos, localizationVariables );
}