# HG changeset patch
# User Nicolas Hake <isilkor@openclonk.org>
# Date 1258041457 -3600
# Node ID 82ddc1a193c04d2b43e84f2215ff06469dca7aee
# Parent  64b4e34551ab287a947a23d995deac7d5a8fdb74
Script: Expose string tables at runtime

Use Translate(string) to access the translation

diff -r 64b4e34551ab -r 82ddc1a193c0 docs/sdk/script/fn/Translate.xml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/sdk/script/fn/Translate.xml	Thu Nov 12 16:57:37 2009 +0100
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
+<!DOCTYPE funcs SYSTEM "../../../clonk.dtd">
+<?xml-stylesheet type="text/xsl" href="../../../clonk.xsl"?>
+<funcs>
+  <func>
+    <title>Translate</title>
+    <category>Script</category><subcat>Strings</subcat>
+    <version>4.10.0.0 OC</version>
+    <syntax>
+      <rtype>string</rtype>
+      <params>
+        <param><type>string</type><name>text</name><desc>Zeichenkette, die übersetzt werden soll</desc></param>
+      </params>
+    </syntax>
+    <desc>
+      Schlägt einen Text in der <emlink href="lang.html">StringTbl??.txt</emlink> des aufrufenden Skripts nach, und gibt die Übersetzung zurück.
+    </desc>
+    <examples>
+      <example>
+        <code><funclink>Message</funclink>(Translate("I need a better example"));</code>
+        <text>Gibt "Ich benötige ein besseres Beispiel" auf dem Bildschirm aus.</text>
+      </example>
+    </examples>
+  </func>
+  <author>Isilkor</author><date>November 2009</date>
+</funcs>
diff -r 64b4e34551ab -r 82ddc1a193c0 src/c4group/C4ComponentHost.h
--- a/src/c4group/C4ComponentHost.h	Thu Nov 12 14:14:08 2009 +0100
+++ b/src/c4group/C4ComponentHost.h	Thu Nov 12 16:57:37 2009 +0100
@@ -33,8 +33,8 @@
 		void Default();
 		void Clear();
 		void Open();
-		const char *GetData() { return Data.getData(); }
-		size_t GetDataSize() { return Data.getLength(); }
+		const char *GetData() const { return Data.getData(); }
+		size_t GetDataSize() const { return Data.getLength(); }
 		virtual void Close();
 		bool Load(const char *szName, C4Group &hGroup, const char *szFilename, const char *szLanguage=NULL);
 		bool Load(const char *szName, C4GroupSet &hGroupSet, const char *szFilename, const char *szLanguage=NULL);
diff -r 64b4e34551ab -r 82ddc1a193c0 src/c4group/C4LangStringTable.cpp
--- a/src/c4group/C4LangStringTable.cpp	Thu Nov 12 14:14:08 2009 +0100
+++ b/src/c4group/C4LangStringTable.cpp	Thu Nov 12 16:57:37 2009 +0100
@@ -24,14 +24,80 @@
 #include "C4LangStringTable.h"
 #include "C4InputValidation.h"
 
-struct C4StringTableEntry
+C4LangStringTable::C4LangStringTable() : loaded(false) {}
+
+bool C4LangStringTable::HasTranslation(const std::string &text) const
+{
+	if (!loaded)
+		PopulateStringTable();
+	assert(loaded);
+	return strings.find(text) != strings.end();
+}
+
+std::string C4LangStringTable::Translate(const std::string &text) const
+{
+	if (!loaded)
+		PopulateStringTable();
+	assert(loaded);
+	Table::const_iterator it = strings.find(text);
+	if (it == strings.end())
 	{
-	const char *pName, *pEntry;
+		throw NoSuchTranslation(text);
+	}
+	return it->second;
+}
 
-	C4StringTableEntry(const char *pName, const char *pEntry)
-		: pName(pName), pEntry(pEntry) {}
-	};
+void C4LangStringTable::PopulateStringTable() const
+{
+	assert(!loaded);
+	
+	strings.clear();
+	std::string key, value;
 
+	// if we return after this, the string table either is loaded, or there was an error,
+	// in which case we don't want to try to reload it on access
+	loaded = true;
+
+	// read table
+	const char *data = GetData();
+	if (!data || !*data)
+		return;
+
+	enum { PSTS_Key, PSTS_Val } state = PSTS_Key;
+	do
+	{
+		if (state == PSTS_Key)
+		{
+			if (*data == '=')
+			{
+				state = PSTS_Val;
+			}
+			else if (*data == '\0' || *data == '\n' || *data == '\r')
+			{
+				if (!key.empty())
+					LogF("%s: string table entry without a value: \"%s\"", GetFilePath()[0] ? GetFilePath() : "<unknown>", key.c_str());
+				key.clear();
+			}
+			else
+			{
+				key.push_back(*data);
+			}
+		}
+		else
+		{
+			if (*data == '\0' || *data == '\n' || *data == '\r')
+			{
+				strings.insert(std::make_pair(key, value));
+				key.clear(); value.clear();
+				state = PSTS_Key;
+			}
+			else
+			{
+				value.push_back(*data);
+			}
+		}
+	} while (*data++);
+}
 
 void C4LangStringTable::ReplaceStrings(const StdStrBuf &rBuf, StdStrBuf &rTarget, const char *szParentFilePath)
 	{
@@ -42,38 +108,9 @@
 	// grab char ptr from buf
 	const char *Data = rBuf.getData();
 
-	// string table
-	std::vector<C4StringTableEntry> Entries;
-
-	// read string table
-	char *pStrTblBuf = NULL;
-	if(GetData())
-	{
-		// copy data
-		pStrTblBuf = new char [GetDataSize() + 1];
-		SCopy(GetData(), pStrTblBuf, GetDataSize());
-		// find entries
-		const char *pLine = pStrTblBuf;
-		bool found_eq = false;
-		for(char *pPos = pStrTblBuf; *pPos; pPos++)
-			if(*pPos == '\n' || *pPos == '\r')
-			{
-				found_eq = false;
-				*pPos = '\0'; pLine = pPos + 1;
-			}
-			else if(*pPos == '=' && !found_eq)
-			{
-				*pPos = '\0';
-				// We found an '=' sign, so parse everything to end of line from now on, ignoring more '=' signs. Bug #2327.
-				found_eq = true;
-				// add entry
-				Entries.push_back(C4StringTableEntry(pLine, pPos + 1));
-			}
-	}
-
 	// Find Replace Positions
 	int iScriptLen = SLen(Data);
-	struct RP { const char *Pos, *String; unsigned int Len; RP *Next; } *pRPList = NULL, *pRPListEnd = NULL;
+	struct RP { const char *Pos; std::string String; unsigned int Len; RP *Next; } *pRPList = NULL, *pRPListEnd = NULL;
 	for(const char *pPos = SSearch(Data, "$"); pPos; pPos = SSearch(pPos, "$"))
 	{
 		// Get name
@@ -90,25 +127,24 @@
 				break;
 		if(*pPos2) continue;
 		// check termination
-		// search in string table
-		const char *pStrTblEntry = NULL;
-		for(unsigned int i = 0; i < Entries.size(); i++)
-			if(SEqual(szStringName, Entries[i].pName))
-			{
-				pStrTblEntry = Entries[i].pEntry; break;
-			}
-		// found?
-		if(!pStrTblEntry)
-			{ LogF("%s: string table entry not found: \"%s\"", FilePath[0] ? FilePath : (szParentFilePath ? szParentFilePath : "Unknown"), szStringName); continue; }
-		// add new replace-position entry
-		RP *pnRP = new RP;
-		pnRP->Pos = pPos - SLen(szStringName) - 2;
-		pnRP->String = pStrTblEntry;
-		pnRP->Len = SLen(szStringName) + 2;
-		pnRP->Next = NULL;
-		pRPListEnd = (pRPListEnd ? pRPListEnd->Next : pRPList) = pnRP;
-		// calculate new script length
-		iScriptLen += SLen(pStrTblEntry) - pnRP->Len;
+		try
+		{
+			// search in string table
+			std::string pStrTblEntry = Translate(szStringName);
+			// add new replace-position entry
+			RP *pnRP = new RP;
+			pnRP->Pos = pPos - SLen(szStringName) - 2;
+			pnRP->String = pStrTblEntry;
+			pnRP->Len = SLen(szStringName) + 2;
+			pnRP->Next = NULL;
+			pRPListEnd = (pRPListEnd ? pRPListEnd->Next : pRPList) = pnRP;
+			// calculate new script length
+			iScriptLen += pStrTblEntry.size() - pnRP->Len;
+		}
+		catch (NoSuchTranslation &)
+		{
+			LogF("%s: string table entry not found: \"%s\"", GetFilePath()[0] ? GetFilePath() : "<unknown>", szStringName);
+		}
 	}
 	// Alloc new Buffer
 	char *pNewBuf;
@@ -123,7 +159,7 @@
 		SCopy(pRPos, pWPos, pRPPos->Pos - pRPos);
 		pWPos += pRPPos->Pos - pRPos;
 		// copy string
-		SCopyUntil(pRPPos->String, pWPos, '\n');
+		SCopyUntil(pRPPos->String.c_str(), pWPos, '\n');
 		SReplaceChar(pWPos, '\r', '\0');
 		// advance
 		pRPos = pRPPos->Pos + pRPPos->Len;
@@ -138,9 +174,6 @@
 		delete pRP;
 	}
 
-	// free buffer
-	if(pStrTblBuf) delete [] pStrTblBuf;
-
 	// assign this buf
 	rTarget.Clear();
 	rTarget.Take(sNewBuf);
diff -r 64b4e34551ab -r 82ddc1a193c0 src/c4group/C4LangStringTable.h
--- a/src/c4group/C4LangStringTable.h	Thu Nov 12 14:14:08 2009 +0100
+++ b/src/c4group/C4LangStringTable.h	Thu Nov 12 16:57:37 2009 +0100
@@ -22,13 +22,30 @@
 
 #include "C4ComponentHost.h"
 
+#include <map>
+#include <string>
+
 class C4LangStringTable : public C4ComponentHost
+{
+	// Contains the localization string->string mapping. Populated lazily from PopulateStringTable, thus mutable.
+	typedef std::map<std::string, std::string> Table;
+	mutable Table strings;
+	mutable bool loaded;
+	void PopulateStringTable() const;
+public:
+	C4LangStringTable();
+	std::string Translate(const std::string &text) const;
+	bool HasTranslation(const std::string &text) const;
+	// do replacement in buffer
+	// if any replacement is done, the buffer will be realloced
+	void ReplaceStrings(StdStrBuf &rBuf);
+	void ReplaceStrings(const StdStrBuf &rBuf, StdStrBuf &rTarget, const char *szParentFilePath = NULL);
+
+	class NoSuchTranslation : public std::runtime_error
 	{
 	public:
-		// do replacement in buffer
-		// if any replacement is done, the buffer will be realloced
-		void ReplaceStrings(StdStrBuf &rBuf);
-		void ReplaceStrings(const StdStrBuf &rBuf, StdStrBuf &rTarget, const char *szParentFilePath = NULL);
+		NoSuchTranslation(const std::string &text) : std::runtime_error("No such translation: \"" + text + "\"") {}
 	};
+};
 
 #endif // INC_C4LangStringTable
diff -r 64b4e34551ab -r 82ddc1a193c0 src/game/script/C4Script.cpp
--- a/src/game/script/C4Script.cpp	Thu Nov 12 14:14:08 2009 +0100
+++ b/src/game/script/C4Script.cpp	Thu Nov 12 16:57:37 2009 +0100
@@ -5362,6 +5362,33 @@
 	return ::Messages.New(iType,sMsg,pObj,iOwner,iOffX,iOffY,(uint32_t)dwClr, idDeco, sPortrait ? sPortrait->GetCStr() : NULL, dwFlags, iHSize);
 	}
 
+static C4String *FnTranslate(C4AulContext *ctx, C4String *text)
+{
+	assert(!ctx->Obj || ctx->Def == ctx->Obj->Def);
+	if (!text || text->GetData().isNull()) return NULL;
+	// Find correct script: containing script unless -> operator used
+	C4AulScript *script = NULL;
+	if (ctx->Obj == ctx->Caller->Obj && ctx->Def == ctx->Caller->Def)
+		script = ctx->Caller->Func->pOrgScript;
+	else
+		script = &ctx->Def->Script;
+	assert(script);
+	try
+	{
+		return ::Strings.RegString(script->Translate(text->GetCStr()).c_str());
+	}
+	catch (C4LangStringTable::NoSuchTranslation &)
+	{
+		DebugLogF("WARNING: Translate: no translation for string \"%s\"", text->GetCStr());
+		// Trace
+		for (C4AulScriptContext *cursor = ctx->Caller; cursor; cursor = cursor->Caller)
+		{
+			cursor->dump(StdStrBuf(" by: "));
+		}
+		return text;
+	}
+}
+
 /*static long FnSetSaturation(C4AulContext *ctx, long s)
 	{
 	return lpDDraw->SetSaturation(BoundBy(s,0l,255l));
@@ -5899,6 +5926,8 @@
 	AddFunc(pEngine, "GetGravity", FnGetGravity);
 	AddFunc(pEngine, "Exit", FnExit);
 	AddFunc(pEngine, "Collect", FnCollect);
+
+	AddFunc(pEngine, "Translate", FnTranslate);
 }
 
 C4ScriptConstDef C4ScriptConstMap[]={
diff -r 64b4e34551ab -r 82ddc1a193c0 src/script/C4Aul.cpp
--- a/src/script/C4Aul.cpp	Thu Nov 12 14:14:08 2009 +0100
+++ b/src/script/C4Aul.cpp	Thu Nov 12 16:57:37 2009 +0100
@@ -27,7 +27,7 @@
 #include <C4Def.h>
 #include <C4Log.h>
 #include <C4Components.h>
-
+#include "C4LangStringTable.h"
 
 C4AulError::C4AulError() {}
 
@@ -183,6 +183,8 @@
 	// prepare include list
 	Includes = NULL;
 	Appends = NULL;
+
+	stringTable = 0;
 	}
 
 
@@ -374,6 +376,26 @@
 
 	}
 
+std::string C4AulScript::Translate(const std::string &text) const
+{
+	const C4AulScript *cursor = this;
+	while (cursor)
+	{
+		try
+		{
+			if (cursor->stringTable)
+				return cursor->stringTable->Translate(text);
+		}
+		catch (C4LangStringTable::NoSuchTranslation &e)
+		{
+			// Ignore, soldier on
+		}
+		// Walk tree structure upwards
+		cursor = cursor->Owner;
+	}
+	throw C4LangStringTable::NoSuchTranslation(text);
+}
+
 void C4AulScriptFunc::CopyBody(C4AulScriptFunc &FromFunc)
 	{
 	// copy some members, that are set before linking
diff -r 64b4e34551ab -r 82ddc1a193c0 src/script/C4Aul.h
--- a/src/script/C4Aul.h	Thu Nov 12 14:14:08 2009 +0100
+++ b/src/script/C4Aul.h	Thu Nov 12 16:57:37 2009 +0100
@@ -34,6 +34,7 @@
 #include <C4Id.h>
 #include <C4Script.h>
 #include <C4StringTable.h>
+#include <string>
 #include <vector>
 
 // debug mode?
@@ -51,6 +52,7 @@
 class C4AulDefFunc;
 class C4AulScript;
 class C4AulScriptEngine;
+class C4LangStringTable;
 
 struct C4AulContext;
 struct C4AulBCC;
@@ -518,6 +520,12 @@
 		friend class C4AulScriptFunc;
 		friend class C4AulScriptEngine;
 		friend class C4AulParseState;
+
+		// Translate a string using the script's lang table
+		std::string Translate(const std::string &text) const;
+
+	protected:
+		C4LangStringTable *stringTable;
 	};
 
 
diff -r 64b4e34551ab -r 82ddc1a193c0 src/script/C4AulExec.cpp
--- a/src/script/C4AulExec.cpp	Thu Nov 12 14:14:08 2009 +0100
+++ b/src/script/C4AulExec.cpp	Thu Nov 12 16:57:37 2009 +0100
@@ -1324,6 +1324,7 @@
 	pScript->Strict = Strict;
 	pScript->Temporary = true;
 	pScript->State = ASS_LINKED;
+	pScript->stringTable = stringTable;
 	if (pObj)
 		{
 		pScript->Def = pObj->Def;
diff -r 64b4e34551ab -r 82ddc1a193c0 src/script/C4ScriptHost.cpp
--- a/src/script/C4ScriptHost.cpp	Thu Nov 12 14:14:08 2009 +0100
+++ b/src/script/C4ScriptHost.cpp	Thu Nov 12 16:57:37 2009 +0100
@@ -40,14 +40,12 @@
 	{
 	C4AulScript::Default();
 	C4ComponentHost::Default();
-	pStringTable = NULL;
 	}
 
 void C4ScriptHost::Clear()
 	{
 	C4AulScript::Clear();
 	C4ComponentHost::Clear();
-	pStringTable = NULL;
 	}
 
 bool C4ScriptHost::Load(const char *szName, C4Group &hGroup, const char *szFilename,
@@ -58,10 +56,10 @@
 	// Base load
 	bool fSuccess = C4ComponentHost::LoadAppend(szName,hGroup,szFilename,szLanguage);
 	// String Table
-	pStringTable = pLocalTable;
+	stringTable = pLocalTable;
 	// load it if specified
-	if (pStringTable && fLoadTable)
-		pStringTable->LoadEx("StringTbl", hGroup, C4CFN_ScriptStringTbl, szLanguage);
+	if (stringTable && fLoadTable)
+		stringTable->LoadEx("StringTbl", hGroup, C4CFN_ScriptStringTbl, szLanguage);
 	// set name
 	ScriptName.Format("%s" DirSep "%s", hGroup.GetFullName().getData(), Filename);
 	// preparse script
@@ -77,9 +75,9 @@
 	Script.Clear();
 
 	// create script
-	if (pStringTable)
+	if (stringTable)
 	{
-		pStringTable->ReplaceStrings(Data, Script, FilePath);
+		stringTable->ReplaceStrings(Data, Script, FilePath);
 	}
 	else
 	{
@@ -141,13 +139,13 @@
 bool C4ScriptHost::ReloadScript(const char *szPath)
   {
   // this?
-  if(SEqualNoCase(szPath, FilePath) || (pStringTable && SEqualNoCase(szPath, pStringTable->GetFilePath())))
+  if(SEqualNoCase(szPath, FilePath) || (stringTable && SEqualNoCase(szPath, stringTable->GetFilePath())))
   {
     // try reload
     char szParentPath[_MAX_PATH + 1]; C4Group ParentGrp;
     if(GetParentPath(szPath, szParentPath))
       if(ParentGrp.Open(szParentPath))
-        if(Load(Name, ParentGrp, Filename, Config.General.Language, NULL, pStringTable))
+        if(Load(Name, ParentGrp, Filename, Config.General.Language, NULL, stringTable))
           return true;
   }
   // call for childs
diff -r 64b4e34551ab -r 82ddc1a193c0 src/script/C4ScriptHost.h
--- a/src/script/C4ScriptHost.h	Thu Nov 12 14:14:08 2009 +0100
+++ b/src/script/C4ScriptHost.h	Thu Nov 12 16:57:37 2009 +0100
@@ -50,7 +50,6 @@
 		int32_t GetControlMethod(int32_t com, int32_t first, int32_t second);
 		C4Value Call(const char *szFunction, C4Object *pObj=0, C4AulParSet *pPars=0, bool fPrivateCall=false, bool fPassError=false);
 	protected:
-		class C4LangStringTable *pStringTable;
 		void SetError(const char *szMessage);
 		void MakeScript();
     bool ReloadScript(const char *szPath);
