Confusion with String Table localization, c++

I want to localize strings in c++ using string table assets. I have read the documentation, yet there remains some confusion.

I don’t use the macros LOCTEXT / NSLOCTEXT since they are macros and don’t work with variable input.

This does not localize anything if the table is not loaded:

FText OutText = FText();
FText::FindText(InNameSpace, InKey, OutText);

// This does not localize anything from the namespace alone, apparently you need the entire asset path, which makes no sense because we specifically specify a namespace for a string table. I absolutely want to avoid hardcoding an asset path or having to configure one in the editor:

const FText Localized = FText::FromStringTable(InNameSpace, InKey, EStringTableLoadingPolicy::FindOrLoad);

The question is: How do I, in a oneliner, localize a string from a string table by namespace and key alone, which does not fail?

FText::FromStringTable:

LogStringTable: Warning: Failed to find string table entry for 'MainMenu' 'Value_Large'. Did you forget to add a string table redirector?

I already checked the namespace and keys exist.
There’s nothing about it here:

String Tables | Unreal Engine 4.27 Documentation

To be sure, referenced the directory directly containing the string tables:

UCorePluginEditorUtils::AddAdditionalAssetDirectoryToCook(TEXT(“/CorePlugin/Localization/Text”));

To be double sure, referenced the string table on an FText node on a used blueprint.

Absolutely nothing. What the??

Related, same problem:

FText::FromStringTable returns "Missing String Table Entry"

1 Like

bump…

Ridiculous, but here we go. Getting somewhere. String tables must be referenced in the project settings on the Package tab and on the Asset Manager tab, because assets which are not referenced are not loaded. Next up FromStringTable will still be failing, so use the FText::FindText …

FText ULocalizationUtils::Localize(const FName& InNameSpace, const FString& InKey) {
	/** 
	* FromStringTable doesn't do the job, I don't want a hardcoded path here. I just want the namespace + key to be enough.
	* //const FText Localized = FText::FromStringTable(InNameSpace, InKey);
	*
	* String tables must be registered on the asset manager and packaging settings (both project settings) to always cook, or they won't be loaded.
	*/
	FText Localized = FText();
	if (!FText::FindText(InNameSpace.ToString(), InKey, Localized)) {
		CUR_LOG(LogCorePlugin, Warning, "Could not localize text. Namespace: %s, Key: %s.", *InNameSpace.ToString(), *InKey);
	}
	return Localized;
}

To register your assets in the project settings either memorize what you need to click like a madman so you won’t run into the same thing in 2025, or be like me and write some code into your own editor module…

void UCorePluginEditorUtils::AddDirectoryToScanAndCook(const FString& InDirectory, const FName& InAssetType, const TSoftClassPtr<UObject> InAssetBaseClass) {
	UProjectPackagingSettings* PackagingSettings = GetMutableDefault<UProjectPackagingSettings>();
	check(IsValid(PackagingSettings));

	// For whatever reason the datatype required is a poorly implemented directory one, containing just a string path.
	const FDirectoryPath* PathX = PackagingSettings->DirectoriesToAlwaysCook.FindByPredicate([&InDirectory](const FDirectoryPath& PathX) { return PathX.Path == InDirectory; });
	if (PathX == nullptr) {
		FDirectoryPath NewPath = FDirectoryPath();
		NewPath.Path = InDirectory;
		PackagingSettings->DirectoriesToAlwaysCook.Add(NewPath);
	}

	UAssetManagerSettings* AssetManagerSettings = GetMutableDefault<UAssetManagerSettings>();
	check(IsValid(AssetManagerSettings));
	check(!InAssetBaseClass.IsNull());

	FPrimaryAssetTypeInfo* ExistingInfo = AssetManagerSettings->PrimaryAssetTypesToScan.FindByPredicate([&InDirectory, &InAssetType, &InAssetBaseClass](const FPrimaryAssetTypeInfo& InfoX) { return (
		// If the directory matches the parameter
		(InfoX.GetDirectories().FindByPredicate([&InDirectory, &InAssetType, &InAssetBaseClass](const FDirectoryPath& PathX) { return PathX.Path == InDirectory; }))
		// And if the PrimaryAssetType matches the parameter
		&& (InfoX.PrimaryAssetType == InAssetType)
		// And if the GetAssetBaseClass matches the parameter
		&& (InfoX.GetAssetBaseClass() == InAssetBaseClass)
		// Then we already exist and should abort.
		); 
	});
	if (ExistingInfo == nullptr) {
		FPrimaryAssetTypeInfo NewInfo = FPrimaryAssetTypeInfo();
		NewInfo.PrimaryAssetType = InAssetType;
		NewInfo.SetAssetBaseClass(InAssetBaseClass);
		FDirectoryPath NewDir = FDirectoryPath();
		NewDir.Path = InDirectory;
		NewInfo.GetDirectories().Add(NewDir);
		NewInfo.Rules.CookRule = EPrimaryAssetCookRule::AlwaysCook;
		AssetManagerSettings->PrimaryAssetTypesToScan.Add(NewInfo);
	}
}

User friendly they said. There a better way?

Hello, I found a nice fix for this problem.

  1. Create a string table in unreal editor (make sure you specify its residing folder in the cooked directories)
  2. Create a TSoftObjectPtr to your string table e.g. TSoftObjectPtr LocalizationData; in any class header which you can access in Project Settings through DefaultGame.ini. Assign your string table in project settings like this
  3. You can now create FText variable by referencing strings from string tables like this:
FSoftObjectPath LocalizationDataPath = DataTableSettings->LocalizationData.ToSoftObjectPath();
if (LocalizationDataPath.IsValid())
{
	FName TableId = DataTableSettings->LocalizationData.ToSoftObjectPath().GetAssetPathName();
	
	EmptyString = FText::FromStringTable(TableId, "Container_Txt_Empty");
	SearchedString = FText::FromStringTable(TableId, "Container_Txt_Searched");
}

DataTableSettings in the code above is an object of class where TSoftObjectPtr to my string table was defined. This worked without any issues for me.

This would reference this one table so it might be collected for localization? but as far as I’m aware it is not a guarantee and would only work for this string table alone, because others are not referenced and need still need to be loaded / referenced (it’s some time ago I did this, I don’t remember all of it). Another issue with project settings is that when you rename or move the asset the soft pointer breaks. I don’t think I ever got the implementation optimal / correct even though it also works with the code i posted.