From ae4b10953dabcfb863c7f734bff5a191d4909289 Mon Sep 17 00:00:00 2001 From: Damian Nowakowski Date: Wed, 20 May 2026 00:34:54 +0200 Subject: [PATCH] import refactor and ui refinement --- .../Private/ELTEditor.cpp | 427 +++++++++++------- .../Private/SELTEditorWidget.cpp | 369 ++++++++------- .../Public/CSVReader.h | 1 + 3 files changed, 462 insertions(+), 335 deletions(-) diff --git a/Source/EasyLocalizationToolEditor/Private/ELTEditor.cpp b/Source/EasyLocalizationToolEditor/Private/ELTEditor.cpp index feb1bdd..9a8a423 100644 --- a/Source/EasyLocalizationToolEditor/Private/ELTEditor.cpp +++ b/Source/EasyLocalizationToolEditor/Private/ELTEditor.cpp @@ -1,7 +1,6 @@ // Copyright (c) 2026 Damian Nowakowski. All rights reserved. #include "ELTEditor.h" -#include "AssetToolsModule.h" #include "Internationalization/TextLocalizationResource.h" #include "Internationalization/TextLocalizationManager.h" #include "Internationalization/StringTableCore.h" @@ -510,6 +509,14 @@ enum class EFallbackWhenEmptyType : uint8 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); @@ -527,226 +534,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; - TMap> NamespaceToKeysMap; + // 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; + TMap> NamespaceToKeysToNotesMap; // List of keys with dev notes for each namespace (used for generating String Tables with dev notes). #endif - for (const FString& CSVPath : CSVPaths) + // 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) { - int32 CurrentColumn = 0; - int32 FirstLangColumn = 0; + OutMessage = FString::Printf(TEXT("ERROR: Failed to load CSV file (%s)! Error: %s"), *CSVFilePath, *OutMessage); + return false; + } - int32 NamespaceColumn = INDEX_NONE; - int32 DevNotesColumn = INDEX_NONE; + // 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++) + { + 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)) - { - NamespaceColumn = CurrentColumn; - ++CurrentColumn; - ++FirstLangColumn; - continue; - } - if (Column.Values[0].Equals(TEXT("devnotes"), ESearchCase::IgnoreCase)) + if (NamespaceColumn != INDEX_NONE) { - DevNotesColumn = CurrentColumn; - ++CurrentColumn; - ++FirstLangColumn; - continue; + 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) { - ++FirstLangColumn; - break; + OutMessage = FString::Printf(TEXT("ERROR: Invalid CSV structure in file (%s)! Multiple 'devnotes' columns found!"), *(CSVPaths[CSVIdx])); + return false; } - if ((NamespaceColumn != INDEX_NONE) || (DevNotesColumn != INDEX_NONE)) + DevNotesColumn = ColumnIdx; + } + else if (Header.Equals(TEXT("key"), ESearchCase::IgnoreCase)) + { + if (KeysColumn != INDEX_NONE) { - OutMessage = TEXT("ERROR: Invalid CSV! The 'namespace' and 'devnotes' columns must be before the 'key' column!"); + OutMessage = FString::Printf(TEXT("ERROR: Invalid CSV structure in file (%s)! Multiple 'key' columns found!"), *(CSVPaths[CSVIdx])); return false; } - ++CurrentColumn; + KeysColumn = ColumnIdx; } - - if (Columns.Num() > (CurrentColumn + 1)) + else { - const int32 NumOfValues = Columns[CurrentColumn].Values.Num(); - for (int32 CIdx = CurrentColumn + 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 = (NamespaceColumn != INDEX_NONE) ? Columns[NamespaceColumn] : Columns[0]; + // 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 = (NamespaceColumn == INDEX_NONE) && (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 && (NamespaceColumn == INDEX_NONE)) - { - 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 devnotes. - const FCSVColumn& DevNotes = (DevNotesColumn != INDEX_NONE) ? Columns[DevNotesColumn] : Columns[0]; + // Potential place for namespaces (if the column exists). + const FCSVColumn& Namespaces = (NamespaceColumn != INDEX_NONE) ? Columns[NamespaceColumn] : FCSVColumn(); - // 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 (bFirstCSV) + // 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, 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) { - TArray FilesToDelete; - IFileManager::Get().FindFilesRecursive(FilesToDelete, *LocPath, TEXT("*"), true, false); - for (const FString& File : FilesToDelete) - { - if (File.EndsWith(TEXT(".uasset")) == false) - { - IFileManager::Get().Delete(*File); - } - } + IFileManager::Get().Delete(*File); } } + } + } - // Get the keys column and check if it is valid. - const FCSVColumn& Keys = Columns[FirstLangColumn-1]; - if (Keys.Values[0].Equals(TEXT("key"), ESearchCase::IgnoreCase) == false) - { - OutMessage = TEXT("ERROR: Key column in CSV not found!"); - return false; - } + // Get the keys column. We know it's valid because we've already validated it. + const FCSVColumn& Keys = Columns[KeysColumn]; #if ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 8)) - // Gather Dev Notes if available - if (DevNotesColumn != INDEX_NONE) + // 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) { - if (DevNotes.Values[0].Equals(TEXT("devnotes"), ESearchCase::IgnoreCase) == false) + const FString& Namespace = (bUseGlobalNamespace || Namespaces.Values[Key].IsEmpty()) ? GlobalNamespace : Namespaces.Values[Key]; + if (Namespace.IsEmpty()) { - OutMessage = TEXT("ERROR: Dev Notes column in CSV not found!"); + OutMessage = FString::Printf(TEXT("ERROR: Namespace in row %i (counting from 1) for dev note is empty!"), Key); return false; } - - for (int32 Key = 1; Key < Keys.Values.Num(); Key++) - { - FString DevNote = DevNotes.Values[Key]; - if (DevNote.IsEmpty() == 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; - } - TMap& DevNotesList = NamespaceToKeysToNotesMap.FindOrAdd(Namespace); - DevNotesList.Add(Keys.Values[Key], DevNote); - } - } + 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; } - 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) + if (FallbackWhenEmptyType == EFallbackWhenEmptyType::FIRST_LANG) { - LocReses.Add(Lang, FTextLocalizationResource()); - } - FTextLocalizationResource& LocRes = LocReses[Lang]; - for (int32 Key = 1; Key < Keys.Values.Num(); Key++) - { - 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) is empty!"), Key); - return false; - } - - // 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 (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]; - } - } - } - - if (bLogDebug) + // Use the first language column value as fallback. + if (Columns.IsValidIndex(FirstLangColumn)) { - UE_LOG(ELTEditorLog, Log, TEXT("%s | %s | %s | %s"), *Lang, *Namespace, *(Keys.Values[Key]), *LocalizedString); + LocalizedString = Columns[FirstLangColumn].Values[Key]; } - LocRes.AddEntry( - FTextKey(Namespace), - FTextKey(Keys.Values[Key]), - Keys.Values[Key], - LocalizedString, - 0); - - if (bGenerateStringTables && (Keys.Values[Key].IsEmpty() == false)) + // If the first language value is also empty - use the key as a fallback. + if (LocalizedString.TrimStartAndEnd().IsEmpty()) { - NamespaceToKeysMap.FindOrAdd(Namespace).Add(Keys.Values[Key]); + LocalizedString = Keys.Values[Key]; } } + 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. @@ -772,8 +839,6 @@ bool UELTEditor::GenerateLocFilesImpl(const TArray& CSVPaths, const FSt // Generate Key Reference String Table if (bGenerateStringTables && NamespaceToKeysMap.Num() > 0) { - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); - for (const auto& KVP : NamespaceToKeysMap) { const FString& Namespace = KVP.Key; @@ -786,41 +851,47 @@ bool UELTEditor::GenerateLocFilesImpl(const TArray& CSVPaths, const FSt UPackage* Package = FindPackage(nullptr, *PackagePath); if (Package == nullptr) { - if (FPackageName::DoesPackageExist(*PackagePath)) - { - Package = LoadPackage(nullptr, *PackagePath, LOAD_None); - } else - { - Package = CreatePackage(*PackagePath); - } + // 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 from the package before creating a new one. - if (UStringTable* Existing = FindObject(Package, *AssetName)) + // Clear any existing StringTable that resides in memory before creating a new one. + if (UStringTable* ExistingStringTableAsset = FindObject(Package, *AssetName)) { - Existing->ClearFlags(RF_Public | RF_Standalone); - Existing->MarkAsGarbage(); + 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) && (ENGINE_MINOR_VERSION >= 8)) +#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) { @@ -832,18 +903,32 @@ bool UELTEditor::GenerateLocFilesImpl(const TArray& CSVPaths, const FSt StringTableRef->SetSourceString(FTextKey(Key), Key, DevNotes); } -#else + #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; diff --git a/Source/EasyLocalizationToolEditor/Private/SELTEditorWidget.cpp b/Source/EasyLocalizationToolEditor/Private/SELTEditorWidget.cpp index d443a69..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(600.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,48 +58,47 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) // > Spacer ================ +SVerticalBox::Slot() .AutoHeight() - .Padding(FMargin(0.f, 15.f, 0.f, 15.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() [ SNew(SVerticalBox) .ToolTipText(INVTEXT("Name of currently selected Localization. The game can have multiple localization directories.")) - // >>>> Localization Name box - +SVerticalBox::Slot() - .AutoHeight() - [ - SNew(SHorizontalBox) - // >>>>>>>> Localization Name label - +SHorizontalBox::Slot() - .AutoWidth() - .Padding(FMargin(0.f, 5.f, 20.f, 0.f)) - [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) - .Text(INVTEXT("Localization name:")) - ] - // >>>>>>>> Localization Name value - +SHorizontalBox::Slot() - .AutoWidth() - .Padding(FMargin(0.f, 5.f, 20.f, 0.f)) - [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Bold", 11)) - .Text_Lambda([this]() -> FText - { - return FText::FromString(CurrentLocName); - }) - ] - ] // >>>> Localization Path selection list +SVerticalBox::Slot() - .Padding(FMargin(0.f, 3.f, 0.f, 0.f)) .AutoHeight() .HAlign(EHorizontalAlignment::HAlign_Left) [ @@ -129,7 +133,7 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) // > Spacer ================ +SVerticalBox::Slot() .AutoHeight() - .Padding(FMargin(0.f, 15.f, 0.f, 15.f)) + .Padding(FMargin(0.f, SpacerPadding, 0.f, SpacerPadding)) .HAlign(EHorizontalAlignment::HAlign_Left) .VAlign(EVerticalAlignment::VAlign_Center) [ @@ -146,7 +150,7 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) .Text(INVTEXT("Available Languages In Selected Localization:")) ] // >>>> Available Langs In Selected Localization Value @@ -154,7 +158,7 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Bold", 11)) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 12)) .Text_Lambda([this]() -> FText { return FText::FromString(AvailableLangsInLocFile); @@ -164,16 +168,16 @@ 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.")) // >>>> Available Langs Label +SVerticalBox::Slot() - .Padding(FMargin(0.f, 10.f, 0.f, 0)) .AutoHeight() [ SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) .Text(INVTEXT("Available Languages:")) ] // >>>> Localization Names value @@ -181,7 +185,7 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Bold", 11)) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 12)) .Text_Lambda([this]() -> FText { return FText::FromString(AvailableLangs); @@ -191,7 +195,7 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) // > Spacer ================ +SVerticalBox::Slot() .AutoHeight() - .Padding(FMargin(0.f, 15.f, 0.f, 15.f)) + .Padding(FMargin(0.f, SpacerPadding, 0.f, SpacerPadding)) .HAlign(EHorizontalAlignment::HAlign_Left) .VAlign(EVerticalAlignment::VAlign_Center) [ @@ -202,24 +206,28 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(SBox) - .MinDesiredHeight(24.f) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SHorizontalBox) .ToolTipText(INVTEXT("Reimports the lastly selected localization with the last used CSV file when editor starts.")) - // >>>> Reimport on editor startup Label + // >>>> Reimport on editor startup Label +SHorizontalBox::Slot() - .FillWidth(1.0f) - .MaxWidth(400.0f) - .VAlign(EVerticalAlignment::VAlign_Center) + .AutoWidth() [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) - .Text(INVTEXT("Reimport on editor startup:")) + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Reimport on editor startup:")) + ] ] - // >>>> Reimport on editor startup checkbox + + + // >>>> Reimport on editor startup checkbox +SHorizontalBox::Slot() .AutoWidth() - .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SCheckBox) .IsChecked_Lambda([this]() -> ECheckBoxState @@ -242,24 +250,26 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(SBox) - .MinDesiredHeight(24.f) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SHorizontalBox) .ToolTipText(INVTEXT("Enabled the preview of the localization in the editor.")) // >>>> Localization Preview Label +SHorizontalBox::Slot() - .FillWidth(1.0f) - .MaxWidth(400.0f) - .VAlign(EVerticalAlignment::VAlign_Center) + .AutoWidth() [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) - .Text(INVTEXT("Localization Preview:")) + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Localization Preview:")) + ] ] // >>>> Localization Preview Checkbox +SHorizontalBox::Slot() .AutoWidth() - .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SCheckBox) .IsChecked_Lambda([this]() -> ECheckBoxState @@ -278,7 +288,6 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) // >>>> Localization Preview List +SHorizontalBox::Slot() .AutoWidth() - .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SComboBox>) .OptionsSource(&PreviewsAvailables) @@ -314,24 +323,26 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(SBox) - .MinDesiredHeight(24.f) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SHorizontalBox) .ToolTipText(INVTEXT("If enabled it won't save and load lastly set language automatically.")) // >>>> Manually Set Last Language Label +SHorizontalBox::Slot() - .FillWidth(1.0f) - .MaxWidth(400.0f) - .VAlign(EVerticalAlignment::VAlign_Center) + .AutoWidth() [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) - .Text(INVTEXT("Manually Set Last Language:")) + 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() - .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SCheckBox) .IsChecked_Lambda([this]() -> ECheckBoxState @@ -354,24 +365,26 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(SBox) - .MinDesiredHeight(24.f) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ 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() - .FillWidth(1.0f) - .MaxWidth(400.0f) - .VAlign(EVerticalAlignment::VAlign_Center) + .AutoWidth() [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) - .Text(INVTEXT("Override Language on Startup:")) + 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() - .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SCheckBox) .IsChecked_Lambda([this]() -> ECheckBoxState @@ -390,7 +403,6 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) // >>>> Override Language on Startup List +SHorizontalBox::Slot() .AutoWidth() - .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SComboBox>) .OptionsSource(&LanguageOverridesAvailable) @@ -426,40 +438,47 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(SBox) - .MinDesiredHeight(24.f) + .MinDesiredHeight(BoxesHeight + 4.f) + .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SHorizontalBox) .ToolTipText(INVTEXT("A CSV column separator. It's \",\" by default, but it can be any other single character.")) // >>>> Separator Label +SHorizontalBox::Slot() - .FillWidth(1.0f) - .MaxWidth(400.0f) - .VAlign(EVerticalAlignment::VAlign_Center) + .AutoWidth() [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) - .Text(INVTEXT("Separator:")) + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Separator:")) + ] ] // >>>> Separator Value +SHorizontalBox::Slot() - .FillWidth(1.0f) - .MaxWidth(200.0f) - .VAlign(EVerticalAlignment::VAlign_Center) + .FillWidth(1.f) + .HAlign(EHorizontalAlignment::HAlign_Left) [ - SNew(SEditableTextBox) - .Font(FCoreStyle::GetDefaultFontStyle("Regular", 11)) - .Text_Lambda([this]() -> FText - { - return FText::FromString(SeparatorValue); - }) - .OnTextCommitted_Lambda([this](const FText& NewText, ETextCommit::Type CommitType) -> void - { - SeparatorValue = NewText.ToString(); - if (WidgetController.IsValid()) + SNew(SBox) + .MaxDesiredWidth(TextBlockMaxWidth) + [ + SNew(SEditableTextBox) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 12)) + .MinDesiredWidth(256.f) + .Text_Lambda([this]() -> FText { - WidgetController->OnSeparatorChanged(SeparatorValue); - } - }) + return FText::FromString(SeparatorValue); + }) + .OnTextCommitted_Lambda([this](const FText& NewText, ETextCommit::Type CommitType) -> void + { + SeparatorValue = NewText.ToString(); + if (WidgetController.IsValid()) + { + WidgetController->OnSeparatorChanged(SeparatorValue); + } + }) + ] ] ] ] @@ -468,7 +487,8 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(SBox) - .MinDesiredHeight(24.f) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SHorizontalBox) .ToolTipText(INVTEXT("\ @@ -478,18 +498,19 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) KEY - use the key of this entry")) // >>>> Fallback when empty Label +SHorizontalBox::Slot() - .FillWidth(1.0f) - .MaxWidth(400.0f) - .VAlign(EVerticalAlignment::VAlign_Center) + .AutoWidth() [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) - .Text(INVTEXT("Fallback when empty:")) + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Fallback when empty:")) + ] ] // >>>> Fallback when empty List +SHorizontalBox::Slot() .AutoWidth() - .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SComboBox>) .OptionsSource(&FallbackWhenEmptyAvailable) @@ -525,27 +546,29 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(SBox) - .MinDesiredHeight(24.f) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ 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 CSV Import Label +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() - .FillWidth(1.0f) - .MaxWidth(400.0f) - .VAlign(EVerticalAlignment::VAlign_Center) + .AutoWidth() [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) - .Text(INVTEXT("Generate Key Reference String Table on Import:")) + 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 + // >>>> Generate Key Reference String Table on Import Checkbox +SHorizontalBox::Slot() .AutoWidth() - .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SCheckBox) .IsChecked_Lambda([this]() -> ECheckBoxState @@ -566,7 +589,7 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) // > Spacer ================ +SVerticalBox::Slot() .AutoHeight() - .Padding(FMargin(0.f, 15.f, 0.f, 15.f)) + .Padding(FMargin(0.f, SpacerPadding, 0.f, SpacerPadding)) .HAlign(EHorizontalAlignment::HAlign_Left) .VAlign(EVerticalAlignment::VAlign_Center) [ @@ -577,7 +600,7 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(SVerticalBox) - .ToolTipText(INVTEXT("CSV files to import. You can import multiple files at once to the same Localization.")) + .ToolTipText(INVTEXT("CSV files to import. You can import mutliple files at once to the same Localization.")) // >>>> CSV files list box +SVerticalBox::Slot() .AutoHeight() @@ -588,15 +611,15 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) - .Text(INVTEXT("CSV file:")) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("CSV file(s):")) ] // >>>>>>> CSV files list value +SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Bold", 11)) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 12)) .Text_Lambda([this]() -> FText { return FText::FromString(CSVFiles); @@ -606,11 +629,11 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) +SVerticalBox::Slot() // >>>> CSV select files box .AutoHeight() - .Padding(FMargin(0.f, 5.f, 0.f, 0.f)) + .Padding(FMargin(0.f, 4.f, 0.f, 4.f)) [ SNew(SHorizontalBox) // >>>>>>> CSV select files button - + SHorizontalBox::Slot() + +SHorizontalBox::Slot() .AutoWidth() [ SNew(SButton) @@ -624,6 +647,13 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) return FReply::Handled(); }) ] + // >>>>>>> Spacer between buttons + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SSpacer) + .Size(FVector2D(4.f, 1.f)) + ] // >>>>>>> CSV import files button +SHorizontalBox::Slot() .AutoWidth() @@ -644,51 +674,56 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) // > Global namespace box ================ +SVerticalBox::Slot() .AutoHeight() - .Padding(FMargin(0.f, 15.f, 0.f, 0.f)) [ SNew(SBox) - .MinDesiredHeight(24.f) + .MinDesiredHeight(BoxesHeight + 4.f) + .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SHorizontalBox) .ToolTipText(INVTEXT("This namespace will be assigned to every key in localization.")) // >>>> Global namespace label ================ +SHorizontalBox::Slot() - .FillWidth(1.0f) - .MaxWidth(400.0f) - .VAlign(EVerticalAlignment::VAlign_Center) + .AutoWidth() [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) - .Text(INVTEXT("Global namespace:")) + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Global namespace:")) + ] ] // >>>> Global namespace value ================ +SHorizontalBox::Slot() - .FillWidth(1.0f) - .MaxWidth(200.0f) - .VAlign(EVerticalAlignment::VAlign_Center) + .FillWidth(1.f) + .HAlign(EHorizontalAlignment::HAlign_Left) [ - SNew(SEditableTextBox) - .Font(FCoreStyle::GetDefaultFontStyle("Regular", 11)) - .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()) + SNew(SBox) + .MaxDesiredWidth(TextBlockMaxWidth) + [ + SNew(SEditableTextBox) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 12)) + .MinDesiredWidth(256.f) + .Text_Lambda([this]() -> FText { - WidgetController->OnGlobalNamespaceChanged(GlobalNamespaceValue); - } - }) + 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, 15.f, 0.f, 15.f)) + .Padding(FMargin(0.f, SpacerPadding, 0.f, SpacerPadding)) .HAlign(EHorizontalAlignment::HAlign_Left) .VAlign(EVerticalAlignment::VAlign_Center) [ @@ -699,23 +734,26 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(SBox) - .MinDesiredHeight(24.f) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ 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() - .MaxWidth(400.0f) - .VAlign(EVerticalAlignment::VAlign_Center) + .AutoWidth() [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) - .Text(INVTEXT("Log Debug:")) + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Log Debug:")) + ] ] // >>>> Log Debug checkbox +SHorizontalBox::Slot() .AutoWidth() - .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SCheckBox) .IsChecked_Lambda([this]() -> ECheckBoxState @@ -738,7 +776,8 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) .AutoHeight() [ SNew(SBox) - .MinDesiredHeight(24.f) + .MinDesiredHeight(BoxesHeight) + .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SHorizontalBox) .ToolTipText(INVTEXT("Select this option to show a localization preview under the Text fields in the Editor UI.")) @@ -755,17 +794,19 @@ void SELTEditorWidget::Construct(const FArguments& InArgs) }) // >>>> Preview In UI Label +SHorizontalBox::Slot() - .MaxWidth(400.0f) - .VAlign(EVerticalAlignment::VAlign_Center) + .AutoWidth() [ - SNew(STextBlock) - .Font(FCoreStyle::GetDefaultFontStyle("Light", 11)) - .Text(INVTEXT("Show preview in UI:")) + SNew(SBox) + .WidthOverride(WidthMargin) + [ + SNew(STextBlock) + .Font(FCoreStyle::GetDefaultFontStyle("Light", 12)) + .Text(INVTEXT("Show preview in UI:")) + ] ] // >>>> Preview In UI checkbox +SHorizontalBox::Slot() .AutoWidth() - .VAlign(EVerticalAlignment::VAlign_Center) [ SNew(SCheckBox) .IsChecked_Lambda([this]() -> ECheckBoxState 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);