After upgrading to UE 5.6 from UE 5.5, we found that gathering loc from assets no longer works.
Context about our project setup:
- We define our localization text in `.uasset` files, and we use the `GatherTextFromAssets` commandlet to gather them, and then pass them to our translators.
- We configure our localization target through the Localization dashboard, and our asset gather step is configured with `IncludePathFilters=%LOCPROJECTROOT%Plugins/GameFeatures/*`
- The GameFeatures directory contains a number of Game Feature plugins, whose assets we want to scan. The number of plugins is large and we don’t want to list them 1 by 1.
With UE 5.5 this worked (Gather would find our text), with 5.6 it doesn’t work (Gather finds 0 text).
Culprit:
This change updated asset filters, and more specifically the FirstPassFilter, which I think is an optimization to load less assets when a plugin path is used (all the way to a Plugin’s `Content` directory), to set `InOutFilter.WithoutPackageFlags = PKG_Cooked;`
This FistPassFilter is later checked with `IsEmpty()` to determine if we should scan all assets, or just the assets pertaining to the filter, however, `IsEmpty()` takes into account the `WithoutPackageFlags` that was already set to non 0, so the `IsEmpty()` check never evaluates to `true`. See:
How to fix
I would like to discuss possible ways to fix this, since I don’t have context on all the possible scenarios of using the commandlet.
Option A:
In `UGatherTextFromAssetsCommandlet::ApplyFirstPassFilter`, replace the `if (InFilter.IsEmpty())`
with `if ((InFilter.PackageNames.Num() + InFilter.PackagePaths.Num() + InFilter.SoftObjectPaths.Num() + InFilter.ClassPaths.Num() + InFilter.TagsAndValues.Num()) == 0)
which more correctly represents the concept of “are there any assets in the first pass filter?”
Option B:
Move the setting of the `WithoutPackageFlags`, so it happens after we evaluate `IsEmpty()`.
void UGatherTextFromAssetsCommandlet::ApplyFirstPassFilter(/*removed const*/ FARFilter& InFilter, TArray<FAssetData>& InOutAssetDataArray) const
{
// Apply filter if valid to do so, get all assets otherwise.
if (InFilter.IsEmpty())
{
// @TODOLocalization: Logging that the first path filter is empty resulting in all assets being gathered can confuse users who generally rely on the second pass.
// Figure out a good way to still convey the information in a log or clog.
const double GetAllAssetsStartTime = FPlatformTime::Seconds();
IAssetRegistry::GetChecked().GetAllAssets(InOutAssetDataArray);
UE_LOG(LogGatherTextFromAssetsCommandlet, Display, TEXT("Loading all assets from asset registry took %.2f seconds."), FPlatformTime::Seconds() - GetAllAssetsStartTime);
}
else
{
const double GetAllAssetsWithFirstPassFilterStartTime = FPlatformTime::Seconds();
// NEW CODE HERE - Note we can also rename InFilter to InOutFilter since it's changed
InFilter.bIncludeOnlyOnDiskAssets = true;
InFilter.WithoutPackageFlags = PKG_Cooked;
// ----
IAssetRegistry::GetChecked().GetAssets(InFilter, InOutAssetDataArray);
UE_LOG(LogGatherTextFromAssetsCommandlet, Display, TEXT("Getting all assets with first pass filter from asset registry took %.2f seconds."), FPlatformTime::Seconds() - GetAllAssetsWithFirstPassFilterStartTime);
}
}
I think that Option B is more maintainable, but I’m curious about what you think, and possibly another option.