Skip to content

Commit 777717f

Browse files
authored
Merge pull request #28 from thegangstudio/main
Added workflow for manual handling of Translations.json
2 parents 4807f24 + 1013b28 commit 777717f

File tree

4 files changed

+165
-10
lines changed

4 files changed

+165
-10
lines changed

Source/Tolgee/Public/TolgeeSettings.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ class TOLGEE_API UTolgeeSettings : public UDeveloperSettings
6464
*/
6565
UPROPERTY(Config, EditAnywhere, Category = "Tolgee Localization", meta = (EditCondition = "bLiveTranslationUpdates", UIMin = "0"))
6666
float UpdateInterval = 60.0f;
67-
67+
/**
68+
* @brief Automatically fetch Translations.json on cook
69+
*/
70+
UPROPERTY(Config, EditAnywhere, Category = "Tolgee Localization")
71+
bool bFetchTranslationsOnCook = true;
6872
private:
6973
// Begin UDeveloperSettings interface
7074
virtual FName GetContainerName() const override;

Source/TolgeeEditor/Private/TolgeeEditor.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,18 @@ void FTolgeeEditorModule::ExtendToolbar(FToolBarBuilder& Builder)
201201
}
202202
))
203203
);
204+
205+
MenuBuilder.AddMenuEntry(
206+
LOCTEXT("DownloadTranslations", "Download Translations.json"),
207+
LOCTEXT("DownloadTranslationsTip", "Download the latest Translations.json file from Tolgee and add it to VCS"),
208+
FSlateIcon(),
209+
FUIAction(FExecuteAction::CreateLambda(
210+
[]()
211+
{
212+
GEditor->GetEditorSubsystem<UTolgeeEditorIntegrationSubsystem>()->DownloadTranslationsJson();
213+
}
214+
))
215+
);
204216

205217
return MenuBuilder.MakeWidget();
206218
}

Source/TolgeeEditor/Private/TolgeeEditorIntegrationSubsystem.cpp

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
#include <HttpManager.h>
66
#include <HttpModule.h>
7+
#include <ISourceControlModule.h>
8+
#include <ISourceControlOperation.h>
9+
#include <ISourceControlProvider.h>
10+
#include <ISourceControlState.h>
711
#include <Interfaces/IHttpRequest.h>
812
#include <Interfaces/IHttpResponse.h>
913
#include <Interfaces/IMainFrameModule.h>
@@ -19,6 +23,7 @@
1923
#include <Misc/MessageDialog.h>
2024
#include <Serialization/JsonInternationalizationManifestSerializer.h>
2125
#include <Settings/ProjectPackagingSettings.h>
26+
#include <SourceControlOperations.h>
2227

2328
#include "STolgeeSyncDialog.h"
2429
#include "TolgeeEditor.h"
@@ -147,6 +152,114 @@ void UTolgeeEditorIntegrationSubsystem::Sync()
147152
}
148153
}
149154

155+
void UTolgeeEditorIntegrationSubsystem::DownloadTranslationsJson()
156+
{
157+
// Gather remote keys
158+
UTolgeeLocalizationSubsystem* LocalizationSubsystem = GEngine->GetEngineSubsystem<UTolgeeLocalizationSubsystem>();
159+
LocalizationSubsystem->ManualFetch();
160+
161+
// We need to wait synchronously wait for the fetch response to ensure we have the latest data
162+
while (LocalizationSubsystem->IsFetchInprogress())
163+
{
164+
FPlatformProcess::Sleep(0.01f);
165+
166+
if (IsInGameThread())
167+
{
168+
FHttpModule::Get().GetHttpManager().Tick(0.01f);
169+
}
170+
}
171+
172+
const FString FilePath = TolgeeUtils::GetLocalizationSourceFile();
173+
174+
EnsureFileCheckedOutSourceControl(FilePath);
175+
bool bWasModified = ExportLocalTranslations();
176+
EnsureAddedStateSourceControl(FilePath, bWasModified);
177+
}
178+
179+
bool UTolgeeEditorIntegrationSubsystem::EnsureFileCheckedOutSourceControl(FString FilePath)
180+
{
181+
if (!FPaths::FileExists(FilePath))
182+
{
183+
return true;
184+
}
185+
186+
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
187+
188+
if (!SourceControlProvider.IsEnabled())
189+
{
190+
return true;
191+
}
192+
193+
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(FilePath, EStateCacheUsage::ForceUpdate);
194+
if (!SourceControlState.IsValid() || !SourceControlState->IsSourceControlled())
195+
{
196+
return true;
197+
}
198+
199+
if (!SourceControlState->IsCheckedOut())
200+
{
201+
TSharedRef<FCheckOut, ESPMode::ThreadSafe> CheckOutOperation = ISourceControlOperation::Create<FCheckOut>();
202+
203+
if (SourceControlProvider.Execute(CheckOutOperation, FilePath) == ECommandResult::Succeeded)
204+
{
205+
return true;
206+
}
207+
208+
return false;
209+
}
210+
211+
return true;
212+
}
213+
214+
bool UTolgeeEditorIntegrationSubsystem::EnsureAddedStateSourceControl(FString FilePath, bool bWasFileModified)
215+
{
216+
if (!FPaths::FileExists(FilePath))
217+
{
218+
return false;
219+
}
220+
221+
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
222+
223+
if (!SourceControlProvider.IsEnabled())
224+
{
225+
return true;
226+
}
227+
228+
// Ensure we have the latest state from Perforce
229+
TArray<FString> FilesToUpdate = { FilePath };
230+
SourceControlProvider.Execute(ISourceControlOperation::Create<FUpdateStatus>(), FilesToUpdate);
231+
232+
// Fetch the refreshed state
233+
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(FilePath, EStateCacheUsage::ForceUpdate);
234+
235+
if (SourceControlState.IsValid() && SourceControlState->IsSourceControlled() && SourceControlState->IsCheckedOut())
236+
{
237+
// TODO: The idiomatic way of checking if the file was modified would be to use SourceControlState->IsModified()
238+
// But that did not work in a reliable way with Perforce even when retrying with waiting up to 5 seconds and still the file was
239+
// reverted even if it was modified. Relying on the provided bool bWasFileModified is a workaround to make this reliable.
240+
241+
if (bWasFileModified)
242+
{
243+
// File is modified, do NOT revert it
244+
return true;
245+
}
246+
247+
// If the file is checked out but NOT modified, revert it
248+
TSharedRef<FRevert, ESPMode::ThreadSafe> RevertOperation = ISourceControlOperation::Create<FRevert>();
249+
SourceControlProvider.Execute(RevertOperation, FilePath);
250+
251+
return true;
252+
}
253+
254+
TSharedRef<FMarkForAdd, ESPMode::ThreadSafe> AddOperation = ISourceControlOperation::Create<FMarkForAdd>();
255+
if (SourceControlProvider.Execute(AddOperation, FilePath) == ECommandResult::Succeeded)
256+
{
257+
return true;
258+
}
259+
260+
return false;
261+
}
262+
150263
void UTolgeeEditorIntegrationSubsystem::UploadLocalKeys(TArray<FLocalizationKey> NewLocalKeys)
151264
{
152265
const UTolgeeSettings* Settings = GetDefault<UTolgeeSettings>();
@@ -449,7 +562,7 @@ void UTolgeeEditorIntegrationSubsystem::OnMainFrameReady(TSharedPtr<SWindow> InR
449562
#endif
450563
}
451564

452-
void UTolgeeEditorIntegrationSubsystem::ExportLocalTranslations()
565+
bool UTolgeeEditorIntegrationSubsystem::ExportLocalTranslations()
453566
{
454567
const UTolgeeLocalizationSubsystem* LocalizationSubsystem = GEngine->GetEngineSubsystem<UTolgeeLocalizationSubsystem>();
455568
while (LocalizationSubsystem->GetLocalizedDictionary().Keys.Num() == 0)
@@ -468,14 +581,26 @@ void UTolgeeEditorIntegrationSubsystem::ExportLocalTranslations()
468581
if (!FJsonObjectConverter::UStructToJsonObjectString(Dictionary, JsonString))
469582
{
470583
UE_LOG(LogTolgee, Error, TEXT("Couldn't convert the localized dictionary to string"));
471-
return;
584+
return false;
472585
}
473586

474587
const FString Filename = TolgeeUtils::GetLocalizationSourceFile();
588+
FString BeforeHash;
589+
if (FPaths::FileExists(Filename))
590+
{
591+
FString BeforeFileContents;
592+
if (FFileHelper::LoadFileToString(BeforeFileContents, *Filename))
593+
{
594+
BeforeHash = FMD5::HashAnsiString(*BeforeFileContents);
595+
}
596+
}
597+
598+
bool wasModified = BeforeHash != FMD5::HashAnsiString(*JsonString);
599+
475600
if (!FFileHelper::SaveStringToFile(JsonString, *Filename))
476601
{
477602
UE_LOG(LogTolgee, Error, TEXT("Couldn't save the localized dictionary to file: %s"), *Filename);
478-
return;
603+
return false;
479604
}
480605

481606
const FDirectoryPath TolgeeLocalizationPath = TolgeeUtils::GetLocalizationDirectory();
@@ -492,9 +617,10 @@ void UTolgeeEditorIntegrationSubsystem::ExportLocalTranslations()
492617
ProjectPackagingSettings->DirectoriesToAlwaysStageAsNonUFS.Add(TolgeeLocalizationPath);
493618
ProjectPackagingSettings->SaveConfig();
494619
}
495-
496-
620+
497621
UE_LOG(LogTolgee, Display, TEXT("Localized dictionary succesfully saved to file: %s"), *Filename);
622+
623+
return wasModified;
498624
}
499625

500626
void UTolgeeEditorIntegrationSubsystem::Initialize(FSubsystemCollectionBase& Collection)
@@ -523,9 +649,9 @@ void UTolgeeEditorIntegrationSubsystem::Initialize(FSubsystemCollectionBase& Col
523649
#endif
524650

525651
const UTolgeeSettings* Settings = GetDefault<UTolgeeSettings>();
526-
if (bIsRunningCookCommandlet && !Settings->bLiveTranslationUpdates)
652+
if (bIsRunningCookCommandlet && !Settings->bLiveTranslationUpdates && Settings->bFetchTranslationsOnCook)
527653
{
528-
ExportLocalTranslations();
654+
DownloadTranslationsJson();
529655
}
530656
}
531657

Source/TolgeeEditor/Private/TolgeeEditorIntegrationSubsystem.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,21 @@ class UTolgeeEditorIntegrationSubsystem : public UEditorSubsystem
2929
* Collects the local keys and fetches the remotes keys. After comparing the 2, it lets the users chose how to update the keys
3030
*/
3131
void Sync();
32+
/**
33+
* Downloads the latest translations from the backend and updates the Translations.json file in the project
34+
*/
35+
void DownloadTranslationsJson();
3236

3337
private:
38+
/**
39+
* Ensures that the file is checked out if it's source controlled.
40+
*/
41+
bool EnsureFileCheckedOutSourceControl(FString FilePath);
42+
/**
43+
* Ensures that the file is added to source control if it was modified
44+
* If it was not modified it will ensure that the file does not remain checked out
45+
*/
46+
bool EnsureAddedStateSourceControl(FString FilePath, bool bWasFileModified);
3447
/**
3548
* Sends a request to the backend to import new keys
3649
*/
@@ -72,9 +85,9 @@ class UTolgeeEditorIntegrationSubsystem : public UEditorSubsystem
7285
*/
7386
void OnMainFrameReady(TSharedPtr<SWindow> InRootWindow, bool bIsRunningStartupDialog);
7487
/**
75-
* @brief Exports the locally available data to a file on disk to package it in the final build
88+
* @brief Exports the locally available data to a file on disk to package it in the final build, returns true if the local file content was modified during the operation
7689
*/
77-
void ExportLocalTranslations();
90+
bool ExportLocalTranslations();
7891

7992
// Begin UEditorSubsystem interface
8093
virtual void Initialize(FSubsystemCollectionBase& Collection) override;

0 commit comments

Comments
 (0)