diff --git a/Source/EasyLocalizationToolEditor/Private/ELTCommandlet.cpp b/Source/EasyLocalizationToolEditor/Private/ELTCommandlet.cpp index 5a93910..6ab23e6 100644 --- a/Source/EasyLocalizationToolEditor/Private/ELTCommandlet.cpp +++ b/Source/EasyLocalizationToolEditor/Private/ELTCommandlet.cpp @@ -37,11 +37,13 @@ int32 UELTCommandlet::Main(const FString& Params) FString Fallback = TEXT("NONE"); FParse::Value(*Params, TEXT("-Fallback="), Fallback); + bool bGenerateStringTables = FParse::Param(*Params, TEXT("-GenStringTables")); + const FString LocName = FPaths::GetBaseFilename(LocPath); // Run generation of loc files implementation. Get the output message and display it the localization fails. FString OutMessage; - if (UELTEditor::GenerateLocFilesImpl(CSVPath, LocPath, LocName, Namespace, Separator, Fallback, OutMessage) == false) + if (UELTEditor::GenerateLocFilesImpl(CSVPath, LocPath, LocName, Namespace, Separator, Fallback, bGenerateStringTables, OutMessage) == false) { UE_LOG(ELTCommandletLog, Log, TEXT("+++ Failed to generate Localization: %s"), *OutMessage); return 1; diff --git a/Source/EasyLocalizationToolEditor/Private/ELTEditor.cpp b/Source/EasyLocalizationToolEditor/Private/ELTEditor.cpp index e4dfc65..16e1de2 100644 --- a/Source/EasyLocalizationToolEditor/Private/ELTEditor.cpp +++ b/Source/EasyLocalizationToolEditor/Private/ELTEditor.cpp @@ -3,12 +3,15 @@ #include "ELTEditor.h" #include "Internationalization/TextLocalizationResource.h" #include "Internationalization/TextLocalizationManager.h" +#include "Internationalization/StringTableCore.h" +#include "Internationalization/StringTable.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Misc/MessageDialog.h" #include "ELTEditorSettings.h" #include "ELTEditorWidget.h" #include "ELTSettings.h" +#include "UObject/SavePackage.h" #if ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 1)) #include "AssetRegistry/AssetRegistryModule.h" @@ -162,6 +165,7 @@ void UELTEditor::InitializeTheWidget() EditorWidget->OnGlobalNamespaceChangedDelegate.BindUObject(this, &UELTEditor::OnGlobalNamespaceChanged); EditorWidget->OnSeparatorChangedDelegate.BindUObject(this, &UELTEditor::OnSeparatorChanged); EditorWidget->OnFallbackWhenEmptyChangedDelegate.BindUObject(this, &UELTEditor::OnFallbackWhenEmptyChanged); + EditorWidget->OnGenerateKeyReferenceStringTableChangedDelegate.BindUObject(this, &UELTEditor::OnGenerateKeyReferenceStringTableChanged); EditorWidget->OnLogDebugChangedDelegate.BindUObject(this, &UELTEditor::OnLogDebugChanged); EditorWidget->OnPreviewInUIChangedDelegate.BindUObject(this, &UELTEditor::OnPreviewInUIChanged); @@ -198,6 +202,9 @@ void UELTEditor::InitializeTheWidget() EditorWidget->CallSetLocalizationOnFirstRun(UELTSettings::GetOverrideLanguageAtFirstLaunch()); EditorWidget->CallSetLocalizationOnFirstRunLang(UELTSettings::GetLanguageToOverrideAtFirstLaunch()); + // Set the Generate Key Reference String Table current value to the Widget. + EditorWidget->CallSetGenerateKeyReferenceStringTable(UELTEditorSettings::GetGenerateKeyReferenceStringTable()); + // Set LogDebug value to the Widget. EditorWidget->CallSetLogDebug(UELTEditorSettings::GetLogDebug()); @@ -271,7 +278,7 @@ void UELTEditor::OnGenerateLocFiles() } // Display a Dialog Window to inform user that the localization generation has been finished. -#if (ENGINE_MAJOR_VERSION == 5) +#if (ENGINE_MAJOR_VERSION == 5) && ENGINE_MINOR_VERSION >= 3 FMessageDialog::Open((bSuccess ? EAppMsgCategory::Success : EAppMsgCategory::Error), EAppMsgType::Ok, FText::FromString(ReturnMessage)); #else FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(ReturnMessage)); @@ -357,6 +364,11 @@ void UELTEditor::OnFallbackWhenEmptyChanged(const FString& NewFallback) UELTEditorSettings::SetFallbackWhenEmpty(NewFallback); } +void UELTEditor::OnGenerateKeyReferenceStringTableChanged(bool bNewGenerateKeyReferenceStringTable) +{ + UELTEditorSettings::SetGenerateKeyReferenceStringTable(bNewGenerateKeyReferenceStringTable); +} + void UELTEditor::OnLogDebugChanged(bool bNewLogDebug) { // "Log Debug" flag has been changed in the Widget. Save this setting. @@ -465,12 +477,12 @@ bool UELTEditor::GenerateLocFiles(FString& OutMessage) } const TArray& CSVFilePaths = PathsStringToList(GetCurrentCSVPath()); const FString LocPath = FPaths::ConvertRelativePathToFull(CurrentLocPath); - return GenerateLocFilesImpl(CSVFilePaths, LocPath, GetCurrentLocName(), GetCurrentGlobalNamespace(), UELTEditorSettings::GetSeparator(), UELTEditorSettings::GetFallbackWhenEmpty(), OutMessage); + return GenerateLocFilesImpl(CSVFilePaths, LocPath, GetCurrentLocName(), GetCurrentGlobalNamespace(), UELTEditorSettings::GetSeparator(), UELTEditorSettings::GetFallbackWhenEmpty(), UELTEditorSettings::GetGenerateKeyReferenceStringTable(), OutMessage); } -bool UELTEditor::GenerateLocFilesImpl(const FString& CSVPaths, const FString& LocPath, const FString& LocName, const FString& GlobalNamespace, const FString& Separator, const FString& FallbackWhenEmpty, FString& OutMessage) +bool UELTEditor::GenerateLocFilesImpl(const FString& CSVPaths, const FString& LocPath, const FString& LocName, const FString& GlobalNamespace, const FString& Separator, const FString& FallbackWhenEmpty, bool bGenerateStringTables, FString& OutMessage) { - return GenerateLocFilesImpl(PathsStringToList(CSVPaths), LocPath, LocName, GlobalNamespace, Separator, FallbackWhenEmpty, OutMessage); + return GenerateLocFilesImpl(PathsStringToList(CSVPaths), LocPath, LocName, GlobalNamespace, Separator, FallbackWhenEmpty, bGenerateStringTables, OutMessage); } // Define the type of behavior when the localized string in CSV is empty and the fallback value should be used. @@ -481,8 +493,16 @@ enum class EFallbackWhenEmptyType : uint8 KEY }; -bool UELTEditor::GenerateLocFilesImpl(const TArray& CSVPaths, const FString& LocPath, const FString& LocName, const FString& GlobalNamespace, const FString& Separator, const FString& FallbackWhenEmpty, FString& OutMessage) +bool UELTEditor::GenerateLocFilesImpl(const TArray& CSVPaths, const FString& LocPath, const FString& LocName, const FString& GlobalNamespace, const FString& Separator, const FString& FallbackWhenEmpty, bool bGenerateStringTables, FString& OutMessage) { + // Validate the input data first. If something is wrong - return false and set the OutMessage with the error description. + if (CSVPaths.Num() == 0) + { + OutMessage = TEXT("ERROR: No CSV files provided! Please provide at least one CSV file to generate localization files."); + return false; + } + + // Validate the Separator value. It must be exactly 1 character. if (Separator.Len() != 1) { OutMessage = FString::Printf(TEXT("ERROR: The Separator is invalid. Must be exactly 1 character. Current Separator = %s"), *Separator); @@ -500,158 +520,286 @@ bool UELTEditor::GenerateLocFilesImpl(const TArray& CSVPaths, const FSt FallbackWhenEmptyType = EFallbackWhenEmptyType::KEY; } + // Prepare locmeta file name. const FString MetaFileName = LocPath / LocName + TEXT(".locmeta"); + + // Cache the info if we want to print debug logs. const bool bLogDebug = UELTEditorSettings::GetLogDebug(); - bool bFirstCSV = true; - TMap LocReses; - for (const FString& CSVPath : CSVPaths) + + // Prepare containers for localization informations we will use later. + TMap LocReses; // Actual LocRes for each language. + TMap> NamespaceToKeysMap; // List of keys for each namespace (used for generating String Tables). +#if ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 8)) + TMap> NamespaceToKeysToNotesMap; // List of keys with dev notes for each namespace (used for generating String Tables with dev notes). +#endif + + // Go thorugh all CVS files. + for (int32 CSVIdx = 0; CSVIdx < CSVPaths.Num(); CSVIdx++) { - const FString CSVFilePath = FPaths::ConvertRelativePathToFull(CSVPath); + // Get the full path to the CSV file. + const FString CSVFilePath = FPaths::ConvertRelativePathToFull(CSVPaths[CSVIdx]); if (bLogDebug) { UE_LOG(ELTEditorLog, Log, TEXT("Parsing file: %s"), *CSVFilePath); } + FCSVReader Reader; - if (Reader.LoadFromFile(CSVFilePath, (*Separator)[0], OutMessage)) + if (Reader.LoadFromFile(CSVFilePath, (*Separator)[0], OutMessage) == false) + { + OutMessage = FString::Printf(TEXT("ERROR: Failed to load CSV file (%s)! Error: %s"), *CSVFilePath, *OutMessage); + return false; + } + + // Get the Idx for the columns that has namespace, devnotes and keys. Validate if the CSV has proper structure. + int32 NamespaceColumn = INDEX_NONE; + int32 DevNotesColumn = INDEX_NONE; + int32 KeysColumn = INDEX_NONE; + const TArray Columns = Reader.Columns; + for (int32 ColumnIdx = 0; ColumnIdx < Columns.Num(); ColumnIdx++) { - int32 FirstColumn = 0; - bool bHasNamespaces = false; + if (Columns[ColumnIdx].Values.Num() == 0) + { + OutMessage = FString::Printf(TEXT("ERROR: The Column (%i) in CSV (%s) is empty!"), ColumnIdx, *(CSVPaths[CSVIdx])); + return false; + } - const TArray Columns = Reader.Columns; - for (const FCSVColumn& Column : Columns) + FString Header = Columns[ColumnIdx].Values[0].TrimStartAndEnd(); + if (Header.Equals(TEXT("namespace"), ESearchCase::IgnoreCase)) { - if (Column.Values[0].Equals(TEXT("namespace"), ESearchCase::IgnoreCase)) + if (NamespaceColumn != INDEX_NONE) { - bHasNamespaces=true; - break; + OutMessage = FString::Printf(TEXT("ERROR: Invalid CSV structure in file (%s)! Multiple 'namespace' columns found!"), *(CSVPaths[CSVIdx])); + return false; } - if (Column.Values[0].Equals(TEXT("key"), ESearchCase::IgnoreCase)) + NamespaceColumn = ColumnIdx; + } + else if (Header.Equals(TEXT("devnotes"), ESearchCase::IgnoreCase)) + { + if (DevNotesColumn != INDEX_NONE) { - break; + OutMessage = FString::Printf(TEXT("ERROR: Invalid CSV structure in file (%s)! Multiple 'devnotes' columns found!"), *(CSVPaths[CSVIdx])); + return false; } - ++FirstColumn; + DevNotesColumn = ColumnIdx; } - - if (Columns.Num() > (FirstColumn + 1)) + else if (Header.Equals(TEXT("key"), ESearchCase::IgnoreCase)) + { + if (KeysColumn != INDEX_NONE) + { + OutMessage = FString::Printf(TEXT("ERROR: Invalid CSV structure in file (%s)! Multiple 'key' columns found!"), *(CSVPaths[CSVIdx])); + return false; + } + KeysColumn = ColumnIdx; + } + else { - const int32 NumOfValues = Columns[FirstColumn].Values.Num(); - for (int32 CIdx = FirstColumn + 1; CIdx < Columns.Num(); CIdx++) + Header.ReplaceCharInline(TEXT('_'), TEXT('-')); + if (Header.RemoveFromStart(TEXT("lang-"), ESearchCase::IgnoreCase)) { - if (Columns[CIdx].Values.Num() != NumOfValues) + if (KeysColumn == INDEX_NONE) { - OutMessage = FString::Printf(TEXT("ERROR: Invalid CSV! Column %i (counting from 1) has %i values while Column 1 has %i values. Every Column must have the same amount of values!"), CIdx+1, Columns[CIdx].Values.Num(), NumOfValues); + OutMessage = FString::Printf(TEXT("ERROR: Invalid CSV structure in file (%s)! Language column found before key column!"), *(CSVPaths[CSVIdx])); return false; } } + } + } - // Potential place for namespaces. - const FCSVColumn& Namespaces = Columns[FirstColumn]; + // Make sure we have keys column. + if (KeysColumn == INDEX_NONE) + { + OutMessage = TEXT("ERROR: Invalid CSV! Key column not found!"); + return false; + } - // Check if we have namespaces defined for every key or to use global value. - const bool bUseGlobalNamespace = (bHasNamespaces == false) && (GlobalNamespace.IsEmpty() == false); + // Ensure namespace/devnotes (if present) are located before the key column. + if ((NamespaceColumn != INDEX_NONE && NamespaceColumn > KeysColumn) || + (DevNotesColumn != INDEX_NONE && DevNotesColumn > KeysColumn)) + { + OutMessage = TEXT("ERROR: Invalid CSV! The 'namespace' and 'devnotes' columns must be before the 'key' column!"); + return false; + } - if (bUseGlobalNamespace == false && bHasNamespaces == false) - { - OutMessage = TEXT("ERROR: Namespaces in CSV not found!"); - return false; - } + // Check if there are more columns after keys column. If not - we don't have any language columns and we can't generate loc files. + if (Columns.Num() <= KeysColumn + 1) + { + OutMessage = TEXT("ERROR: Invalid CSV! There are no lang columns after key column!"); + return false; + } + + // Validate if all columns have the same number of values. If not - we have invalid CSV structure and we can't generate loc files. + const int32 NumOfValues = Columns[KeysColumn].Values.Num(); + for (int32 CIdx = KeysColumn + 1; CIdx < Columns.Num(); CIdx++) + { + if (Columns[CIdx].Values.Num() != NumOfValues) + { + OutMessage = FString::Printf(TEXT("ERROR: Invalid CSV! Column %i (counting from 1) has %i values while Column 1 has %i values. Every Column must have the same amount of values!"), CIdx + 1, Columns[CIdx].Values.Num(), NumOfValues); + return false; + } + } + + // Potential place for namespaces (if the column exists). + const FCSVColumn& Namespaces = (NamespaceColumn != INDEX_NONE) ? Columns[NamespaceColumn] : FCSVColumn(); + + // Check if we want to use global namespace (there is no namespace column and global namespace value is empty). + const bool bUseGlobalNamespace = (NamespaceColumn == INDEX_NONE) && (GlobalNamespace.IsEmpty() == false); + + // We don't want to use global namespace but we don't have namespace column - it's an error. + if (bUseGlobalNamespace == false && (NamespaceColumn == INDEX_NONE)) + { + OutMessage = TEXT("ERROR: Namespaces in CSV not found!"); + return false; + } + + // Potential place for devnotes. + const FCSVColumn& DevNotes = (DevNotesColumn != INDEX_NONE) ? Columns[DevNotesColumn] : FCSVColumn(); - // Clear the localization directory first. - if (bFirstCSV) + // Clear the localization directory first, preserving any .uasset files (e.g. string table assets). + // Deleting .uasset files while the corresponding UPackage is still in memory invalidates the async loader's package tracking and causes an assertion on the next reimport. + if (CSVIdx == 0) + { + // Ensure we are not deleting any important files by checking if we are in Content directory and the Meta file is there exists. + if (LocPath.Contains("Content") && IFileManager::Get().FileExists(*MetaFileName)) + { + TArray FilesToDelete; + IFileManager::Get().FindFilesRecursive(FilesToDelete, *LocPath, TEXT("*"), true, false); + for (const FString& File : FilesToDelete) { - // Ensure we are not deleting any important files by checking if we are in Content directory and the Meta file is there exists. - if (LocPath.Contains("Content") && IFileManager::Get().FileExists(*MetaFileName)) + if (File.EndsWith(TEXT(".uasset")) == false) { - IFileManager::Get().DeleteDirectory(*LocPath, false, true); + IFileManager::Get().Delete(*File); } } + } + } + + // Get the keys column. We know it's valid because we've already validated it. + const FCSVColumn& Keys = Columns[KeysColumn]; - // Keys will be in first row if not having namespaces. - const FCSVColumn& Keys = Columns[bHasNamespaces ? FirstColumn+1 : FirstColumn]; - if (Keys.Values[0].Equals(TEXT("key"), ESearchCase::IgnoreCase) == false) +#if ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 8)) + // Gather Dev Notes if available + if (DevNotesColumn != INDEX_NONE) + { + for (int32 Key = 1; Key < Keys.Values.Num(); Key++) + { + FString DevNote = DevNotes.Values[Key]; + if (DevNote.IsEmpty() == false) { - OutMessage = TEXT("ERROR: Key column in CSV not found!"); - return false; + const FString& Namespace = (bUseGlobalNamespace || Namespaces.Values[Key].IsEmpty()) ? GlobalNamespace : Namespaces.Values[Key]; + if (Namespace.IsEmpty()) + { + OutMessage = FString::Printf(TEXT("ERROR: Namespace in row %i (counting from 1) for dev note is empty!"), Key); + return false; + } + NamespaceToKeysToNotesMap.FindOrAdd(Namespace).Add(Keys.Values[Key], DevNote); } + } + } +#endif - if (bLogDebug) + if (bLogDebug) + { + UE_LOG(ELTEditorLog, Log, TEXT("Adding Entries")); + UE_LOG(ELTEditorLog, Log, TEXT("[Lang] | [Namespace] | [Key] | [Value]")); + } + + // Prepare to cache the index of the first language column. We will need it if the FallbackWhenEmptyType is set to FIRST_LANG. + int32 FirstLangColumn = INDEX_NONE; + + // Go through all language columns and add entries to proper LocRes. + for (int32 Column = KeysColumn + 1; Column < Columns.Num(); Column++) + { + // Get the column with the localized values. + const FCSVColumn& Locs = Columns[Column]; + + // Read the language code. We know the number of values in this column is the same as in keys column because we've already validated it. + FString Lang = Locs.Values[0].ToLower(); + + // Replace underscores with hyphens to match the expected format. + Lang.ReplaceCharInline('_', '-'); + + // Remove "lang-" prefix if exists to get the actual language code. + // If the prefix is not there - it's an invalid CSV structure, because all language columns must start with "lang-" prefix. + if (Lang.RemoveFromStart(TEXT("lang-")) == false) + { + continue; + } + + // This is our first language column, cache its index. + if (FirstLangColumn == INDEX_NONE) + { + FirstLangColumn = Column; + } + + // Add LocRes for this language if it doesn't exist yet. + if (LocReses.Contains(Lang) == false) + { + LocReses.Add(Lang, FTextLocalizationResource()); + } + FTextLocalizationResource& LocRes = LocReses[Lang]; + + // For each key add the entry to the LocRes. + for (int32 Key = 1; Key < Keys.Values.Num(); Key++) + { + // Get correct Namespace. + // If we want to use global namespace - use global namespace. + // If there is no namespace specified in this column - use global namespace. + // If there is namespace specified - use it. + // Getting namespace value is safe, because we've already validated that. + const FString& Namespace = (bUseGlobalNamespace || Namespaces.Values[Key].IsEmpty()) ? GlobalNamespace : Namespaces.Values[Key]; + if (Namespace.IsEmpty()) { - UE_LOG(ELTEditorLog, Log, TEXT("Adding Entries")); - UE_LOG(ELTEditorLog, Log, TEXT("[Lang] | [Namespace] | [Key] | [Value]")); + OutMessage = FString::Printf(TEXT("ERROR: Namespace in row %i (counting from 1) is empty!"), Key); + return false; } - const int32 FirstLangColumn = bHasNamespaces ? (FirstColumn + 2) : (FirstColumn + 1); - for (int32 Column = FirstLangColumn; Column < Columns.Num(); Column++) + // If the localized string is empty and the fallback option is set - use the fallback value. + FString LocalizedString = Locs.Values[Key]; + if (FallbackWhenEmptyType != EFallbackWhenEmptyType::NONE) { - const FCSVColumn& Locs = Columns[Column]; - FString Lang = Locs.Values[0].ToLower(); - Lang.ReplaceCharInline('_', '-'); - if (Lang.RemoveFromStart(TEXT("lang-"))) + if (LocalizedString.TrimStartAndEnd().IsEmpty()) { - if (LocReses.Contains(Lang) == false) - { - LocReses.Add(Lang, FTextLocalizationResource()); - } - FTextLocalizationResource& LocRes = LocReses[Lang]; - for (int32 Key = 1; Key < Keys.Values.Num(); Key++) + if (FallbackWhenEmptyType == EFallbackWhenEmptyType::FIRST_LANG) { - const FString& Namespace = (bUseGlobalNamespace || Namespaces.Values[Key].IsEmpty()) ? GlobalNamespace : Namespaces.Values[Key]; - if (Namespace.IsEmpty()) + // Use the first language column value as fallback. + if (Columns.IsValidIndex(FirstLangColumn)) { - OutMessage = FString::Printf(TEXT("ERROR: Namespace in row %i (counting from 1) is empty!"), Key); - return false; + LocalizedString = Columns[FirstLangColumn].Values[Key]; } - // If the localized string is empty and the fallback option is set - use the fallback value. - FString LocalizedString = Locs.Values[Key]; - if (FallbackWhenEmptyType != EFallbackWhenEmptyType::NONE) + // If the first language value is also empty - use the key as a fallback. + if (LocalizedString.TrimStartAndEnd().IsEmpty()) { - if (LocalizedString.TrimStartAndEnd().IsEmpty()) - { - if (FallbackWhenEmptyType == EFallbackWhenEmptyType::FIRST_LANG) - { - LocalizedString = Columns[FirstLangColumn].Values[Key]; - - // If the first language value is also empty - use the key as a fallback. - if (LocalizedString.TrimStartAndEnd().IsEmpty()) - { - LocalizedString = Keys.Values[Key]; - } - } - else if (FallbackWhenEmptyType == EFallbackWhenEmptyType::KEY) - { - LocalizedString = Keys.Values[Key]; - } - } + LocalizedString = Keys.Values[Key]; } - - if (bLogDebug) - { - UE_LOG(ELTEditorLog, Log, TEXT("%s | %s | %s | %s"), *Lang, *Namespace, *(Keys.Values[Key]), *LocalizedString); - } - - LocRes.AddEntry( - FTextKey(Namespace), - FTextKey(Keys.Values[Key]), - Keys.Values[Key], - LocalizedString, - 0); + } + else if (FallbackWhenEmptyType == EFallbackWhenEmptyType::KEY) + { + LocalizedString = Keys.Values[Key]; } } } + + if (bLogDebug) + { + UE_LOG(ELTEditorLog, Log, TEXT("%s | %s | %s | %s"), *Lang, *Namespace, *(Keys.Values[Key]), *LocalizedString); + } - bFirstCSV = false; - } - else - { - OutMessage = TEXT("ERROR: CSV has not enough Columns!"); - return false; + // Finally, we can add the LocRes entry! + LocRes.AddEntry( + FTextKey(Namespace), + FTextKey(Keys.Values[Key]), + Keys.Values[Key], + LocalizedString, + 0); + + // If we want to generate string tables with key references - cache the key for this namespace. We will use it later to generate string tables. + if (bGenerateStringTables && (Keys.Values[Key].IsEmpty() == false)) + { + NamespaceToKeysMap.FindOrAdd(Namespace).Add(Keys.Values[Key]); + } } } - else - { - return false; - } } // LocMeta must be created for every localization path. @@ -674,6 +822,111 @@ bool UELTEditor::GenerateLocFilesImpl(const TArray& CSVPaths, const FSt LocRes.Value.SaveToFile(LocFileName); } + // Generate Key Reference String Table + if (bGenerateStringTables && NamespaceToKeysMap.Num() > 0) + { + for (const auto& KVP : NamespaceToKeysMap) + { + const FString& Namespace = KVP.Key; + const TSet& Keys = KVP.Value; + + FString AssetName = FString::Printf(TEXT("ELT_KeyReferences_%s_%s"), *LocName, *Namespace); + FString PackagePath = FPackageName::FilenameToLongPackageName(LocPath / AssetName); + + // If the package is already in memory (e.g. from a previous reimport), use it directly. + UPackage* Package = FindPackage(nullptr, *PackagePath); + if (Package == nullptr) + { + // If the package is not in memory - check if it exists on disk. If it exists - load it, if not - create a new one. + Package = FPackageName::DoesPackageExist(*PackagePath) ? LoadPackage(nullptr, *PackagePath, LOAD_None) : CreatePackage(*PackagePath); + } + + // If we failed to find, load or create the package - return an error. + if (Package == nullptr) + { + OutMessage = FString::Printf(TEXT("ERROR: Failed to create package path for StringTable: %s"), *PackagePath); + return false; + } + + // Clear any existing StringTable that resides in memory before creating a new one. + if (UStringTable* ExistingStringTableAsset = FindObject(Package, *AssetName)) + { + ExistingStringTableAsset->ClearFlags(RF_Public | RF_Standalone); +#if (ENGINE_MAJOR_VERSION == 5) + ExistingStringTableAsset->MarkAsGarbage(); +#else + ExistingStringTableAsset->MarkPendingKill(); +#endif + } + + // Create new StringTable asset in memory. If we fail - return an error. + UStringTable* StringTableAsset = NewObject(Package, UStringTable::StaticClass(), FName(*AssetName), (RF_Public | RF_Standalone | RF_Transactional)); + if (StringTableAsset == nullptr) + { + OutMessage = FString::Printf(TEXT("ERROR: Failed to create StringTable asset: %s"), *AssetName); + return false; + } + FAssetRegistryModule::AssetCreated(StringTableAsset); + Package->MarkPackageDirty(); + + // Setup StringTable asset with keys as source strings. We will use keys as localized strings too, so the value is the same as the key. + // Clear the StringTable asset from any existing source strings before adding new ones, so we can properly update the existing asset on reimport. + FStringTableRef StringTableRef = StringTableAsset->GetMutableStringTable(); + StringTableRef->SetNamespace(Namespace); + StringTableRef->ClearSourceStrings(); + +#if (ENGINE_MAJOR_VERSION == 5) + #if (ENGINE_MINOR_VERSION >= 8) + // For UE5.8 and newer add source strings to the String Table alongside with dev notes if they are available in the CSV. + TMap* KeysToNotes = NamespaceToKeysToNotesMap.Find(Namespace); + for (const FString& Key : Keys) + { + FString DevNotes = TEXT(""); + if (FString* Note = KeysToNotes ? KeysToNotes->Find(Key) : nullptr) + { + DevNotes = *Note; + } + + StringTableRef->SetSourceString(FTextKey(Key), Key, DevNotes); + } + #else + // For UE5.0 - UE5.7 add source strings to the String Table without dev notes. + for (const FString& Key : Keys) + { + StringTableRef->SetSourceString(FTextKey(Key), Key); + } + #endif +#else + // For UE4 add source strings to the String Table, but with different function signature. + for (const FString& Key : Keys) + { + StringTableRef->SetSourceString(Key, Key); + } +#endif + + // Save the package with the StringTable asset to disk. If we fail - return an error. + FString PackageFileName = FPackageName::LongPackageNameToFilename(PackagePath, FPackageName::GetAssetPackageExtension()); + FSavePackageArgs SaveArgs; + SaveArgs.TopLevelFlags = RF_Public | RF_Standalone; + SaveArgs.Error = GError; + +#if (ENGINE_MAJOR_VERSION == 5) + if (UPackage::SavePackage(Package, StringTableAsset, *PackageFileName, SaveArgs) == false) +#else + if (UPackage::SavePackage(Package, StringTableAsset, SaveArgs.TopLevelFlags, *PackageFileName, SaveArgs.Error) == false) +#endif + { + OutMessage = FString::Printf(TEXT("ERROR: Failed to save StringTable package file to disk path: %s"), *PackageFileName); + return false; + } + + if (bLogDebug) + { + UE_LOG(ELTEditorLog, Log, TEXT("Saved String Table Asset: %s"), *PackageFileName); + } + } + } + OutMessage = TEXT("SUCCESS: Localization import complete!"); return true; } diff --git a/Source/EasyLocalizationToolEditor/Private/ELTEditorSettings.cpp b/Source/EasyLocalizationToolEditor/Private/ELTEditorSettings.cpp index 6a74ded..c7b1704 100644 --- a/Source/EasyLocalizationToolEditor/Private/ELTEditorSettings.cpp +++ b/Source/EasyLocalizationToolEditor/Private/ELTEditorSettings.cpp @@ -72,6 +72,16 @@ void UELTEditorSettings::SetReimportAtEditorStartup(bool bNewReimportAtEditorSta ELTE_SET_SETTING(bReimportAtEditorStartup, bNewReimportAtEditorStartup); } +bool UELTEditorSettings::GetGenerateKeyReferenceStringTable() +{ + ELTE_GET_SETTING(bGenerateKeyReferenceStringTable); +} + +void UELTEditorSettings::SetGenerateKeyReferenceStringTable(bool bNewGenerateKeyReferenceStringTable) +{ + ELTE_SET_SETTING(bGenerateKeyReferenceStringTable, bNewGenerateKeyReferenceStringTable); +} + bool UELTEditorSettings::GetPreviewInUIEnabled() { ELTE_GET_SETTING(bPreviewInUI); diff --git a/Source/EasyLocalizationToolEditor/Private/ELTEditorWidget.cpp b/Source/EasyLocalizationToolEditor/Private/ELTEditorWidget.cpp index c3d0b07..af514c3 100644 --- a/Source/EasyLocalizationToolEditor/Private/ELTEditorWidget.cpp +++ b/Source/EasyLocalizationToolEditor/Private/ELTEditorWidget.cpp @@ -153,6 +153,14 @@ void UELTEditorWidget::CallSetLocalizationOnFirstRun(bool LocalizationOnFirstRun #endif } +void UELTEditorWidget::CallSetGenerateKeyReferenceStringTable(bool bGenerateKeyReferenceStringTable) +{ + if (MyWidget.IsValid()) + { + MyWidget->SetGenerateKeyReferenceStringTable(bGenerateKeyReferenceStringTable); + } +} + void UELTEditorWidget::CallSetLocalizationOnFirstRunLang(const FString& OnFirstRunLang) { #if ELTEDITOR_USE_SLATE_EDITOR_UI @@ -281,6 +289,11 @@ void UELTEditorWidget::OnLocalizationOnFirstRunLangChanged(const FString& OnFirs OnLocalizationOnFirstRunLangChangedDelegate.ExecuteIfBound(OnFirstRunLang); } +void UELTEditorWidget::OnGenerateKeyReferenceStringTableChanged(bool bGenerateKeyReferenceStringTable) +{ + OnGenerateKeyReferenceStringTableChangedDelegate.ExecuteIfBound(bGenerateKeyReferenceStringTable); +} + void UELTEditorWidget::OnGlobalNamespaceChanged(const FString& NewGlobalNamespace) { OnGlobalNamespaceChangedDelegate.ExecuteIfBound(NewGlobalNamespace); diff --git a/Source/EasyLocalizationToolEditor/Private/SELTEditorWidget.cpp b/Source/EasyLocalizationToolEditor/Private/SELTEditorWidget.cpp index 5f59a13..69ce160 100644 --- a/Source/EasyLocalizationToolEditor/Private/SELTEditorWidget.cpp +++ b/Source/EasyLocalizationToolEditor/Private/SELTEditorWidget.cpp @@ -16,9 +16,14 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) FallbackWhenEmptyAvailable.Add(MakeShareable(new FString(TEXT("KEY")))); SelectedFallbackWhenEmpty = FallbackWhenEmptyAvailable[0]; - SpacerBrush.SetImageSize(FVector2D(350.f, 1.f)); + SpacerBrush.SetImageSize(FVector2D(400.f, 1.f)); SpacerBrush.TintColor = FSlateColor(FLinearColor(.62f,.62f,.62f,1.f)); + const float WidthMargin = 300.f; + const float TextBlockMaxWidth = 100.f; + const float SpacerPadding = 8.f; + const float BoxesHeight = 24.f; + SUserWidget::Construct(SUserWidget::FArguments() [ SNew(SConstraintCanvas) @@ -53,12 +58,39 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) // > Spacer ================ +SVerticalBox::Slot() .AutoHeight() - .Padding(FMargin(0.f, 4.f, 0.f, 4.f)) + .Padding(FMargin(0.f, SpacerPadding, 0.f, SpacerPadding)) .HAlign(EHorizontalAlignment::HAlign_Left) .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SImage).Image(&SpacerBrush) ] + // >>>> Localization Name box + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + // >>>>>>>> Localization Name label + +SHorizontalBox::Slot() + .AutoWidth() + .Padding(FMargin(0.f, 0.f, 20.f, 0.f)) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Localization name:")) + ] + // >>>>>>>> Localization Name value + +SHorizontalBox::Slot() + .AutoWidth() + .Padding(FMargin(0.f, 0.f, 20.f, 0.f)) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 12)) + .Text_Lambda([this]() -> FText + { + return FText::FromString(CurrentLocName); + }) + ] + ] // > Localization Paths Box +SVerticalBox::Slot() .AutoHeight() @@ -97,38 +129,11 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) }) ] ] - // >>>> Localization Name box - +SVerticalBox::Slot() - .AutoHeight() - [ - SNew(SHorizontalBox) - // >>>>>>>> Localization Name label - +SHorizontalBox::Slot() - .AutoWidth() - .Padding(FMargin(0.f, 0.f, 20.f, 0.f)) - [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) - .Text(INVTEXT("Localization name:")) - ] - // >>>>>>>> Localization Name value - +SHorizontalBox::Slot() - .AutoWidth() - .Padding(FMargin(0.f, 0.f, 20.f, 0.f)) - [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Bold", 12)) - .Text_Lambda([this]() -> FText - { - return FText::FromString(CurrentLocName); - }) - ] - ] ] // > Spacer ================ +SVerticalBox::Slot() .AutoHeight() - .Padding(FMargin(0.f, 4.f, 0.f, 4.f)) + .Padding(FMargin(0.f, SpacerPadding, 0.f, SpacerPadding)) .HAlign(EHorizontalAlignment::HAlign_Left) .VAlign(EVerticalAlignment::VAlign_Center) [ @@ -163,6 +168,7 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) // > Available Langs Box ================ +SVerticalBox::Slot() .AutoHeight() + .Padding(FMargin(0.f, 4.f, 0.f, 0.f)) [ SNew(SVerticalBox) .ToolTipText(INVTEXT("List of language codes that are implemented by every localization directory.")) @@ -189,7 +195,7 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) // > Spacer ================ +SVerticalBox::Slot() .AutoHeight() - .Padding(FMargin(0.f, 4.f, 0.f, 4.f)) + .Padding(FMargin(0.f, SpacerPadding, 0.f, SpacerPadding)) .HAlign(EHorizontalAlignment::HAlign_Left) .VAlign(EVerticalAlignment::VAlign_Center) [ @@ -199,96 +205,116 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) +SVerticalBox::Slot() .AutoHeight() [ - SNew(SHorizontalBox) - .ToolTipText(INVTEXT("Reimports the lastly selected localization with the last used CSV file when editor starts.")) - // >>>> Reimport on editor startup Label - +SHorizontalBox::Slot() - .AutoWidth() + SNew(SBox) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) - .Text(INVTEXT("Reimport on editor startup:")) - ] - // >>>> Reimport on editor startup checkbox - +SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SCheckBox) - .IsChecked_Lambda([this]() -> ECheckBoxState - { - return bReimportAtEditorStartup_Chkbox ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([this](ECheckBoxState InCheckBoxState) -> void - { - bReimportAtEditorStartup_Chkbox = (InCheckBoxState == ECheckBoxState::Checked); - if (WidgetController.IsValid()) + SNew(SHorizontalBox) + .ToolTipText(INVTEXT("Reimports the lastly selected localization with the last used CSV file when editor starts.")) + // >>>> Reimport on editor startup Label + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Reimport on editor startup:")) + ] + ] + + + // >>>> Reimport on editor startup checkbox + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SCheckBox) + .IsChecked_Lambda([this]() -> ECheckBoxState { - WidgetController->OnReimportAtEditorStartupChanged(bReimportAtEditorStartup_Chkbox); - } - }) + return bReimportAtEditorStartup_Chkbox ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([this](ECheckBoxState InCheckBoxState) -> void + { + bReimportAtEditorStartup_Chkbox = (InCheckBoxState == ECheckBoxState::Checked); + if (WidgetController.IsValid()) + { + WidgetController->OnReimportAtEditorStartupChanged(bReimportAtEditorStartup_Chkbox); + } + }) + ] ] ] // > Localization Preview Box ================ +SVerticalBox::Slot() .AutoHeight() [ - SNew(SHorizontalBox) - .ToolTipText(INVTEXT("Enabled the preview of the localization in the editor.")) - // >>>> Localization Preview Label - +SHorizontalBox::Slot() - .AutoWidth() + SNew(SBox) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) - .Text(INVTEXT("Localization Preview:")) - ] - // >>>> Localization Preview Checkbox - +SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SCheckBox) - .IsChecked_Lambda([this]() -> ECheckBoxState - { - return bIsLocalisationPreviewEnabled_Chkbox ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([this](ECheckBoxState InCheckBoxState) -> void - { - bIsLocalisationPreviewEnabled_Chkbox = (InCheckBoxState == ECheckBoxState::Checked); - if (WidgetController.IsValid()) + SNew(SHorizontalBox) + .ToolTipText(INVTEXT("Enabled the preview of the localization in the editor.")) + // >>>> Localization Preview Label + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Localization Preview:")) + ] + ] + // >>>> Localization Preview Checkbox + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SCheckBox) + .IsChecked_Lambda([this]() -> ECheckBoxState { - WidgetController->OnLocalizationPreviewChanged(bIsLocalisationPreviewEnabled_Chkbox); - } - }) - ] - // >>>> Localization Preview List - +SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SComboBox>) - .OptionsSource(&PreviewsAvailables) - .OnGenerateWidget_Lambda([this](TSharedPtr InItem) -> TSharedRef - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda([this](TSharedPtr Item, ESelectInfo::Type SelectInfo) -> void - { - SelectedPreviewLang = Item; - if (WidgetController.IsValid()) + return bIsLocalisationPreviewEnabled_Chkbox ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([this](ECheckBoxState InCheckBoxState) -> void { - WidgetController->OnLocalizationPreviewLangChanged(*Item); - } - }) + bIsLocalisationPreviewEnabled_Chkbox = (InCheckBoxState == ECheckBoxState::Checked); + if (WidgetController.IsValid()) + { + WidgetController->OnLocalizationPreviewChanged(bIsLocalisationPreviewEnabled_Chkbox); + } + }) + ] + // >>>> Localization Preview List + +SHorizontalBox::Slot() + .AutoWidth() [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Regular", 10)) - .Text_Lambda([this]() -> FText + SNew(SComboBox>) + .OptionsSource(&PreviewsAvailables) + .OnGenerateWidget_Lambda([this](TSharedPtr InItem) -> TSharedRef + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda([this](TSharedPtr Item, ESelectInfo::Type SelectInfo) -> void + { + SelectedPreviewLang = Item; + if (WidgetController.IsValid()) { - if (SelectedPreviewLang.IsValid()) + WidgetController->OnLocalizationPreviewLangChanged(*Item); + } + }) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 10)) + .Text_Lambda([this]() -> FText { - return FText::FromString(*SelectedPreviewLang); - } - return FText::GetEmpty(); - }) + if (SelectedPreviewLang.IsValid()) + { + return FText::FromString(*SelectedPreviewLang); + } + return FText::GetEmpty(); + }) + ] ] ] ] @@ -296,96 +322,114 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) +SVerticalBox::Slot() .AutoHeight() [ - SNew(SHorizontalBox) - .ToolTipText(INVTEXT("If enabled it won't save and load lastly set language automatically.")) - // >>>> Manually Set Last Language Label - +SHorizontalBox::Slot() - .AutoWidth() + SNew(SBox) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) - .Text(INVTEXT("Manually Set Last Language:")) - ] - // >>>> Manually Set Last Language checkbox - +SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SCheckBox) - .IsChecked_Lambda([this]() -> ECheckBoxState - { - return bManuallySetLastLanguage_Chkbox ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([this](ECheckBoxState InCheckBoxState) -> void - { - bManuallySetLastLanguage_Chkbox = (InCheckBoxState == ECheckBoxState::Checked); - if (WidgetController.IsValid()) + SNew(SHorizontalBox) + .ToolTipText(INVTEXT("If enabled it won't save and load lastly set language automatically.")) + // >>>> Manually Set Last Language Label + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Manually Set Last Language:")) + ] + ] + // >>>> Manually Set Last Language checkbox + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SCheckBox) + .IsChecked_Lambda([this]() -> ECheckBoxState { - WidgetController->OnManuallySetLastUsedLanguageChanged(bManuallySetLastLanguage_Chkbox); - } - }) + return bManuallySetLastLanguage_Chkbox ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([this](ECheckBoxState InCheckBoxState) -> void + { + bManuallySetLastLanguage_Chkbox = (InCheckBoxState == ECheckBoxState::Checked); + if (WidgetController.IsValid()) + { + WidgetController->OnManuallySetLastUsedLanguageChanged(bManuallySetLastLanguage_Chkbox); + } + }) + ] ] ] // > Override Language on Startup Box ================ +SVerticalBox::Slot() .AutoHeight() [ - SNew(SHorizontalBox) - .ToolTipText(INVTEXT("If enabled, when the game starts for the very first time the selected language will be used.\nNormally, the system language will be used or it will fallback to \"en\".")) - // >>>> Override Language on Startup Label - +SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) - .Text(INVTEXT("Override Language on Startup:")) - ] - // >>>> Override Language on Startup Checkbox - +SHorizontalBox::Slot() - .AutoWidth() + SNew(SBox) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ - SNew(SCheckBox) - .IsChecked_Lambda([this]() -> ECheckBoxState - { - return bOverrideLanguageOnStartup_Chkbox ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([this](ECheckBoxState InCheckBoxState) -> void - { - bOverrideLanguageOnStartup_Chkbox = (InCheckBoxState == ECheckBoxState::Checked); - if (WidgetController.IsValid()) + SNew(SHorizontalBox) + .ToolTipText(INVTEXT("If enabled, when the game starts for the very first time the selected language will be used.\nNormally, the system language will be used or it will fallback to \"en\".")) + // >>>> Override Language on Startup Label + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Override Language on Startup:")) + ] + ] + // >>>> Override Language on Startup Checkbox + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SCheckBox) + .IsChecked_Lambda([this]() -> ECheckBoxState { - WidgetController->OnLocalizationOnFirstRun(bOverrideLanguageOnStartup_Chkbox); - } - }) - ] - // >>>> Override Language on Startup List - +SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SComboBox>) - .OptionsSource(&LanguageOverridesAvailable) - .OnGenerateWidget_Lambda([this](TSharedPtr InItem) -> TSharedRef - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda([this](TSharedPtr Item, ESelectInfo::Type SelectInfo) -> void - { - SelectedLanguageOverride = Item; - if (WidgetController.IsValid()) + return bOverrideLanguageOnStartup_Chkbox ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([this](ECheckBoxState InCheckBoxState) -> void { - WidgetController->OnLocalizationOnFirstRunLangChanged(*Item); - } - }) + bOverrideLanguageOnStartup_Chkbox = (InCheckBoxState == ECheckBoxState::Checked); + if (WidgetController.IsValid()) + { + WidgetController->OnLocalizationOnFirstRun(bOverrideLanguageOnStartup_Chkbox); + } + }) + ] + // >>>> Override Language on Startup List + +SHorizontalBox::Slot() + .AutoWidth() [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Regular", 10)) - .Text_Lambda([this]() -> FText + SNew(SComboBox>) + .OptionsSource(&LanguageOverridesAvailable) + .OnGenerateWidget_Lambda([this](TSharedPtr InItem) -> TSharedRef + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda([this](TSharedPtr Item, ESelectInfo::Type SelectInfo) -> void + { + SelectedLanguageOverride = Item; + if (WidgetController.IsValid()) { - if (SelectedLanguageOverride.IsValid()) + WidgetController->OnLocalizationOnFirstRunLangChanged(*Item); + } + }) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 10)) + .Text_Lambda([this]() -> FText { - return FText::FromString(*SelectedLanguageOverride); - } - return FText::GetEmpty(); - }) + if (SelectedLanguageOverride.IsValid()) + { + return FText::FromString(*SelectedLanguageOverride); + } + return FText::GetEmpty(); + }) + ] ] ] ] @@ -393,93 +437,159 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) +SVerticalBox::Slot() .AutoHeight() [ - SNew(SVerticalBox) - .ToolTipText(INVTEXT("A CSV column separator. It's \",\" by default, but it can be any other single character.")) - // >>>> Separator Label - +SVerticalBox::Slot() - .AutoHeight() - [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) - .Text(INVTEXT("Separator:")) - ] - // >>>> Separator Value - +SVerticalBox::Slot() - .AutoHeight() - .HAlign(EHorizontalAlignment::HAlign_Left) + SNew(SBox) + .MinDesiredHeight(BoxesHeight + 4.f) + .VAlign(EVerticalAlignment::VAlign_Center) [ - SNew(SEditableTextBox) - .Font(FCoreStyle::GetDefaultFontStyle("Regular", 12)) - .MinDesiredWidth(256.f) - .Text_Lambda([this]() -> FText - { - return FText::FromString(SeparatorValue); - }) - .OnTextCommitted_Lambda([this](const FText& NewText, ETextCommit::Type CommitType) -> void - { - SeparatorValue = NewText.ToString(); - if (WidgetController.IsValid()) - { - WidgetController->OnSeparatorChanged(SeparatorValue); - } - }) + SNew(SHorizontalBox) + .ToolTipText(INVTEXT("A CSV column separator. It's \",\" by default, but it can be any other single character.")) + // >>>> Separator Label + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Separator:")) + ] + ] + // >>>> Separator Value + +SHorizontalBox::Slot() + .FillWidth(1.f) + .HAlign(EHorizontalAlignment::HAlign_Left) + [ + SNew(SBox) + .MaxDesiredWidth(TextBlockMaxWidth) + [ + SNew(SEditableTextBox) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 12)) + .MinDesiredWidth(256.f) + .Text_Lambda([this]() -> FText + { + return FText::FromString(SeparatorValue); + }) + .OnTextCommitted_Lambda([this](const FText& NewText, ETextCommit::Type CommitType) -> void + { + SeparatorValue = NewText.ToString(); + if (WidgetController.IsValid()) + { + WidgetController->OnSeparatorChanged(SeparatorValue); + } + }) + ] + ] ] ] // > Fallback when empty Box ================ +SVerticalBox::Slot() .AutoHeight() - .Padding(FMargin(0.f, 4.f, 0.f, 0.f)) [ - SNew(SHorizontalBox) - .ToolTipText(INVTEXT("\ -When the entry is empty should it fill it with a fallback value?\n\ -NONE - no fallback\n\ -FIRST_LANG - use value of the first language.If that value is empty use Key\n\ -KEY - use the key of this entry")) - // >>>> Fallback when empty Label - +SHorizontalBox::Slot() - .AutoWidth() + SNew(SBox) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) - .Text(INVTEXT("Fallback when empty:")) + SNew(SHorizontalBox) + .ToolTipText(INVTEXT("\ + When the entry is empty should it fill it with a fallback value?\n\ + NONE - no fallback\n\ + FIRST_LANG - use value of the first language.If that value is empty use Key\n\ + KEY - use the key of this entry")) + // >>>> Fallback when empty Label + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Fallback when empty:")) + ] + ] + // >>>> Fallback when empty List + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SComboBox>) + .OptionsSource(&FallbackWhenEmptyAvailable) + .OnGenerateWidget_Lambda([this](TSharedPtr InItem) -> TSharedRef + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda([this](TSharedPtr Item, ESelectInfo::Type SelectInfo) -> void + { + SelectedFallbackWhenEmpty = Item; + if (SelectedFallbackWhenEmpty.IsValid()) + { + WidgetController->OnFallbackWhenEmptyChanged(*Item); + } + }) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 10)) + .Text_Lambda([this]() -> FText + { + if (SelectedFallbackWhenEmpty.IsValid()) + { + return FText::FromString(*SelectedFallbackWhenEmpty); + } + return FText::GetEmpty(); + }) + ] + ] ] - // >>>> Fallback when empty List - +SHorizontalBox::Slot() - .AutoWidth() + ] + // > Generate Key Reference String Table on Import Box ================ + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SBox) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ - SNew(SComboBox>) - .OptionsSource(&FallbackWhenEmptyAvailable) - .OnGenerateWidget_Lambda([this](TSharedPtr InItem) -> TSharedRef - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda([this](TSharedPtr Item, ESelectInfo::Type SelectInfo) -> void - { - SelectedFallbackWhenEmpty = Item; - if (SelectedFallbackWhenEmpty.IsValid()) - { - WidgetController->OnFallbackWhenEmptyChanged(*Item); - } - }) + SNew(SHorizontalBox) + .ToolTipText(INVTEXT("\ +On CSV Import, a String Table filled with Key References will be generated PER namespace.\n\ +These String Table can be used to easily assign keys to FText properties.\n\n\ +The String Table will be generated in the Localization Folder path and IS OVERRIDDEN if it already exists.")) + // >>>> Generate Key Reference String Table on Import Label + +SHorizontalBox::Slot() + .AutoWidth() [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Bold", 10)) - .Text_Lambda([this]() -> FText + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Generate String Table on Import:")) + ] + ] + // >>>> Generate Key Reference String Table on Import Checkbox + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SCheckBox) + .IsChecked_Lambda([this]() -> ECheckBoxState + { + return bGenerateKeyReferenceStringTable_Chkbox ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([this](ECheckBoxState InCheckBoxState) -> void + { + bGenerateKeyReferenceStringTable_Chkbox = (InCheckBoxState == ECheckBoxState::Checked); + if (WidgetController.IsValid()) { - if (SelectedFallbackWhenEmpty.IsValid()) - { - return FText::FromString(*SelectedFallbackWhenEmpty); - } - return FText::GetEmpty(); - }) + WidgetController->OnGenerateKeyReferenceStringTableChanged(bGenerateKeyReferenceStringTable_Chkbox); + } + }) ] ] ] // > Spacer ================ +SVerticalBox::Slot() .AutoHeight() - .Padding(FMargin(0.f, 4.f, 0.f, 4.f)) + .Padding(FMargin(0.f, SpacerPadding, 0.f, SpacerPadding)) .HAlign(EHorizontalAlignment::HAlign_Left) .VAlign(EVerticalAlignment::VAlign_Center) [ @@ -502,7 +612,7 @@ KEY - use the key of this entry")) [ SNew(STextBlock) .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) - .Text(INVTEXT("CSV file:")) + .Text(INVTEXT("CSV file(s):")) ] // >>>>>>> CSV files list value +SVerticalBox::Slot() @@ -519,10 +629,11 @@ KEY - use the key of this entry")) +SVerticalBox::Slot() // >>>> CSV select files box .AutoHeight() + .Padding(FMargin(0.f, 4.f, 0.f, 4.f)) [ SNew(SHorizontalBox) // >>>>>>> CSV select files button - + SHorizontalBox::Slot() + +SHorizontalBox::Slot() .AutoWidth() [ SNew(SButton) @@ -536,6 +647,13 @@ KEY - use the key of this entry")) return FReply::Handled(); }) ] + // >>>>>>> Spacer between buttons + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SSpacer) + .Size(FVector2D(4.f, 1.f)) + ] // >>>>>>> CSV import files button +SHorizontalBox::Slot() .AutoWidth() @@ -557,42 +675,55 @@ KEY - use the key of this entry")) +SVerticalBox::Slot() .AutoHeight() [ - SNew(SVerticalBox) - .ToolTipText(INVTEXT("This namespace will be assigned to every key in localization.")) - // >>>> Global namespace label ================ - +SVerticalBox::Slot() - .AutoHeight() - [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) - .Text(INVTEXT("Global namespace:")) - ] - // >>>> Global namespace value ================ - +SVerticalBox::Slot() - .AutoHeight() - .HAlign(EHorizontalAlignment::HAlign_Left) + SNew(SBox) + .MinDesiredHeight(BoxesHeight + 4.f) + .VAlign(EVerticalAlignment::VAlign_Center) [ - SNew(SEditableTextBox) - .Font(FCoreStyle::GetDefaultFontStyle("Regular", 12)) - .MinDesiredWidth(256.f) - .Text_Lambda([this]() -> FText - { - return FText::FromString(GlobalNamespaceValue); - }) - .OnTextCommitted_Lambda([this](const FText& NewText, ETextCommit::Type CommitType) -> void - { - GlobalNamespaceValue = NewText.ToString(); - if (WidgetController.IsValid()) - { - WidgetController->OnGlobalNamespaceChanged(GlobalNamespaceValue); - } - }) + SNew(SHorizontalBox) + .ToolTipText(INVTEXT("This namespace will be assigned to every key in localization.")) + // >>>> Global namespace label ================ + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Global namespace:")) + ] + ] + // >>>> Global namespace value ================ + +SHorizontalBox::Slot() + .FillWidth(1.f) + .HAlign(EHorizontalAlignment::HAlign_Left) + [ + SNew(SBox) + .MaxDesiredWidth(TextBlockMaxWidth) + [ + SNew(SEditableTextBox) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 12)) + .MinDesiredWidth(256.f) + .Text_Lambda([this]() -> FText + { + return FText::FromString(GlobalNamespaceValue); + }) + .OnTextCommitted_Lambda([this](const FText& NewText, ETextCommit::Type CommitType) -> void + { + GlobalNamespaceValue = NewText.ToString(); + if (WidgetController.IsValid()) + { + WidgetController->OnGlobalNamespaceChanged(GlobalNamespaceValue); + } + }) + ] + ] ] ] // > Spacer ================ +SVerticalBox::Slot() .AutoHeight() - .Padding(FMargin(0.f, 4.f, 0.f, 4.f)) + .Padding(FMargin(0.f, SpacerPadding, 0.f, SpacerPadding)) .HAlign(EHorizontalAlignment::HAlign_Left) .VAlign(EVerticalAlignment::VAlign_Center) [ @@ -602,77 +733,95 @@ KEY - use the key of this entry")) +SVerticalBox::Slot() .AutoHeight() [ - SNew(SHorizontalBox) - .ToolTipText(INVTEXT("Select this option to see additional informations in Output Log.\nBe aware that big CSVs might generate a lot of logs.")) - // >>>> Log Debug Label - +SHorizontalBox::Slot() - .AutoWidth() + SNew(SBox) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) - .Text(INVTEXT("Log Debug:")) - ] - // >>>> Log Debug checkbox - +SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SCheckBox) - .IsChecked_Lambda([this]() -> ECheckBoxState - { - return bLogDebug_Chkbox ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([this](ECheckBoxState InCheckBoxState) -> void - { - bLogDebug_Chkbox = (InCheckBoxState == ECheckBoxState::Checked); - if (WidgetController.IsValid()) + SNew(SHorizontalBox) + .ToolTipText(INVTEXT("Select this option to see additional informations in Output Log.\nBe aware that big CSVs might generate a lot of logs.")) + // >>>> Log Debug Label + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Log Debug:")) + ] + ] + // >>>> Log Debug checkbox + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SCheckBox) + .IsChecked_Lambda([this]() -> ECheckBoxState { - WidgetController->OnLogDebugChanged(bLogDebug_Chkbox); - } - }) + return bLogDebug_Chkbox ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([this](ECheckBoxState InCheckBoxState) -> void + { + bLogDebug_Chkbox = (InCheckBoxState == ECheckBoxState::Checked); + if (WidgetController.IsValid()) + { + WidgetController->OnLogDebugChanged(bLogDebug_Chkbox); + } + }) + ] ] ] // > Preview In UI Box ================ +SVerticalBox::Slot() .AutoHeight() [ - SNew(SHorizontalBox) - .ToolTipText(INVTEXT("Select this option to show a localization preview under the Text fields in the Editor UI.")) - .Visibility_Lambda([this]() -> EVisibility - { - if (WidgetController.IsValid()) - { - if (WidgetController->IsPreviewInUISupported()) - { - return EVisibility::Visible; - } - } - return EVisibility::Collapsed; - }) - // >>>> Preview In UI Label - +SHorizontalBox::Slot() - .AutoWidth() + SNew(SBox) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) - .Text(INVTEXT("Show preview in UI:")) - ] - // >>>> Preview In UI checkbox - +SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SCheckBox) - .IsChecked_Lambda([this]() -> ECheckBoxState - { - return bPreviewInUI_Chkbox ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([this](ECheckBoxState InCheckBoxState) -> void + SNew(SHorizontalBox) + .ToolTipText(INVTEXT("Select this option to show a localization preview under the Text fields in the Editor UI.")) + .Visibility_Lambda([this]() -> EVisibility + { + if (WidgetController.IsValid()) { - bPreviewInUI_Chkbox = (InCheckBoxState == ECheckBoxState::Checked); - if (WidgetController.IsValid()) + if (WidgetController->IsPreviewInUISupported()) { - WidgetController->OnPreviewInUIChanged(bPreviewInUI_Chkbox); + return EVisibility::Visible; } - }) + } + return EVisibility::Collapsed; + }) + // >>>> Preview In UI Label + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Show preview in UI:")) + ] + ] + // >>>> Preview In UI checkbox + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SCheckBox) + .IsChecked_Lambda([this]() -> ECheckBoxState + { + return bPreviewInUI_Chkbox ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([this](ECheckBoxState InCheckBoxState) -> void + { + bPreviewInUI_Chkbox = (InCheckBoxState == ECheckBoxState::Checked); + if (WidgetController.IsValid()) + { + WidgetController->OnPreviewInUIChanged(bPreviewInUI_Chkbox); + } + }) + ] ] ] ] @@ -819,6 +968,11 @@ void SELTEditorWidget::SetFallbackWhenEmpty(const FString& FallbackWhenEmpty) } } +void SELTEditorWidget::SetGenerateKeyReferenceStringTable(bool bGenerateKeyReferenceStringTable) +{ + bGenerateKeyReferenceStringTable_Chkbox = bGenerateKeyReferenceStringTable; +} + void SELTEditorWidget::SetLogDebug(bool bLogDebug) { bLogDebug_Chkbox = bLogDebug; diff --git a/Source/EasyLocalizationToolEditor/Public/CSVReader.h b/Source/EasyLocalizationToolEditor/Public/CSVReader.h index 079894b..4acee6a 100644 --- a/Source/EasyLocalizationToolEditor/Public/CSVReader.h +++ b/Source/EasyLocalizationToolEditor/Public/CSVReader.h @@ -15,6 +15,7 @@ struct FCSVColumn { TArray Values; + FCSVColumn() = default; FCSVColumn(const FString& FirstValue) { Values.Add(FirstValue); diff --git a/Source/EasyLocalizationToolEditor/Public/ELTEditor.h b/Source/EasyLocalizationToolEditor/Public/ELTEditor.h index 9e2668b..c8efd8c 100644 --- a/Source/EasyLocalizationToolEditor/Public/ELTEditor.h +++ b/Source/EasyLocalizationToolEditor/Public/ELTEditor.h @@ -49,8 +49,8 @@ class EASYLOCALIZATIONTOOLEDITOR_API UELTEditor : public UObject * Implementation of generating Unreal localization files. It is statically exposed, * so other elements like Commandlet can run it. */ - static bool GenerateLocFilesImpl(const FString& CSVPaths, const FString& LocPath, const FString& LocName, const FString& GlobalNamespace, const FString& Separator, const FString& FallbackWhenEmpty, FString& OutMessage); - static bool GenerateLocFilesImpl(const TArray& CSVPaths, const FString& LocPath, const FString& LocName, const FString& GlobalNamespace, const FString& Separator, const FString& FallbackWhenEmpty, FString& OutMessage); + static bool GenerateLocFilesImpl(const FString& CSVPaths, const FString& LocPath, const FString& LocName, const FString& GlobalNamespace, const FString& Separator, const FString& FallbackWhenEmpty, bool bGenerateStringTables, FString& OutMessage); + static bool GenerateLocFilesImpl(const TArray& CSVPaths, const FString& LocPath, const FString& LocName, const FString& GlobalNamespace, const FString& Separator, const FString& FallbackWhenEmpty, bool bGenerateStringTables, FString& OutMessage); private: @@ -144,6 +144,11 @@ class EASYLOCALIZATIONTOOLEDITOR_API UELTEditor : public UObject */ void OnFallbackWhenEmptyChanged(const FString& NewFallback); + /** + * Called when "GenerateKeyReferenceStringTable" option has been changed in the Widget. + */ + void OnGenerateKeyReferenceStringTableChanged(bool bNewGenerateKeyReferenceStringTable); + /** * Called when "LogDebug" option has been changed in the Widget. */ diff --git a/Source/EasyLocalizationToolEditor/Public/ELTEditorSettings.h b/Source/EasyLocalizationToolEditor/Public/ELTEditorSettings.h index dcd8da2..e748c96 100644 --- a/Source/EasyLocalizationToolEditor/Public/ELTEditorSettings.h +++ b/Source/EasyLocalizationToolEditor/Public/ELTEditorSettings.h @@ -59,6 +59,9 @@ class EASYLOCALIZATIONTOOLEDITOR_API UELTEditorSettings : public UObject static bool GetReimportAtEditorStartup(); static void SetReimportAtEditorStartup(bool bNewReimportAtEditorStartup); + static bool GetGenerateKeyReferenceStringTable(); + static void SetGenerateKeyReferenceStringTable(bool bNewGenerateKeyReferenceStringTable); + /** * Get/Set if the preview should be displayed on UI. */ @@ -99,7 +102,10 @@ class EASYLOCALIZATIONTOOLEDITOR_API UELTEditorSettings : public UObject UPROPERTY(config) bool bReimportAtEditorStartup = false; - + + UPROPERTY(config) + bool bGenerateKeyReferenceStringTable = false; + UPROPERTY(config) bool bPreviewInUI = true; diff --git a/Source/EasyLocalizationToolEditor/Public/ELTEditorWidget.h b/Source/EasyLocalizationToolEditor/Public/ELTEditorWidget.h index 3853c5a..dc5b79e 100644 --- a/Source/EasyLocalizationToolEditor/Public/ELTEditorWidget.h +++ b/Source/EasyLocalizationToolEditor/Public/ELTEditorWidget.h @@ -23,6 +23,7 @@ DECLARE_DELEGATE_OneParam(FOnLocalizationPreviewLangChanged, const FString&); DECLARE_DELEGATE_OneParam(FManuallySetLastLanguageChanged, bool); DECLARE_DELEGATE_OneParam(FOnLocalizationOnFirstRunChanged, bool); DECLARE_DELEGATE_OneParam(FOnLocalizationOnFirstRunLangChanged, const FString&); +DECLARE_DELEGATE_OneParam(FOnGenerateKeyReferenceStringTableChanged, bool); DECLARE_DELEGATE_OneParam(FOnGlobalNamespaceChanged, const FString&); DECLARE_DELEGATE_OneParam(FOnSeparatorChanged, const FString&); DECLARE_DELEGATE_OneParam(FOnFallbackWhenEmptyChanged, const FString&); @@ -196,6 +197,18 @@ class EASYLOCALIZATIONTOOLEDITOR_API UELTEditorWidget : public UEditorUtilityWid + /** + * Set "Generate Key Reference String Table On CSV Import" to the Widget. + */ + void CallSetGenerateKeyReferenceStringTable(bool bGenerateKeyReferenceStringTable); + + /** + * "Generate Key Reference String Table On CSV Import" option has been changed on the Widget. + */ + UFUNCTION(BlueprintCallable, Category = "Easy Localization Tool Editor") + void OnGenerateKeyReferenceStringTableChanged(bool bGenerateKeyReferenceStringTable); + + /** * Set "Global Namespace" option to the Widget. */ @@ -287,6 +300,7 @@ class EASYLOCALIZATIONTOOLEDITOR_API UELTEditorWidget : public UEditorUtilityWid FManuallySetLastLanguageChanged OnManuallySetLastLanguageChangedDelegate; FOnLocalizationOnFirstRunChanged OnLocalizationOnFirstRunChangedDelegate; FOnLocalizationOnFirstRunLangChanged OnLocalizationOnFirstRunLangChangedDelegate; + FOnGenerateKeyReferenceStringTableChanged OnGenerateKeyReferenceStringTableChangedDelegate; FOnGlobalNamespaceChanged OnGlobalNamespaceChangedDelegate; FOnSeparatorChanged OnSeparatorChangedDelegate; FOnFallbackWhenEmptyChanged OnFallbackWhenEmptyChangedDelegate; diff --git a/Source/EasyLocalizationToolEditor/Public/SELTEditorWidget.h b/Source/EasyLocalizationToolEditor/Public/SELTEditorWidget.h index 58f6487..cdcf909 100644 --- a/Source/EasyLocalizationToolEditor/Public/SELTEditorWidget.h +++ b/Source/EasyLocalizationToolEditor/Public/SELTEditorWidget.h @@ -33,6 +33,7 @@ class EASYLOCALIZATIONTOOLEDITOR_API SELTEditorWidget : public SUserWidget void SetGlobalNamespace(const FString& GlobalNamespace); void SetSeparator(const FString& Separator); void SetFallbackWhenEmpty(const FString& FallbackWhenEmpty); + void SetGenerateKeyReferenceStringTable(bool bGenerateKeyReferenceStringTable); void SetLogDebug(bool bLogDebug); void SetPreviewInUI(bool bPreviewInUI); @@ -54,6 +55,7 @@ class EASYLOCALIZATIONTOOLEDITOR_API SELTEditorWidget : public SUserWidget bool bIsLocalisationPreviewEnabled_Chkbox = false; bool bManuallySetLastLanguage_Chkbox = false; bool bOverrideLanguageOnStartup_Chkbox = false; + bool bGenerateKeyReferenceStringTable_Chkbox = false; bool bLogDebug_Chkbox = false; bool bPreviewInUI_Chkbox = false;