Confusion with String Table localization, c++

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?