You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
852 lines
32 KiB
C++
852 lines
32 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "TranslationEditorPrivatePCH.h"
|
|
#include "TranslationEditor.h"
|
|
#include "WorkspaceMenuStructureModule.h"
|
|
#include "TranslationUnit.h"
|
|
#include "ISourceControlModule.h"
|
|
#include "MessageLog.h"
|
|
#include "TextLocalizationManager.h"
|
|
#include "JsonInternationalizationArchiveSerializer.h"
|
|
#include "JsonInternationalizationManifestSerializer.h"
|
|
#include "SNotificationList.h"
|
|
#include "NotificationManager.h"
|
|
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogTranslationEditor, Log, All);
|
|
|
|
#define LOCTEXT_NAMESPACE "TranslationDataManager"
|
|
|
|
|
|
FTranslationDataManager::FTranslationDataManager( const FString& InManifestFilePath, const FString& InArchiveFilePath )
|
|
: ManifestFilePath(InManifestFilePath)
|
|
, ArchiveFilePath(InArchiveFilePath)
|
|
{
|
|
GWarn->BeginSlowTask(LOCTEXT("LoadingTranslationData", "Loading Translation Data..."), true);
|
|
TArray<UTranslationUnit*> TranslationUnits;
|
|
|
|
ManifestAtHeadRevisionPtr = ReadManifest( ManifestFilePath );
|
|
if (ManifestAtHeadRevisionPtr.IsValid())
|
|
{
|
|
TSharedRef< FInternationalizationManifest > ManifestAtHeadRevision = ManifestAtHeadRevisionPtr.ToSharedRef();
|
|
int32 ManifestEntriesCount = ManifestAtHeadRevision->GetNumEntriesBySourceText();
|
|
|
|
if (ManifestEntriesCount < 1)
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add( TEXT("ManifestFilePath"), FText::FromString(ManifestFilePath) );
|
|
Arguments.Add( TEXT("ManifestEntriesCount"), FText::AsNumber(ManifestEntriesCount) );
|
|
FMessageLog TranslationEditorMessageLog("TranslationEditor");
|
|
TranslationEditorMessageLog.Error(FText::Format(LOCTEXT("CurrentManifestEmpty", "Most current translation manifest ({ManifestFilePath}) has {ManifestEntriesCount} entries."), Arguments));
|
|
TranslationEditorMessageLog.Notify(LOCTEXT("TranslationLoadError", "Error Loading Translations!"));
|
|
TranslationEditorMessageLog.Open(EMessageSeverity::Error);
|
|
}
|
|
|
|
ArchivePtr = ReadArchive();
|
|
if (ArchivePtr.IsValid())
|
|
{
|
|
int32 NumManifestEntriesParsed = 0;
|
|
|
|
GWarn->BeginSlowTask(LOCTEXT("LoadingCurrentManifest", "Loading Entries from Current Translation Manifest..."), true);
|
|
// Get all manifest entries by source text (same source text in multiple contexts will only show up once)
|
|
for (auto ManifestItr = ManifestAtHeadRevision->GetEntriesBySourceTextIterator(); ManifestItr; ++ManifestItr, ++NumManifestEntriesParsed)
|
|
{
|
|
GWarn->StatusUpdate(NumManifestEntriesParsed, ManifestEntriesCount, FText::Format(LOCTEXT("LoadingCurrentManifestEntries", "Loading Entry {0} of {1} from Current Translation Manifest..."), FText::AsNumber(NumManifestEntriesParsed), FText::AsNumber(ManifestEntriesCount)));
|
|
const TSharedRef<FManifestEntry> ManifestEntry = ManifestItr.Value();
|
|
UTranslationUnit* TranslationUnit = NewObject<UTranslationUnit>();
|
|
check(TranslationUnit != nullptr);
|
|
// We want Undo/Redo support
|
|
TranslationUnit->SetFlags(RF_Transactional);
|
|
TranslationUnit->HasBeenReviewed = false;
|
|
TranslationUnit->Source = ManifestEntry->Source.Text;
|
|
TranslationUnit->Namespace = ManifestEntry->Namespace;
|
|
|
|
for(auto ContextIter( ManifestEntry->Contexts.CreateConstIterator() ); ContextIter; ++ContextIter)
|
|
{
|
|
FTranslationContextInfo ContextInfo;
|
|
const FContext& AContext = *ContextIter;
|
|
|
|
ContextInfo.Context = AContext.SourceLocation;
|
|
ContextInfo.Key = AContext.Key;
|
|
|
|
TranslationUnit->Contexts.Add(ContextInfo);
|
|
}
|
|
|
|
TranslationUnits.Add(TranslationUnit);
|
|
}
|
|
GWarn->EndSlowTask();
|
|
|
|
LoadFromArchive(TranslationUnits);
|
|
}
|
|
else // ArchivePtr.IsValid() is false
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add( TEXT("ArchiveFilePath"), FText::FromString(ArchiveFilePath) );
|
|
FMessageLog TranslationEditorMessageLog("TranslationEditor");
|
|
TranslationEditorMessageLog.Error(FText::Format(LOCTEXT("FailedToLoadCurrentArchive", "Failed to load most current translation archive ({ArchiveFilePath}), unable to load translations."), Arguments));
|
|
TranslationEditorMessageLog.Notify(LOCTEXT("TranslationLoadError", "Error Loading Translations!"));
|
|
TranslationEditorMessageLog.Open(EMessageSeverity::Error);
|
|
}
|
|
}
|
|
else // ManifestAtHeadRevisionPtr.IsValid() is false
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add( TEXT("ManifestFilePath"), FText::FromString(ManifestFilePath) );
|
|
FMessageLog TranslationEditorMessageLog("TranslationEditor");
|
|
TranslationEditorMessageLog.Error(FText::Format(LOCTEXT("FailedToLoadCurrentManifest", "Failed to load most current translation manifest ({ManifestFilePath}), unable to load translations."), Arguments));
|
|
TranslationEditorMessageLog.Notify(LOCTEXT("TranslationLoadError", "Error Loading Translations!"));
|
|
TranslationEditorMessageLog.Open(EMessageSeverity::Error);
|
|
}
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
|
|
FTranslationDataManager::~FTranslationDataManager()
|
|
{
|
|
RemoveTranslationUnitArrayfromRoot(AllTranslations); // Re-enable garbage collection for all current UTranslationDataObjects
|
|
}
|
|
|
|
TSharedPtr< FInternationalizationManifest > FTranslationDataManager::ReadManifest( const FString& ManifestFilePathToRead )
|
|
{
|
|
|
|
TSharedPtr<FJsonObject> ManifestJsonObject = ReadJSONTextFile( ManifestFilePathToRead );
|
|
|
|
if( !ManifestJsonObject.IsValid() )
|
|
{
|
|
UE_LOG(LogTranslationEditor, Error, TEXT("Could not read manifest file %s."), *ManifestFilePathToRead);
|
|
return TSharedPtr< FInternationalizationManifest >();
|
|
}
|
|
|
|
TSharedRef< FInternationalizationManifest > InternationalizationManifest = MakeShareable( new FInternationalizationManifest );
|
|
|
|
ManifestSerializer.DeserializeManifest( ManifestJsonObject.ToSharedRef(), InternationalizationManifest );
|
|
|
|
return InternationalizationManifest;
|
|
}
|
|
|
|
TSharedPtr< FInternationalizationArchive > FTranslationDataManager::ReadArchive()
|
|
{
|
|
// Read in any existing archive for this culture.
|
|
TSharedPtr< FJsonObject > ArchiveJsonObject = ReadJSONTextFile( ArchiveFilePath );
|
|
|
|
if ( !ArchiveJsonObject.IsValid() )
|
|
{
|
|
UE_LOG(LogTranslationEditor, Error, TEXT("Could not read archive file %s."), *ArchiveFilePath);
|
|
return nullptr;
|
|
}
|
|
|
|
TSharedRef< FInternationalizationArchive > InternationalizationArchive = MakeShareable( new FInternationalizationArchive );
|
|
|
|
ArchiveSerializer.DeserializeArchive( ArchiveJsonObject.ToSharedRef(), InternationalizationArchive );
|
|
|
|
return InternationalizationArchive;
|
|
}
|
|
|
|
TSharedPtr<FJsonObject> FTranslationDataManager::ReadJSONTextFile(const FString& InFilePath)
|
|
{
|
|
//read in file as string
|
|
FString FileContents;
|
|
if ( !FFileHelper::LoadFileToString(FileContents, *InFilePath) )
|
|
{
|
|
UE_LOG(LogTranslationEditor, Error,TEXT("Failed to load file %s."), *InFilePath);
|
|
return nullptr;
|
|
}
|
|
|
|
//parse as JSON
|
|
TSharedPtr<FJsonObject> JSONObject;
|
|
|
|
TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( FileContents );
|
|
|
|
if( !FJsonSerializer::Deserialize( Reader, JSONObject ) || !JSONObject.IsValid())
|
|
{
|
|
UE_LOG(LogTranslationEditor, Error,TEXT("Invalid JSON in file %s."), *InFilePath);
|
|
return nullptr;
|
|
}
|
|
|
|
return JSONObject;
|
|
}
|
|
|
|
bool FTranslationDataManager::WriteTranslationData(bool bForceWrite /*= false*/)
|
|
{
|
|
check (ArchivePtr.IsValid());
|
|
TSharedRef< FInternationalizationArchive > Archive = ArchivePtr.ToSharedRef();
|
|
|
|
bool bNeedsWrite = false;
|
|
|
|
for (UTranslationUnit* TranslationUnit : Untranslated)
|
|
{
|
|
if (TranslationUnit != nullptr)
|
|
{
|
|
const FLocItem SearchSource(TranslationUnit->Source);
|
|
FString OldTranslation = Archive->FindEntryBySource(TranslationUnit->Namespace, SearchSource, nullptr)->Translation.Text;
|
|
FString TranslationToWrite = TranslationUnit->Translation;
|
|
if (!TranslationToWrite.Equals(OldTranslation))
|
|
{
|
|
Archive->SetTranslation(TranslationUnit->Namespace, TranslationUnit->Source, TranslationToWrite, nullptr);
|
|
bNeedsWrite = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (UTranslationUnit* TranslationUnit : Review)
|
|
{
|
|
if (TranslationUnit != nullptr)
|
|
{
|
|
const FLocItem SearchSource(TranslationUnit->Source);
|
|
FString OldTranslation = Archive->FindEntryBySource(TranslationUnit->Namespace, SearchSource, nullptr)->Translation.Text;
|
|
FString TranslationToWrite = TranslationUnit->Translation;
|
|
if (TranslationUnit->HasBeenReviewed && !TranslationToWrite.Equals(OldTranslation))
|
|
{
|
|
Archive->SetTranslation(TranslationUnit->Namespace, TranslationUnit->Source, TranslationToWrite, nullptr);
|
|
bNeedsWrite = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (UTranslationUnit* TranslationUnit : Complete)
|
|
{
|
|
if (TranslationUnit != nullptr)
|
|
{
|
|
const FLocItem SearchSource(TranslationUnit->Source);
|
|
FString OldTranslation = Archive->FindEntryBySource(TranslationUnit->Namespace, SearchSource, nullptr)->Translation.Text;
|
|
FString TranslationToWrite = TranslationUnit->Translation;
|
|
if (!TranslationToWrite.Equals(OldTranslation))
|
|
{
|
|
Archive->SetTranslation(TranslationUnit->Namespace, TranslationUnit->Source, TranslationToWrite, nullptr);
|
|
bNeedsWrite = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bSuccess = true;
|
|
|
|
if (bForceWrite || bNeedsWrite)
|
|
{
|
|
TSharedRef<FJsonObject> FinalArchiveJsonObj = MakeShareable( new FJsonObject );
|
|
ArchiveSerializer.SerializeArchive( Archive, FinalArchiveJsonObj );
|
|
|
|
bSuccess = WriteJSONToTextFile( FinalArchiveJsonObj, ArchiveFilePath );
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
bool FTranslationDataManager::WriteJSONToTextFile(TSharedRef<FJsonObject>& Output, const FString& Filename)
|
|
{
|
|
bool CheckoutAndSaveWasSuccessful = true;
|
|
bool bPreviouslyCheckedOut = false;
|
|
|
|
// If the user specified a reference file - write the entries read from code to a ref file
|
|
if ( !Filename.IsEmpty() )
|
|
{
|
|
// If source control is enabled, try to check out the file. Otherwise just try to write it
|
|
if (ISourceControlModule::Get().IsEnabled())
|
|
{
|
|
// Already checked out?
|
|
if (CheckedOutFiles.Contains(Filename))
|
|
{
|
|
bPreviouslyCheckedOut = true;
|
|
}
|
|
else if (!SourceControlHelpers::CheckOutFile(Filename))
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("Filename"), FText::FromString(Filename));
|
|
// Use Source Control Message Log here because there might be other useful information in that log for the user.
|
|
FMessageLog SourceControlMessageLog("SourceControl");
|
|
SourceControlMessageLog.Error(FText::Format(LOCTEXT("CheckoutFailed", "Check out of file '{Filename}' failed."), Arguments));
|
|
SourceControlMessageLog.Notify(LOCTEXT("TranslationArchiveCheckoutFailed", "Failed to Check Out Translation Archive!"));
|
|
SourceControlMessageLog.Open(EMessageSeverity::Error);
|
|
CheckoutAndSaveWasSuccessful = false;
|
|
}
|
|
else
|
|
{
|
|
CheckedOutFiles.Add(Filename);
|
|
}
|
|
}
|
|
|
|
if( CheckoutAndSaveWasSuccessful )
|
|
{
|
|
//Print the JSON data out to the ref file.
|
|
FString OutputString;
|
|
TSharedRef< TJsonWriter<> > Writer = TJsonWriterFactory<>::Create( &OutputString );
|
|
FJsonSerializer::Serialize( Output, Writer );
|
|
|
|
if (!FFileHelper::SaveStringToFile(OutputString, *Filename, FFileHelper::EEncodingOptions::ForceUnicode))
|
|
{
|
|
// If we already checked out the file, but cannot write it, perhaps the user checked it in via perforce, so try to check it out again
|
|
if (bPreviouslyCheckedOut)
|
|
{
|
|
bPreviouslyCheckedOut = false;
|
|
|
|
if( !SourceControlHelpers::CheckOutFile(Filename) )
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add( TEXT("Filename"), FText::FromString(Filename) );
|
|
// Use Source Control Message Log here because there might be other useful information in that log for the user.
|
|
FMessageLog SourceControlMessageLog("SourceControl");
|
|
SourceControlMessageLog.Error(FText::Format(LOCTEXT("CheckoutFailed", "Check out of file '{Filename}' failed."), Arguments));
|
|
SourceControlMessageLog.Notify(LOCTEXT("TranslationArchiveCheckoutFailed", "Failed to Check Out Translation Archive!"));
|
|
SourceControlMessageLog.Open(EMessageSeverity::Error);
|
|
CheckoutAndSaveWasSuccessful = false;
|
|
|
|
CheckedOutFiles.Remove(Filename);
|
|
}
|
|
}
|
|
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add( TEXT("Filename"), FText::FromString(Filename) );
|
|
FMessageLog TranslationEditorMessageLog("TranslationEditor");
|
|
TranslationEditorMessageLog.Error(FText::Format(LOCTEXT("WriteFileFailed", "Failed to write localization entries to file '{Filename}'."), Arguments));
|
|
TranslationEditorMessageLog.Notify(LOCTEXT("FileWriteFailed", "Failed to Write Translations to File!"));
|
|
TranslationEditorMessageLog.Open(EMessageSeverity::Error);
|
|
CheckoutAndSaveWasSuccessful = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CheckoutAndSaveWasSuccessful = false;
|
|
}
|
|
|
|
// If this is the first time, let the user know the file was checked out
|
|
if (!bPreviouslyCheckedOut && CheckoutAndSaveWasSuccessful)
|
|
{
|
|
struct Local
|
|
{
|
|
/**
|
|
* Called by our notification's hyperlink to open the Source Control message log
|
|
*/
|
|
static void OpenSourceControlMessageLog( )
|
|
{
|
|
FMessageLog("SourceControl").Open();
|
|
}
|
|
};
|
|
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add( TEXT("Filename"), FText::FromString(Filename) );
|
|
|
|
// Make a note in the Source Control log, including a note to check in the file later via source control application
|
|
FMessageLog TranslationEditorMessageLog("SourceControl");
|
|
TranslationEditorMessageLog.Info(FText::Format(LOCTEXT("TranslationArchiveCheckedOut", "Successfully checked out and saved translation archive '{Filename}'. Please check-in this file later via your source control application."), Arguments));
|
|
|
|
// Display notification that save was successful, along with a link to the Source Control log so the user can see the above message.
|
|
FNotificationInfo Info( LOCTEXT("ArchiveCheckedOut", "Translation Archive Successfully Checked Out and Saved.") );
|
|
Info.ExpireDuration = 5;
|
|
Info.Hyperlink = FSimpleDelegate::CreateStatic(&Local::OpenSourceControlMessageLog);
|
|
Info.HyperlinkText = LOCTEXT("ShowMessageLogHyperlink", "Show Message Log");
|
|
Info.bFireAndForget = true;
|
|
Info.bUseSuccessFailIcons = true;
|
|
Info.Image = FEditorStyle::GetBrush(TEXT("NotificationList.SuccessImage"));
|
|
FSlateNotificationManager::Get().AddNotification(Info);
|
|
}
|
|
|
|
return CheckoutAndSaveWasSuccessful;
|
|
}
|
|
|
|
void FTranslationDataManager::GetHistoryForTranslationUnits()
|
|
{
|
|
GWarn->BeginSlowTask(LOCTEXT("LoadingSourceControlHistory", "Loading Translation History from Source Control..."), true);
|
|
|
|
TArray<UTranslationUnit*>& TranslationUnits = AllTranslations;
|
|
const FString& InManifestFilePath = ManifestFilePath;
|
|
|
|
// Unload any previous history information, going to retrieve it all again.
|
|
UnloadHistoryInformation();
|
|
|
|
// Force history update
|
|
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
|
TSharedRef<FUpdateStatus, ESPMode::ThreadSafe> UpdateStatusOperation = ISourceControlOperation::Create<FUpdateStatus>();
|
|
UpdateStatusOperation->SetUpdateHistory( true );
|
|
ECommandResult::Type Result = SourceControlProvider.Execute(UpdateStatusOperation, InManifestFilePath);
|
|
bool bGetHistoryFromSourceControlSucceeded = Result == ECommandResult::Succeeded;
|
|
|
|
// Now we can get information about the file's history from the source control state, retrieve that
|
|
TArray<FString> Files;
|
|
TArray< TSharedRef<ISourceControlState, ESPMode::ThreadSafe> > States;
|
|
Files.Add(InManifestFilePath);
|
|
Result = SourceControlProvider.GetState( Files, States, EStateCacheUsage::ForceUpdate );
|
|
bGetHistoryFromSourceControlSucceeded = bGetHistoryFromSourceControlSucceeded && (Result == ECommandResult::Succeeded);
|
|
FSourceControlStatePtr SourceControlState;
|
|
if (States.Num() == 1)
|
|
{
|
|
SourceControlState = States[0];
|
|
}
|
|
|
|
// If all the source control operations went ok, continue
|
|
if (bGetHistoryFromSourceControlSucceeded && SourceControlState.IsValid())
|
|
{
|
|
int32 HistorySize = SourceControlState->GetHistorySize();
|
|
|
|
for (int HistoryItemIndex = HistorySize-1; HistoryItemIndex >=0; --HistoryItemIndex)
|
|
{
|
|
GWarn->StatusUpdate(HistorySize - HistoryItemIndex, HistorySize, FText::Format(LOCTEXT("LoadingOldManifestRevisionNumber", "Loading Translation History from Manifest Revision {0} of {1} from Source Control..."), FText::AsNumber(HistorySize - HistoryItemIndex), FText::AsNumber(HistorySize)));
|
|
|
|
TSharedPtr<ISourceControlRevision, ESPMode::ThreadSafe> Revision = SourceControlState->GetHistoryItem(HistoryItemIndex);
|
|
if(Revision.IsValid())
|
|
{
|
|
FString ManifestFullPath = FPaths::ConvertRelativePathToFull(InManifestFilePath);
|
|
FString EngineFullPath = FPaths::ConvertRelativePathToFull(FPaths::EngineContentDir());
|
|
|
|
bool IsEngineManifest = false;
|
|
if (ManifestFullPath.StartsWith(EngineFullPath))
|
|
{
|
|
IsEngineManifest = true;
|
|
}
|
|
|
|
FString ProjectName;
|
|
FString SavedDir; // Store these cached translation history files in the saved directory
|
|
if (IsEngineManifest)
|
|
{
|
|
ProjectName = "Engine";
|
|
SavedDir = FPaths::EngineSavedDir();
|
|
}
|
|
else
|
|
{
|
|
ProjectName = FApp::GetGameName();
|
|
SavedDir = FPaths::GameSavedDir();
|
|
}
|
|
|
|
FString TempFileName = SavedDir / "CachedTranslationHistory" / "UE4-Manifest-" + ProjectName + "-" + FPaths::GetBaseFilename(InManifestFilePath) + "-Rev-" + FString::FromInt(Revision->GetRevisionNumber());
|
|
|
|
|
|
if (!FPaths::FileExists(TempFileName)) // Don't bother syncing again if we already have this manifest version cached locally
|
|
{
|
|
Revision->Get(TempFileName);
|
|
}
|
|
|
|
TSharedPtr< FInternationalizationManifest > OldManifestPtr = ReadManifest( TempFileName );
|
|
if (OldManifestPtr.IsValid()) // There may be corrupt manifests in the history, so ignore them.
|
|
{
|
|
TSharedRef< FInternationalizationManifest > OldManifest = OldManifestPtr.ToSharedRef();
|
|
|
|
for (UTranslationUnit* TranslationUnit : TranslationUnits)
|
|
{
|
|
if(TranslationUnit != nullptr && TranslationUnit->Contexts.Num() > 0)
|
|
{
|
|
for (FTranslationContextInfo& ContextInfo : TranslationUnit->Contexts)
|
|
{
|
|
FString PreviousSourceText = "";
|
|
|
|
// If we already have history, then compare against the newest history so far
|
|
if (ContextInfo.Changes.Num() > 0)
|
|
{
|
|
PreviousSourceText = ContextInfo.Changes[0].Source;
|
|
}
|
|
|
|
FContext SearchContext;
|
|
SearchContext.Key = ContextInfo.Key;
|
|
TSharedPtr< FManifestEntry > OldManifestEntryPtr = OldManifest->FindEntryByContext(TranslationUnit->Namespace, SearchContext);
|
|
if (!OldManifestEntryPtr.IsValid())
|
|
{
|
|
// If this version of the manifest didn't know anything about this string, move onto the next
|
|
continue;
|
|
}
|
|
|
|
// Always add first instance of this string, and then add any versions that changed since
|
|
if (ContextInfo.Changes.Num() == 0 || !OldManifestEntryPtr->Source.Text.Equals(PreviousSourceText))
|
|
{
|
|
TSharedPtr< FArchiveEntry > OldArchiveEntry = ArchivePtr->FindEntryBySource(OldManifestEntryPtr->Namespace, OldManifestEntryPtr->Source, nullptr);
|
|
if (OldArchiveEntry.IsValid())
|
|
{
|
|
FTranslationChange Change;
|
|
Change.Source = OldManifestEntryPtr->Source.Text;
|
|
Change.Translation = OldArchiveEntry->Translation.Text;
|
|
Change.DateAndTime = Revision->GetDate();
|
|
Change.Version = FString::FromInt(Revision->GetRevisionNumber());
|
|
ContextInfo.Changes.Insert(Change, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // OldManifestPtr.IsValid() is false
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("ManifestFilePath"), FText::FromString(InManifestFilePath));
|
|
Arguments.Add( TEXT("ManifestRevisionNumber"), FText::AsNumber(Revision->GetRevisionNumber()) );
|
|
FMessageLog TranslationEditorMessageLog("TranslationEditor");
|
|
TranslationEditorMessageLog.Warning(FText::Format(LOCTEXT("PreviousManifestCorrupt", "Previous revision {ManifestRevisionNumber} of {ManifestFilePath} failed to load correctly. Ignoring."), Arguments));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// If source control operations failed, display error message
|
|
else // (bGetHistoryFromSourceControlSucceeded && SourceControlState.IsValid()) is false
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("ManifestFilePath"), FText::FromString(InManifestFilePath));
|
|
FMessageLog TranslationEditorMessageLog("SourceControl");
|
|
TranslationEditorMessageLog.Warning(FText::Format(LOCTEXT("SourceControlStateQueryFailed", "Failed to query source control state of file {ManifestFilePath}."), Arguments));
|
|
TranslationEditorMessageLog.Notify(LOCTEXT("RetrieveTranslationHistoryFailed", "Unable to Retrieve Translation History from Source Control!"));
|
|
}
|
|
|
|
|
|
// Go though all translation units
|
|
for (int32 CurrentTranslationUnitIndex = 0; CurrentTranslationUnitIndex < TranslationUnits.Num(); ++CurrentTranslationUnitIndex)
|
|
{
|
|
UTranslationUnit* TranslationUnit = TranslationUnits[CurrentTranslationUnitIndex];
|
|
if (TranslationUnit != nullptr)
|
|
{
|
|
if (TranslationUnit->Translation.IsEmpty())
|
|
{
|
|
bool bHasTranslationHistory = false;
|
|
int32 MostRecentNonNullTranslationIndex = -1;
|
|
int32 ContextForRecentTranslation = -1;
|
|
|
|
// Check all contexts for history
|
|
for (int32 ContextIndex = 0; ContextIndex < TranslationUnit->Contexts.Num(); ++ContextIndex)
|
|
{
|
|
for (int32 ChangeIndex = 0; ChangeIndex < TranslationUnit->Contexts[ContextIndex].Changes.Num(); ++ChangeIndex)
|
|
{
|
|
if (!(TranslationUnit->Contexts[ContextIndex].Changes[ChangeIndex].Translation.IsEmpty()))
|
|
{
|
|
bHasTranslationHistory = true;
|
|
MostRecentNonNullTranslationIndex = ChangeIndex;
|
|
ContextForRecentTranslation = ContextIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bHasTranslationHistory)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we have history, but current translation is empty, this goes in the Needs Review tab
|
|
if (bHasTranslationHistory)
|
|
{
|
|
// Offer the most recent translation (for the first context in the list) as a suggestion or starting point (not saved unless user checks "Has Been Reviewed")
|
|
TranslationUnit->Translation = TranslationUnit->Contexts[ContextForRecentTranslation].Changes[MostRecentNonNullTranslationIndex].Translation;
|
|
TranslationUnit->HasBeenReviewed = false;
|
|
|
|
// Move from Untranslated to review
|
|
if (Untranslated.Contains(TranslationUnit))
|
|
{
|
|
Untranslated.Remove(TranslationUnit);
|
|
}
|
|
if (!Review.Contains(TranslationUnit))
|
|
{
|
|
Review.Add(TranslationUnit);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
|
|
void FTranslationDataManager::HandlePropertyChanged(FName PropertyName)
|
|
{
|
|
// When a property changes, write the data so we don't lose changes if user forgets to save or editor crashes
|
|
WriteTranslationData();
|
|
}
|
|
|
|
void FTranslationDataManager::PreviewAllTranslationsInEditor()
|
|
{
|
|
FString ManifestFullPath = FPaths::ConvertRelativePathToFull(ManifestFilePath);
|
|
FString EngineFullPath = FPaths::ConvertRelativePathToFull(FPaths::EngineContentDir());
|
|
|
|
bool IsEngineManifest = false;
|
|
if (ManifestFullPath.StartsWith(EngineFullPath))
|
|
{
|
|
IsEngineManifest = true;
|
|
}
|
|
|
|
FString ConfigDirectory;
|
|
if (IsEngineManifest)
|
|
{
|
|
ConfigDirectory = FPaths::EngineConfigDir();
|
|
}
|
|
else
|
|
{
|
|
ConfigDirectory = FPaths::GameConfigDir();
|
|
}
|
|
|
|
FString ConfigFilePath = ConfigDirectory / "Localization" / "Regenerate" + FPaths::GetBaseFilename(ManifestFilePath) + ".ini";
|
|
|
|
FJsonInternationalizationArchiveSerializer LocalizationArchiveSerializer;
|
|
FJsonInternationalizationManifestSerializer LocalizationManifestSerializer;
|
|
|
|
FTextLocalizationManager::Get().LoadFromManifestAndArchives(ConfigFilePath, LocalizationArchiveSerializer, LocalizationManifestSerializer);
|
|
}
|
|
|
|
void FTranslationDataManager::PopulateSearchResultsUsingFilter(const FString& SearchFilter)
|
|
{
|
|
SearchResults.Empty();
|
|
|
|
for (UTranslationUnit* TranslationUnit : AllTranslations)
|
|
{
|
|
if (TranslationUnit != nullptr)
|
|
{
|
|
bool bAdded = false;
|
|
if (TranslationUnit->Source.Contains(SearchFilter) ||
|
|
TranslationUnit->Translation.Contains(SearchFilter) ||
|
|
TranslationUnit->Namespace.Contains(SearchFilter))
|
|
{
|
|
SearchResults.Add(TranslationUnit);
|
|
bAdded = true;
|
|
}
|
|
|
|
for (FTranslationContextInfo CurrentContext : TranslationUnit->Contexts)
|
|
{
|
|
if (!bAdded &&
|
|
(CurrentContext.Context.Contains(SearchFilter) ||
|
|
CurrentContext.Key.Contains(SearchFilter)))
|
|
{
|
|
SearchResults.Add(TranslationUnit);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FTranslationDataManager::LoadFromArchive(TArray<UTranslationUnit*>& InTranslationUnits, bool bTrackChanges /*= false*/, bool bReloadFromFile /*=false*/)
|
|
{
|
|
GWarn->BeginSlowTask(LOCTEXT("LoadingArchiveEntries", "Loading Entries from Translation Archive..."), true);
|
|
|
|
if (bReloadFromFile)
|
|
{
|
|
ArchivePtr = ReadArchive();
|
|
}
|
|
|
|
if (ArchivePtr.IsValid())
|
|
{
|
|
TSharedRef< FInternationalizationArchive > Archive = ArchivePtr.ToSharedRef();
|
|
|
|
// Make a local copy of this array before we empty the arrays below (we might have been passed AllTranslations array)
|
|
TArray<UTranslationUnit*> TranslationUnits;
|
|
TranslationUnits.Append(InTranslationUnits);
|
|
|
|
AllTranslations.Empty();
|
|
Untranslated.Empty();
|
|
Review.Empty();
|
|
Complete.Empty();
|
|
ChangedOnImport.Empty();
|
|
|
|
for (int32 CurrentTranslationUnitIndex = 0; CurrentTranslationUnitIndex < TranslationUnits.Num(); ++CurrentTranslationUnitIndex)
|
|
{
|
|
UTranslationUnit* TranslationUnit = TranslationUnits[CurrentTranslationUnitIndex];
|
|
if (TranslationUnit != nullptr)
|
|
{
|
|
if (!TranslationUnit->IsRooted())
|
|
{
|
|
TranslationUnit->AddToRoot(); // Disable garbage collection for UTranslationUnit objects
|
|
}
|
|
AllTranslations.Add(TranslationUnit);
|
|
|
|
GWarn->StatusUpdate(CurrentTranslationUnitIndex, TranslationUnits.Num(), FText::Format(LOCTEXT("LoadingCurrentArchiveEntries", "Loading Entry {0} of {1} from Translation Archive..."), FText::AsNumber(CurrentTranslationUnitIndex), FText::AsNumber(TranslationUnits.Num())));
|
|
|
|
const FLocItem SourceSearch(TranslationUnit->Source);
|
|
TSharedPtr<FArchiveEntry> ArchiveEntry = Archive->FindEntryBySource(TranslationUnit->Namespace, SourceSearch, nullptr);
|
|
if (ArchiveEntry.IsValid())
|
|
{
|
|
const FString PreviousTranslation = TranslationUnit->Translation;
|
|
TranslationUnit->Translation = ""; // Reset to null string
|
|
const FString TranslatedString = ArchiveEntry->Translation.Text;
|
|
|
|
if (TranslatedString.IsEmpty())
|
|
{
|
|
bool bHasTranslationHistory = false;
|
|
int32 MostRecentNonNullTranslationIndex = -1;
|
|
int32 ContextForRecentTranslation = -1;
|
|
|
|
for (int32 ContextIndex = 0; ContextIndex < TranslationUnit->Contexts.Num(); ++ContextIndex)
|
|
{
|
|
for (int32 ChangeIndex = 0; ChangeIndex < TranslationUnit->Contexts[ContextIndex].Changes.Num(); ++ChangeIndex)
|
|
{
|
|
if (!(TranslationUnit->Contexts[ContextIndex].Changes[ChangeIndex].Translation.IsEmpty()))
|
|
{
|
|
bHasTranslationHistory = true;
|
|
MostRecentNonNullTranslationIndex = ChangeIndex;
|
|
ContextForRecentTranslation = ContextIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bHasTranslationHistory)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we have history, but current translation is empty, this goes in the Needs Review tab
|
|
if (bHasTranslationHistory)
|
|
{
|
|
// Offer the most recent translation (for the first context in the list) as a suggestion or starting point (not saved unless user checks "Has Been Reviewed")
|
|
TranslationUnit->Translation = TranslationUnit->Contexts[ContextForRecentTranslation].Changes[MostRecentNonNullTranslationIndex].Translation;
|
|
Review.Add(TranslationUnit);
|
|
}
|
|
else
|
|
{
|
|
Untranslated.Add(TranslationUnit);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TranslationUnit->Translation = TranslatedString;
|
|
TranslationUnit->HasBeenReviewed = true;
|
|
Complete.Add(TranslationUnit);
|
|
}
|
|
|
|
// Add to changed array if we're tracking changes (i.e. when we import from .po files)
|
|
if (bTrackChanges)
|
|
{
|
|
if (PreviousTranslation != TranslationUnit->Translation)
|
|
{
|
|
FString PreviousTranslationTrimmed = PreviousTranslation;
|
|
PreviousTranslationTrimmed.Trim().TrimTrailing();
|
|
FString CurrentTranslationTrimmed = TranslationUnit->Translation;
|
|
CurrentTranslationTrimmed.Trim().TrimTrailing();
|
|
// Ignore changes to only whitespace at beginning and/or end of string on import
|
|
if (PreviousTranslationTrimmed == CurrentTranslationTrimmed)
|
|
{
|
|
TranslationUnit->Translation = PreviousTranslation;
|
|
}
|
|
else
|
|
{
|
|
ChangedOnImport.Add(TranslationUnit);
|
|
TranslationUnit->TranslationBeforeImport = PreviousTranslation;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // ArchivePtr.IsValid() is false
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("ArchiveFilePath"), FText::FromString(ArchiveFilePath));
|
|
FMessageLog TranslationEditorMessageLog("TranslationEditor");
|
|
TranslationEditorMessageLog.Error(FText::Format(LOCTEXT("FailedToLoadCurrentArchive", "Failed to load most current translation archive ({ArchiveFilePath}), unable to load translations."), Arguments));
|
|
TranslationEditorMessageLog.Notify(LOCTEXT("TranslationLoadError", "Error Loading Translations!"));
|
|
TranslationEditorMessageLog.Open(EMessageSeverity::Error);
|
|
}
|
|
|
|
GWarn->EndSlowTask();
|
|
}
|
|
|
|
void FTranslationDataManager::RemoveTranslationUnitArrayfromRoot(TArray<UTranslationUnit*>& TranslationUnits)
|
|
{
|
|
for (UTranslationUnit* TranslationUnit : TranslationUnits)
|
|
{
|
|
TranslationUnit->RemoveFromRoot();
|
|
}
|
|
}
|
|
|
|
void FTranslationDataManager::UnloadHistoryInformation()
|
|
{
|
|
TArray<UTranslationUnit*>& TranslationUnits = AllTranslations;
|
|
|
|
for (int32 CurrentTranslationUnitIndex = 0; CurrentTranslationUnitIndex < TranslationUnits.Num(); ++CurrentTranslationUnitIndex)
|
|
{
|
|
UTranslationUnit* TranslationUnit = TranslationUnits[CurrentTranslationUnitIndex];
|
|
if (TranslationUnit != nullptr)
|
|
{
|
|
// If HasBeenReviewed is false, this is a suggestion translation from a previous translation for the same Namespace/Key pair
|
|
if (!TranslationUnit->HasBeenReviewed)
|
|
{
|
|
if (!Untranslated.Contains(TranslationUnit))
|
|
{
|
|
Untranslated.Add(TranslationUnit);
|
|
}
|
|
if (Review.Contains(TranslationUnit))
|
|
{
|
|
Review.Remove(TranslationUnit);
|
|
}
|
|
|
|
// Erase previously suggested translation from history (it has not been reviewed)
|
|
TranslationUnit->Translation.Empty();
|
|
|
|
// Remove all history entries
|
|
for (FTranslationContextInfo Context : TranslationUnit->Contexts)
|
|
{
|
|
Context.Changes.Empty();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FTranslationDataManager::SaveSelectedTranslations(TArray<UTranslationUnit*> TranslationUnitsToSave)
|
|
{
|
|
bool bSucceeded = true;
|
|
|
|
TMap<FString, TSharedPtr<TArray<UTranslationUnit*>>> TextsToSavePerProject;
|
|
|
|
// Regroup the translations to save by project
|
|
for (UTranslationUnit* TextToSave : TranslationUnitsToSave)
|
|
{
|
|
FString LocresFilePath = TextToSave->LocresPath;
|
|
if (!LocresFilePath.IsEmpty())
|
|
{
|
|
if (!TextsToSavePerProject.Contains(LocresFilePath))
|
|
{
|
|
TextsToSavePerProject.Add(LocresFilePath, MakeShareable(new TArray<UTranslationUnit*>()));
|
|
}
|
|
|
|
TSharedPtr<TArray<UTranslationUnit*>> ProjectArray = TextsToSavePerProject.FindRef(LocresFilePath);
|
|
ProjectArray->Add(TextToSave);
|
|
}
|
|
}
|
|
|
|
for (auto TextIt = TextsToSavePerProject.CreateIterator(); TextIt; ++TextIt)
|
|
{
|
|
auto Item = *TextIt;
|
|
FString CurrentLocResPath = Item.Key;
|
|
FString ManifestAndArchiveName = FPaths::GetBaseFilename(CurrentLocResPath);
|
|
FString ArchiveFilePath = FPaths::GetPath(CurrentLocResPath);
|
|
FString CultureName = FPaths::GetBaseFilename(ArchiveFilePath);
|
|
FString ManifestPath = FPaths::GetPath(ArchiveFilePath);
|
|
FString ArchiveFullPath = ArchiveFilePath / ManifestAndArchiveName + ".archive";
|
|
FString ManifestFullPath = ManifestPath / ManifestAndArchiveName + ".manifest";
|
|
|
|
if (FPaths::FileExists(ManifestFullPath) && FPaths::FileExists(ArchiveFullPath))
|
|
{
|
|
TSharedRef<FTranslationDataManager> DataManager = MakeShareable(new FTranslationDataManager(ManifestFullPath, ArchiveFullPath));
|
|
|
|
TArray<UTranslationUnit*>& TranslationsArray = DataManager->GetAllTranslationsArray();
|
|
TSharedPtr<TArray<UTranslationUnit*>> EditedItems = Item.Value;
|
|
|
|
// For each edited item belonging to this manifest/archive pair
|
|
for (auto EditedItemIt = EditedItems->CreateIterator(); EditedItemIt; ++EditedItemIt)
|
|
{
|
|
UTranslationUnit* EditedItem = *EditedItemIt;
|
|
|
|
// Search all translations for the one that matches this FText
|
|
for (UTranslationUnit* Translation : TranslationsArray)
|
|
{
|
|
// If namespace matches...
|
|
if (Translation->Namespace == EditedItem->Namespace)
|
|
{
|
|
// And source matches
|
|
if (Translation->Source == EditedItem->Source)
|
|
{
|
|
// Update the translation in TranslationDataManager, and finish searching these translations
|
|
Translation->Translation = EditedItem->Translation;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save the data to file, and preview in editor
|
|
bSucceeded = bSucceeded && DataManager->WriteTranslationData();
|
|
DataManager->PreviewAllTranslationsInEditor();
|
|
}
|
|
else
|
|
{
|
|
bSucceeded = false;
|
|
}
|
|
}
|
|
|
|
return bSucceeded;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|