summaryrefslogtreecommitdiff
path: root/vgui2/src/LocalizedStringTable.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'vgui2/src/LocalizedStringTable.cpp')
-rw-r--r--vgui2/src/LocalizedStringTable.cpp1060
1 files changed, 1060 insertions, 0 deletions
diff --git a/vgui2/src/LocalizedStringTable.cpp b/vgui2/src/LocalizedStringTable.cpp
new file mode 100644
index 0000000..4e34164
--- /dev/null
+++ b/vgui2/src/LocalizedStringTable.cpp
@@ -0,0 +1,1060 @@
+//========= 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 );
+}