Skip to content

Commit 90c8287

Browse files
committed
Implement Localization support
1 parent ffcde8e commit 90c8287

23 files changed

+989
-31
lines changed

Client/mods/deathmatch/logic/CDownloadableResource.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class CDownloadableResource
3333
RESOURCE_FILE_TYPE_CLIENT_CONFIG,
3434
RESOURCE_FILE_TYPE_HTML,
3535
RESOURCE_FILE_TYPE_CLIENT_FILE,
36+
RESOURCE_FILE_TYPE_TRANSLATION,
3637
};
3738

3839
public:

Client/mods/deathmatch/logic/CPacketHandler.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <game/CBuildingRemoval.h>
2323
#include "net/SyncStructures.h"
2424
#include "CServerInfo.h"
25+
#include "CResourceTranslationItem.h"
2526

2627
using std::list;
2728

@@ -5228,6 +5229,22 @@ void CPacketHandler::Packet_ResourceStart(NetBitStreamInterface& bitStream)
52285229
pDownloadableResource = pResource->AddConfigFile(szParsedChunkData, uiDownloadSize, chunkChecksum);
52295230

52305231
break;
5232+
case CDownloadableResource::RESOURCE_FILE_TYPE_TRANSLATION:
5233+
{
5234+
bool isPrimary = bitStream.ReadBit();
5235+
pDownloadableResource = pResource->AddResourceFile(CDownloadableResource::RESOURCE_FILE_TYPE_TRANSLATION,
5236+
szParsedChunkData, uiDownloadSize, chunkChecksum, true);
5237+
if (pDownloadableResource && isPrimary)
5238+
{
5239+
CResourceTranslationItem* translationItem = dynamic_cast<CResourceTranslationItem*>(pDownloadableResource);
5240+
if (translationItem)
5241+
{
5242+
pResource->SetTranslationPrimary(translationItem->GetLanguage());
5243+
}
5244+
}
5245+
5246+
break;
5247+
}
52315248
default:
52325249

52335250
break;

Client/mods/deathmatch/logic/CResource.cpp

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#define DECLARE_PROFILER_SECTION_CResource
1414
#include "profiler/SharedUtil.Profiler.h"
1515
#include "CServerIdManager.h"
16+
#include "CResourceTranslationItem.h"
1617

1718
using namespace std;
1819

@@ -83,6 +84,8 @@ CResource::CResource(unsigned short usNetID, const char* szResourceName, CClient
8384
// Move this after the CreateVirtualMachine line and heads will roll
8485
m_bOOPEnabled = bEnableOOP;
8586
m_iDownloadPriorityGroup = 0;
87+
88+
m_translationManager = std::make_unique<CResourceTranslationManager>(m_strResourceName.c_str());
8689

8790
m_pLuaVM = m_pLuaManager->CreateVirtualMachine(this, bEnableOOP);
8891
if (m_pLuaVM)
@@ -183,7 +186,18 @@ CDownloadableResource* CResource::AddResourceFile(CDownloadableResource::eResour
183186
return NULL;
184187
}
185188

186-
CResourceFile* pResourceFile = new CResourceFile(this, resourceType, szFileName, strBuffer, uiDownloadSize, serverChecksum, bAutoDownload);
189+
CResourceFile* pResourceFile = nullptr;
190+
191+
if (resourceType == CDownloadableResource::RESOURCE_FILE_TYPE_TRANSLATION)
192+
{
193+
bool isPrimary = m_translationPrimaryFlags.find(szFileName) != m_translationPrimaryFlags.end();
194+
pResourceFile = new CResourceTranslationItem(this, szFileName, strBuffer, uiDownloadSize, serverChecksum, isPrimary);
195+
}
196+
else
197+
{
198+
pResourceFile = new CResourceFile(this, resourceType, szFileName, strBuffer, uiDownloadSize, serverChecksum, bAutoDownload);
199+
}
200+
187201
if (pResourceFile)
188202
{
189203
m_ResourceFiles.push_back(pResourceFile);
@@ -278,6 +292,8 @@ void CResource::Load()
278292
}
279293
}
280294

295+
LoadTranslations();
296+
281297
for (auto& list = m_NoClientCacheScriptList; !list.empty(); list.pop_front())
282298
{
283299
DECLARE_PROFILER_SECTION(OnPreLoadNoClientCacheScript)
@@ -520,3 +536,25 @@ void CResource::HandleDownloadedFileTrouble(CResourceFile* pResourceFile, bool b
520536
g_pClientGame->TellServerSomethingImportant(bScript ? 1002 : 1013, strMessage, 4);
521537
g_pCore->GetConsole()->Printf("Download error: %s", *strMessage);
522538
}
539+
540+
bool CResource::LoadTranslations()
541+
{
542+
for (CResourceFile* resourceFile : m_ResourceFiles)
543+
{
544+
if (resourceFile->GetResourceType() == CDownloadableResource::RESOURCE_FILE_TYPE_TRANSLATION)
545+
{
546+
CResourceTranslationItem* translationItem = dynamic_cast<CResourceTranslationItem*>(resourceFile);
547+
if (translationItem)
548+
{
549+
std::string fullPath = translationItem->GetName();
550+
if (FileExists(fullPath.c_str()))
551+
{
552+
std::string language = translationItem->GetLanguage();
553+
bool isPrimary = m_translationPrimaryFlags.find(language) != m_translationPrimaryFlags.end();
554+
m_translationManager->LoadTranslation(fullPath, isPrimary);
555+
}
556+
}
557+
}
558+
}
559+
return true;
560+
}

Client/mods/deathmatch/logic/CResource.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "CResourceFile.h"
1818
#include "CResourceModelStreamer.h"
1919
#include "CElementGroup.h"
20+
#include "CResourceTranslationManager.h"
2021
#include <list>
2122

2223
#define MAX_RESOURCE_NAME_LENGTH 255
@@ -99,6 +100,10 @@ class CResource
99100
* @return A pointer to CResourceFile on success, null otherwise
100101
*/
101102
CResourceFile* GetResourceFile(const SString& relativePath) const;
103+
104+
CResourceTranslationManager* GetTranslationManager() const noexcept { return m_translationManager.get(); }
105+
bool LoadTranslations();
106+
void SetTranslationPrimary(const std::string& language) { m_translationPrimaryFlags[language] = true; }
102107

103108
void SetRemainingNoClientCacheScripts(unsigned short usRemaining) { m_usRemainingNoClientCacheScripts = usRemaining; }
104109
void LoadNoClientCacheScript(const char* chunk, unsigned int length, const SString& strFilename);
@@ -150,4 +155,7 @@ class CResource
150155
std::list<SNoClientCacheScript> m_NoClientCacheScriptList;
151156

152157
CResourceModelStreamer m_modelStreamer{};
158+
159+
std::unique_ptr<CResourceTranslationManager> m_translationManager;
160+
std::map<std::string, bool> m_translationPrimaryFlags;
153161
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*****************************************************************************
2+
*
3+
* PROJECT: Multi Theft Auto
4+
* LICENSE: See LICENSE in the top level directory
5+
* FILE: Client/mods/deathmatch/logic/CResourceTranslationItem.cpp
6+
* PURPOSE: Resource translation item class
7+
*
8+
* Multi Theft Auto is available from https://www.multitheftauto.com/
9+
*
10+
*****************************************************************************/
11+
12+
#include "StdInc.h"
13+
#include "CResourceTranslationItem.h"
14+
#include "CResource.h"
15+
#include "CResourceTranslationManager.h"
16+
#include "CLogger.h"
17+
#include <filesystem>
18+
19+
CResourceTranslationItem::CResourceTranslationItem(CResource* resource, const char* name, const char* src, uint uiDownloadSize, CChecksum serverChecksum, bool isPrimary)
20+
: CResourceFile(resource, RESOURCE_FILE_TYPE_TRANSLATION, name, src, uiDownloadSize, serverChecksum, true), m_isPrimary(isPrimary)
21+
{
22+
m_language = ExtractLanguageFromName();
23+
}
24+
25+
26+
std::string CResourceTranslationItem::ExtractLanguageFromName() const
27+
{
28+
// Cast away const since we know GetShortName() doesn't modify the object
29+
std::filesystem::path path(const_cast<CResourceTranslationItem*>(this)->GetShortName());
30+
return path.stem().string();
31+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*****************************************************************************
2+
*
3+
* PROJECT: Multi Theft Auto
4+
* LICENSE: See LICENSE in the top level directory
5+
* FILE: Client/mods/deathmatch/logic/CResourceTranslationItem.h
6+
* PURPOSE: Resource translation item class
7+
*
8+
* Multi Theft Auto is available from https://www.multitheftauto.com/
9+
*
10+
*****************************************************************************/
11+
12+
#pragma once
13+
14+
#include "CResourceFile.h"
15+
16+
class CResourceTranslationItem : public CResourceFile
17+
{
18+
public:
19+
CResourceTranslationItem(CResource* resource, const char* name, const char* src, uint uiDownloadSize, CChecksum serverChecksum, bool isPrimary = false);
20+
21+
const std::string& GetLanguage() const { return m_language; }
22+
bool IsPrimary() const noexcept { return m_isPrimary; }
23+
24+
private:
25+
std::string ExtractLanguageFromName() const;
26+
std::string m_language;
27+
bool m_isPrimary;
28+
};

Client/mods/deathmatch/logic/lua/CLuaManager.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
#include "StdInc.h"
1313
#include "../luadefs/CLuaFireDefs.h"
14-
#include "../luadefs/CLuaClientDefs.h"
14+
#include "luadefs/CLuaBuildingDefs.h"
15+
#include "../luadefs/CLuaTranslationDefsClient.h"
1516
#include "../luadefs/CLuaVectorGraphicDefs.h"
17+
#include "../luadefs/CLuaClientDefs.h"
1618

1719
using std::list;
1820

@@ -283,4 +285,5 @@ void CLuaManager::LoadCFunctions()
283285
CLuaClientDefs::LoadFunctions();
284286
CLuaDiscordDefs::LoadFunctions();
285287
CLuaBuildingDefs::LoadFunctions();
288+
CLuaTranslationDefsClient::LoadFunctions();
286289
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*****************************************************************************
2+
*
3+
* PROJECT: Multi Theft Auto
4+
* LICENSE: See LICENSE in the top level directory
5+
* FILE: Client/mods/deathmatch/logic/luadefs/CLuaTranslationDefsClient.cpp
6+
* PURPOSE: Client-only Lua translation definitions class
7+
*
8+
* Multi Theft Auto is available from https://www.multitheftauto.com/
9+
*
10+
*****************************************************************************/
11+
12+
#include "StdInc.h"
13+
#include "CLuaTranslationDefsClient.h"
14+
#include "CScriptArgReader.h"
15+
#include "CResourceTranslationManager.h"
16+
#include "net/Packets.h"
17+
18+
void CLuaTranslationDefsClient::LoadFunctions()
19+
{
20+
constexpr static const std::pair<const char*, lua_CFunction> functions[]{
21+
{"setCurrentTranslationLanguage", SetCurrentTranslationLanguage},
22+
{"getCurrentTranslationLanguage", GetCurrentTranslationLanguage},
23+
{"getTranslation", GetTranslation},
24+
{"getAvailableTranslations", GetAvailableTranslations},
25+
};
26+
27+
for (const auto& [name, func] : functions)
28+
CLuaCFunctions::AddFunction(name, func);
29+
}
30+
31+
int CLuaTranslationDefsClient::SetCurrentTranslationLanguage(lua_State* luaVM)
32+
{
33+
SString language;
34+
35+
CScriptArgReader argStream(luaVM);
36+
argStream.ReadString(language);
37+
38+
if (!argStream.HasErrors())
39+
{
40+
CLuaMain* luaMain = m_pLuaManager->GetVirtualMachine(luaVM);
41+
if (luaMain)
42+
{
43+
CResource* resource = luaMain->GetResource();
44+
if (resource)
45+
{
46+
if (resource->GetTranslationManager())
47+
{
48+
std::string oldLanguage = resource->GetTranslationManager()->GetClientLanguage();
49+
resource->GetTranslationManager()->SetClientLanguage(language);
50+
std::string newLanguage = resource->GetTranslationManager()->GetClientLanguage();
51+
52+
if (oldLanguage != newLanguage)
53+
{
54+
CLuaArguments args;
55+
args.PushString(oldLanguage);
56+
args.PushString(newLanguage);
57+
58+
if (g_pClientGame->GetLocalPlayer())
59+
{
60+
g_pClientGame->GetLocalPlayer()->CallEvent("onClientTranslationLanguageChange", args, true);
61+
}
62+
}
63+
64+
lua_pushboolean(luaVM, true);
65+
return 1;
66+
}
67+
else
68+
{
69+
m_pScriptDebugging->LogError(luaVM, "Translation system not initialized for resource '%s'", resource->GetName());
70+
}
71+
}
72+
}
73+
}
74+
else
75+
m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage());
76+
77+
lua_pushboolean(luaVM, false);
78+
return 1;
79+
}
80+
81+
int CLuaTranslationDefsClient::GetCurrentTranslationLanguage(lua_State* luaVM)
82+
{
83+
CLuaMain* luaMain = m_pLuaManager->GetVirtualMachine(luaVM);
84+
if (luaMain)
85+
{
86+
CResource* resource = luaMain->GetResource();
87+
if (resource)
88+
{
89+
if (resource->GetTranslationManager())
90+
{
91+
std::string currentLanguage = resource->GetTranslationManager()->GetClientLanguage();
92+
lua_pushstring(luaVM, currentLanguage.c_str());
93+
return 1;
94+
}
95+
else
96+
{
97+
m_pScriptDebugging->LogError(luaVM, "Translation system not initialized for resource '%s'", resource->GetName());
98+
}
99+
}
100+
}
101+
102+
lua_pushstring(luaVM, "");
103+
return 1;
104+
}
105+
106+
int CLuaTranslationDefsClient::GetTranslation(lua_State* luaVM)
107+
{
108+
SString msgid;
109+
SString language;
110+
111+
CScriptArgReader argStream(luaVM);
112+
argStream.ReadString(msgid);
113+
argStream.ReadString(language, "");
114+
115+
if (!argStream.HasErrors())
116+
{
117+
CLuaMain* luaMain = m_pLuaManager->GetVirtualMachine(luaVM);
118+
if (luaMain)
119+
{
120+
CResource* resource = luaMain->GetResource();
121+
if (resource)
122+
{
123+
if (resource->GetTranslationManager())
124+
{
125+
// If no language specified, use current client language
126+
std::string targetLanguage = language.empty() ?
127+
resource->GetTranslationManager()->GetClientLanguage() : language;
128+
std::string result = resource->GetTranslationManager()->GetTranslation(msgid, targetLanguage);
129+
lua_pushstring(luaVM, result.c_str());
130+
return 1;
131+
}
132+
else
133+
{
134+
m_pScriptDebugging->LogError(luaVM, "Translation system not initialized for resource '%s' when requesting '%s'", resource->GetName(), msgid.c_str());
135+
}
136+
}
137+
}
138+
}
139+
else
140+
m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage());
141+
142+
lua_pushstring(luaVM, msgid.c_str());
143+
return 1;
144+
}
145+
146+
int CLuaTranslationDefsClient::GetAvailableTranslations(lua_State* luaVM)
147+
{
148+
CLuaMain* luaMain = m_pLuaManager->GetVirtualMachine(luaVM);
149+
if (luaMain)
150+
{
151+
CResource* resource = luaMain->GetResource();
152+
if (resource)
153+
{
154+
if (resource->GetTranslationManager())
155+
{
156+
std::vector<std::string> languages = resource->GetTranslationManager()->GetAvailableLanguages();
157+
158+
lua_newtable(luaVM);
159+
int index = 1;
160+
for (size_t i = 0; i < languages.size(); ++i)
161+
{
162+
lua_pushinteger(luaVM, index++);
163+
lua_pushstring(luaVM, languages[i].c_str());
164+
lua_settable(luaVM, -3);
165+
}
166+
return 1;
167+
}
168+
else
169+
{
170+
m_pScriptDebugging->LogError(luaVM, "Translation system not initialized for resource '%s'", resource->GetName());
171+
}
172+
}
173+
}
174+
175+
lua_newtable(luaVM);
176+
return 1;
177+
}

0 commit comments

Comments
 (0)