Localization QA Gate for Unreal Engine 5.8
Unreal Engine 5.8 · Win64 · Full C++ source included · No third-party code · No other plugin required
LocaleGuard checks localization. It never performs it. Nothing in this plugin translates text,
imports a CSV, edits a string table or writes to an asset — it reads, it measures, and it reports.
That boundary is deliberate and it holds in every part of the plugin: the editor window, the runtime
overlay and the build-server commandlet are all read-only.
Edit > Plugins, search for LocaleGuard, tick Enabled.LocaleGuard folder into <YourProject>/Plugins/LocaleGuard/, so that<YourProject>/Plugins/LocaleGuard/LocaleGuard.uplugin exists..uproject → Generate Visual Studio project files.Development Editor | Win64) and start the editor.A C++ project is not required to use LocaleGuard: with pre-built binaries installed from Fab, a
Blueprint-only project can enable the plugin, open the editor window, run the commandlet and call
every Blueprint node listed in §10.
Tools > Locale Guard exists in the main menu.Project Settings > Plugins > LocaleGuard exists.LocaleGuard. into the console offers five completions.A. Scan your content in the editor
Tools > Locale Guard./Game into Path and de (or your target language) into Cultures.B. Author a rule set
Miscellaneous > Data Asset → LocaleGuardRuleSet.DA_LocaleRules.Source Culture (e.g. en), fill Target Cultures (e.g. de, ru), and add any folderExcluded Paths (debug menus, cheat screens, test maps).Project Settings > Plugins > LocaleGuard → point Default Rule Set at it.C. See overflow in the running game
LocaleGuard.Culture de, then LocaleGuard.Show 1, then LocaleGuard.Scan.UTextBlock wider than the box the layout gave it gets a red frame with the overhang inD. Gate the build
UnrealEditor-Cmd.exe YourProject.uproject -run=LocaleGuard -Path=/Game -Cultures=de,ru ^
-FailOn=Error -Format=json -Out=Saved/LocaleGuard/report.json -unattended -nopause -nosplash
Exit code 0 clean · 1 findings at or above the threshold · 2 the run itself was misconfigured.
Text that is displayed as a literal string rather than as an FText with a namespace and a key. The
engine has no identity for it, so the localization gather never sees it, so no translator ever sees
it — and because it looks perfectly correct in the source language, nobody notices until a player
who does not speak that language does.
LocaleGuard finds it in three places:
| Where | What is reported |
|---|---|
A UTextBlock in a widget blueprint |
Its Text has no key (a plain literal or a culture-invariant FText) |
| A Blueprint graph node | An unconnected String input pin with a literal typed into it, on a node that outputs Text — i.e. a string converted to text and displayed |
| A Blueprint graph pin | An unconnected Text input pin holding a culture-invariant literal |
An FText literal typed into a Blueprint pin normally does get a namespace and key from the
editor, and is therefore correctly translatable. That is why the check targets the string-to-text
conversion instead: it is the one that genuinely escapes the gather.
Filters keep the first scan of a real project readable, because the naive version of this check
reports every -, every 42 and every %.1f:
Minimum Literal Length (2) — a single character is a separator, not language.Ignore Literals Without Letters (on) — 1234, 00:00, %.1f are formatting, not language.Allowed Literals — wildcards, e.g. TODO* for placeholder text.Per target culture, for every key the source culture defines:
The answers come from the compiled .locres files in Content/Localization/<Target>/<Culture>/,
read directly, with the culture fallback chain applied — so German filed under de answers a
question asked about de-DE. .locres is what a packaged build actually loads, so what LocaleGuard
checks is what ships.
If a culture has no
.locresat all, LocaleGuard logs a warning and reports nothing for that
culture, rather than reporting every string as missing. "Not checked" and "nothing translated" are
different sentences and a build server has to be able to tell them apart. Run the Localization
Dashboard's Compile step first.
Is this string, in this font, wider than the box the layout gave it?
Measured with FSlateFontMeasure — the same service Slate uses to lay the text out on screen — so a
"fits" here means the same thing as a "fits" in the running game. The finding carries the overhang in
pixels, which is the number a layout artist can act on.
Where the available width comes from:
| Context | Available width |
|---|---|
Running game (LocaleGuard.Scan) |
The text block's real laid-out geometry. Every visible UTextBlock, every panel type, no exceptions. This is the definitive check. |
| Editor / commandlet | Only a UCanvasPanelSlot with Size To Content off has a design-time width. A horizontal box, a wrap box or a size box driven by its parent has no width until the layout runs. |
That split is not a limitation to work around, it is the reason the runtime half of this plugin
exists. The offline scan catches what it can; the running game catches everything.
The pseudo-localization reserve. When a target culture has no translation yet, the source string
is measured and multiplied by Pseudo Localization Factor (1.3 by default) before being compared
with the box. German runs roughly 30 % longer than English; Russian and Finnish want more. This is
what lets you check a screen in the sprint it was built, while somebody still remembers why that
button is that width. Once a real translation exists, the real string is measured instead — inflating
a translation you already have would only produce findings nobody can reproduce on screen.
ULocaleGuardRuleSet is a Data Asset. Right-click in the Content Browser →
Miscellaneous > Data Asset > LocaleGuardRuleSet. Reset To Defaults fills in a sane starting
point.
| Field | Meaning |
|---|---|
Source Culture |
What the text is authored in, e.g. en. Never reported as missing. |
Target Cultures |
The languages that must be complete. Empty falls back to Project Settings, then to the cultures your project's localization targets already declare. |
Excluded Paths |
Folders the check leaves alone — debug menus, developer cheats, test maps. A path without a wildcard is a folder: /Game/Debug matches /Game/Debug and everything under it, and not /Game/DebugTools. Exclusions are counted in the summary, so an exception list that has quietly grown to swallow half the project is visible. |
Hardcoded Text |
Enable, severity, and the literal filters described above. |
Missing Translation |
Enable, separate severities for missing and empty, the identical-to-source switch, ignored namespaces. |
Overflow |
Enable, severity, overhang tolerance in pixels (1.0 — font measurement and layout rounding disagree by fractions of a pixel), the pseudo-localization factor, whether the reserve also applies to text that is translated, whether the source culture is checked against its own box, and widget-name wildcards to skip. |
Max Findings |
Cap. Zero means no cap. The report says so when it truncated. |
Every check carries its own severity of Info, Warning or Error, so a house rule can be
advisory while a shipping-blocker is fatal.
Project Settings > Plugins > LocaleGuard (ULocaleGuardSettings, saved to DefaultGame.ini):
| Setting | Meaning |
|---|---|
Default Rule Set |
The rule set the window and the commandlet use when none is named. |
Default Scan Path |
The content path the window and the commandlet start on. |
Default Target Cultures |
Used when neither the command line nor the rule set names any. Empty means "ask the project's localization targets", which is usually right. |
Fail On Severity |
The severity at or above which the commandlet returns exit code 1. |
Show Overflow Overlay On Start |
Draw the red frames from the moment the game starts. Off by default. |
Live Scan Interval Seconds |
Re-run the live scan automatically. Zero (default) means on command only. |
Pseudo Localization Reserve On Start |
Apply the growth reserve on the live overlay from the start. |
Default Report File Name |
Where reports go under Saved/ when no path is given. |
Once these are set, a build-server invocation can be as short as -run=LocaleGuard.
Tools > Locale Guard.
/Game/UI.de, ru. Overrides the rule set for this one scan, which isSaved/.The table sorts on every column, filters by severity and by check, and searches asset, widget, key,
namespace and message. Double-click a row to jump the Content Browser to the asset — by asset
data, without loading it, because loading an asset would be a strange thing for a tool that promises
to change nothing.
Only asset types that can carry authored text are read: Blueprints (including widget blueprints),
String Tables, Data Assets and Data Tables. Loading a texture to look for FText on it would triple
the cost of every scan, and a scan people avoid running is a check that does not exist.
The game instance subsystem registers five console commands.
| Command | What it does |
|---|---|
LocaleGuard.Scan |
Walks every visible UTextBlock, measures it, runs the same checker the editor uses, prints the report. |
LocaleGuard.Culture <code> |
Switches the game's culture and rescans. With no argument, prints the current one. |
LocaleGuard.Pseudo 0\|1 |
Applies the growth reserve to text with no translation yet. Rescans. |
LocaleGuard.Show 0\|1 |
Draws a red frame around every overflowing text block, with the overhang in pixels beside it. |
LocaleGuard.Export [path] |
Writes the last report. No path means Saved/. |
The overlay is a hit-test-invisible Slate widget above the game viewport, so it never eats a click
from the button it is accusing. It is off by default — a red frame in a milestone build is a bug
report from a producer — and Project Settings > Plugins > LocaleGuard can turn it on at start for a
dedicated QA build.
The whole thing is also available from Blueprint (see §10.3): Scan Visible Widgets, Set Culture,
Set Overlay Visible, Set Pseudo Localization Reserve, Export Last Report,
Get Last Report Summary and an On Report Ready delegate — enough to build an in-game QA panel
with no C++. The plugin's own demo HUD is built exactly that way.
A live scan only ever measures the language the game is running in. That is the honest answer:
the widths on screen are this culture's widths and nothing else's. Reporting the other target
languages as untranslated on that evidence would be a guess. Switch culture and scan again.
UnrealEditor-Cmd.exe <Project.uproject> -run=LocaleGuard [switches]
-Path=/Game Content path to check. Default: Project Settings.
-RuleSet=<asset path> Rule set. Default: Project Settings, then built-in defaults.
-Cultures=de,ru Languages that must be complete. Default: rule set, then Project
Settings, then the project's localization targets.
-Format=text|json Report format. Default: text.
-Out=<file> Also write the report here. Relative to the project directory.
-FailOn=Info|Warning|Error Threshold for a non-zero exit code. Default: Project Settings.
-Quiet Summary only.
-Help Usage.
| Code | Meaning | What CI should do |
|---|---|---|
0 |
Nothing at or above the threshold. | Pass. |
1 |
Findings at or above the threshold. | Fail the build. Read the JSON. |
2 |
The run itself was wrong — unreadable rule set, unknown -FailOn, unknown format. |
Fail the build loudly. Nothing was checked. |
Code 2 is the one that matters. Without it, a misconfigured job returns "no findings" and a build
server cheerfully reports a pass on a check that never ran.
.github/workflows/localization.yml, or the equivalent step in any other runner:
- name: Compile localization data
shell: cmd
run: >
"%UE_ROOT%\Engine\Binaries\Win64\UnrealEditor-Cmd.exe"
"%GITHUB_WORKSPACE%\YourProject.uproject"
-run=GatherText
-config="Config\Localization\Game_Compile.ini"
-unattended -nopause -nosplash
- name: LocaleGuard
shell: cmd
run: >
"%UE_ROOT%\Engine\Binaries\Win64\UnrealEditor-Cmd.exe"
"%GITHUB_WORKSPACE%\YourProject.uproject"
-run=LocaleGuard
-Path=/Game
-Cultures=de,ru
-FailOn=Error
-Format=json
-Out=Saved\LocaleGuard\report.json
-unattended -nopause -nosplash
- name: Publish the report
if: always()
uses: actions/upload-artifact@v4
with:
name: localeguard-report
path: Saved/LocaleGuard/report.json
The Compile step first, always. LocaleGuard reads compiled .locres, and a job that skips the
compile is a job that checks yesterday's translations.
{
"tool": "LocaleGuard",
"scannedPath": "/Game",
"ruleSet": "/Game/Localization/DA_LocaleRules.DA_LocaleRules",
"timestampUtc": "2026-09-03T09:14:22Z",
"durationSeconds": 4.81,
"entriesChecked": 214,
"entriesExcluded": 11,
"checksEvaluated": 3,
"truncated": false,
"cultures": [ "de", "ru" ],
"counts": {
"error": 4, "warning": 1, "info": 0,
"hardcoded": 1, "missingTranslation": 2, "emptyTranslation": 0, "overflow": 2
},
"findings": [
{
"severity": "Error",
"check": "Overflow",
"assetPath": "/Game/UI/WBP_Settings.WBP_Settings",
"packageName": "/Game/UI/WBP_Settings",
"assetName": "WBP_Settings",
"element": "Text_Graphics",
"culture": "de",
"namespace": "Settings",
"key": "Graphics",
"sourceText": "Graphics",
"overhangPixels": 34.0,
"message": "WBP_Settings.Text_Graphics.Text overflows by 34 px in de: \"Grafikeinstellungen\" needs 154 px and has 120 px."
}
]
}
Every finding names the asset, the widget, the culture, the key and — for an overflow — the exact
number of pixels. Enough for a CI job to annotate a pull request instead of making a human read a
log.
/LocaleGuard/LocaleGuard/Maps/L_LocaleGuardDemo — a graphics settings screen built to fail, on
purpose. Open it from the Content Browser (enable Show Plugin Content if you do not see it) and
press Play.
What is on screen
UI/WBP_LocaleGuardSettingsScreen — seven settings rows. Every label sits in a fixed 200 pxMotion Blur) is deliberately a hardcoded literal with no namespace and no key.UI/WBP_LocaleGuardDemoHUD — the frame around it: a title, the live report panel, and a buttonULocaleGuardSubsystem, so the HUD is also a worked example ofRules/DA_LocaleGuardDemoRules — source culture en, target cultures de and ru, all threeBlueprints/BP_LocaleGuardDemoGameMode, Blueprints/BP_LocaleGuardDemoDirector — the game modeThe button bar
| Button | Call |
|---|---|
| English / Deutsch / Русский | Set Culture + rescan |
| Scan | Scan Visible Widgets |
| Reserve | Set Pseudo Localization Reserve on/off |
| Frames | Set Overlay Visible on/off |
What you see. In English the menu is clean. Press Deutsch and four labels grow past their
200 px boxes: Bildschirmauflösung (+64 px), Qualitätsvoreinstellung (+98 px), Schattenqualität
(+14 px) and Texturdetailgrad (+11 px). Each gets a red frame and its overhang in pixels, and the
report panel on the right lists all four as [Error] [Overflow] plus one [Error] [Hardcoded] for
Motion Blur — five errors in fifteen strings, with thirteen more strings excluded by the rule set.
Press Русский for the same story in a wider alphabet, and Reserve to see the check fire on
untranslated text before a translator has touched it.
The report text ends with the sentence the whole plugin is built around:
LocaleGuard reports only. It never translates, imports or edits text.
The demo map does not ship compiled
.locresfiles, so the missing-translation check reports zero
there by design (§3.2: no localization data means "not checked", never "nothing translated"). To
see that check fire, run the editor window or the commandlet against a project that has run the
Localization Dashboard's Compile step.
| Module | Type | Loading phase | Contains |
|---|---|---|---|
LocaleGuard |
Runtime | PreDefault |
Rules, checker, metrics, translation source, runtime subsystem, overlay, settings, report library |
LocaleGuardEditor |
Editor | Default |
Asset scanner, editor subsystem, Slate report window, commandlet |
Both modules carry "PlatformAllowList": [ "Win64" ].
Why the editor module loads at Default and not PostEngineInit: the engine resolves a
-run=<Name> commandlet class in FEngineLoop::PreInit, immediately after LoadStartupModules(),
which covers PreDefault, Default and PostDefault — and nothing else. A commandlet in a
PostEngineInit module is a commandlet the engine cannot find. Menu and tab registration is deferred
to UToolMenus::RegisterStartupCallback instead.
LocaleGuardTypes.h)| Type | Purpose |
|---|---|
ELocaleGuardSeverity |
Info (0) · Warning (1) · Error (2). Ordered, because -FailOn is a >= comparison. |
ELocaleGuardFindingKind |
HardcodedText · MissingTranslation · EmptyTranslation · Overflow. |
FLocaleGuardCultureData |
What one culture has for one string: bHasTranslation, Translation, bMeasured, MeasuredWidth. |
FLocaleGuardEntry |
The unit of work. Where the text is (AssetPath, PackageName, AssetName, ElementName, PropertyName), what it says (SourceString, Namespace, Key, bIsLiteralText), how it is laid out (bHasLayoutWidth, AvailableWidth, SourceMeasuredWidth, bSourceMeasured) and TMap<FString, FLocaleGuardCultureData> Cultures. |
FLocaleGuardFinding |
One thing that is wrong, in one place, in one culture, from one check — including OverhangPixels and a plain-language Message. |
FLocaleGuardReport |
Findings plus the numbers a summary line needs: EntriesChecked, EntriesExcluded, ChecksEvaluated, CulturesChecked, bTruncated, ScannedPath, RuleSetPath, TimestampUtc, DurationSeconds. Helpers: CountBySeverity, CountByKind, HasAtOrAbove, WorstSeverity. |
FLocaleGuardTextFit |
bFits, MeasuredWidth, AvailableWidth, OverhangPixels. |
Free functions in namespace LocaleGuard: PathMatchesPattern, PathMatchesAny,
SeverityToString, ParseSeverity, FindingKindToString, NormaliseCulture,
GetCultureFallbackChain, Ellipsise.
All structs and enums are BlueprintType.
ULocaleGuardRuleSet : UPrimaryDataAsset — the rules as an asset. Contains
FLocaleGuardHardcodedTextRule, FLocaleGuardMissingTranslationRule, FLocaleGuardOverflowRule
(see §4). Methods: ResetToDefaults() (also a Reset To Defaults button in the details panel),
HasAnyCheck(), GetNormalisedTargetCultures().
ULocaleGuardChecker : UBlueprintFunctionLibrary — the rule engine, one function:
static FLocaleGuardReport CheckTexts(const TArray<FLocaleGuardEntry>& Entries,
const ULocaleGuardRuleSet* RuleSet); // BlueprintCallable
static bool IsReportableLiteral(const FString& Literal,
const FLocaleGuardHardcodedTextRule& Rule); // C++ only
No world, no subsystem, no asset registry, no font cache. A null rule set returns an empty report
rather than a default one — falling back to rules the caller did not choose would let a build server
report a pass on a check it never performed.
ULocaleGuardTextMetrics : UBlueprintFunctionLibrary — the measurement:
static FLocaleGuardTextFit EvaluateFit(float MeasuredWidth, float AvailableWidth,
float TolerancePixels = 1.0f); // BlueprintPure
static bool MeasureStringWidth(const FString& Text, const FSlateFontInfo& Font,
float FontScale, float& OutWidth); // C++ only
static FLocaleGuardTextFit CheckFit(const FString& Text, const FSlateFontInfo& Font,
float AvailableWidth, float FontScale,
float TolerancePixels, bool& bOutCouldMeasure); // C++ only
static bool CanMeasureText(); // BlueprintPure
static FLocaleGuardTextFit CheckTextFit(const FString& Text, const FSlateFontInfo& Font,
float AvailableWidth,
float TolerancePixels = 1.0f); // BlueprintCallable
EvaluateFit is pure arithmetic and works in a headless commandlet; MeasureStringWidth is the half
that needs Slate's font cache and says so when it cannot answer, rather than returning a zero that
would look like text that fits.
FLocaleGuardTranslationSource (plain struct, Core only) — reads compiled .locres directly:
LoadCulture, IsValid, GetCulture, Num, Find(Namespace, Key, OutTranslation),
GetSearchedDirectories. Not FTextLocalizationManager, which only ever holds the culture the
process is currently running in.
ULocaleGuardSubsystem : UGameInstanceSubsystem — the runtime half. Blueprint-exposed:
| Node | Kind |
|---|---|
Scan Visible Widgets (Rule Set) → FLocaleGuardReport |
Callable |
Set Culture (Culture Code) → bool |
Callable |
Get Current Culture → FString |
Pure |
Set Overlay Visible (bVisible) / Is Overlay Visible |
Callable / Pure |
Set Pseudo Localization Reserve (bEnabled) / Is Pseudo Localization Reserve Enabled |
Callable / Pure |
Export Last Report (AbsoluteFilePath, bAsJson) → bool |
Callable |
Get Last Report / Has Report / Get Last Report Summary |
Pure |
On Report Ready (Report) |
Assignable multicast delegate |
ULocaleGuardSettings : UDeveloperSettings — see §5. ULocaleGuardSettings::Get() in C++.
ULocaleGuardReportLibrary : UBlueprintFunctionLibrary — formatting and filtering:
SummariseReport, FormatReportAsText, FormatReportAsJson, SaveReportToFile,
FilterBySeverity, FilterByKind, FilterByCulture, GetDefaultReportFilePath.
SLocaleGuardOverlay (private Slate widget) — the red frames. Hit-test invisible, so it never
eats a click from the button it is accusing.
FLocaleGuardScanner (static, stateless) — the bridge between assets on disk and the rule
engine: ResolveRuleSet, ResolveCultures, Scan, HarvestEntries, GetProjectTargetCultures.
ULocaleGuardEditorSubsystem : UEditorSubsystem — ScanPath(Path, RuleSet, Cultures),
ScanWithProjectDefaults() (Blueprint-callable), ExportLastReport, GetLastReport, HasReport,
OnReportReady().
SLocaleGuardReportWindow — the Slate table under Tools > Locale Guard. Pure Slate; no UMG in
the editor module.
ULocaleGuardCommandlet : UCommandlet — -run=LocaleGuard, exit codes 0/1/2 (§8).
#include "LocaleGuardChecker.h"
#include "LocaleGuardRuleSet.h"
TArray<FLocaleGuardEntry> Entries;
FLocaleGuardEntry Entry;
Entry.PackageName = TEXT("/Game/UI/WBP_Menu");
Entry.AssetName = TEXT("WBP_Menu");
Entry.ElementName = TEXT("Text_Start");
Entry.PropertyName = TEXT("Text");
Entry.SourceString = TEXT("Start");
Entry.Namespace = TEXT("MainMenu");
Entry.Key = TEXT("Start");
// No entry in Entry.Cultures for "de" -> German has never seen this key.
Entries.Add(Entry);
const FLocaleGuardReport Report = ULocaleGuardChecker::CheckTexts(Entries, MyRuleSet);
for (const FLocaleGuardFinding& Finding : Report.Findings)
{
UE_LOG(LogTemp, Warning, TEXT("%s"), *Finding.Message);
}
That is also exactly how the plugin's own automation tests describe a project — which is why the rule
engine is covered by tests that need no content, no editor and no registry to stand up first.
#include "LocaleGuardTextMetrics.h"
bool bCouldMeasure = false;
const FLocaleGuardTextFit Fit = ULocaleGuardTextMetrics::CheckFit(
TEXT("Grafikeinstellungen"), MyFont, /*AvailableWidth=*/120.0f,
/*FontScale=*/1.0f, /*Tolerance=*/1.0f, bCouldMeasure);
if (bCouldMeasure && !Fit.bFits)
{
// Fit.OverhangPixels is how much wider the box has to get.
}
bCouldMeasure is false when the process has no renderer — a commandlet, a dedicated server. A
zero width and a text that fits are different answers, and LocaleGuard never confuses them.
#include "LocaleGuardTranslationSource.h"
FLocaleGuardTranslationSource German;
if (!German.LoadCulture(TEXT("de")))
{
// No compiled .locres for German: "not checked", not "nothing translated".
UE_LOG(LogTemp, Warning, TEXT("No German localization data in %s"),
*FString::Join(German.GetSearchedDirectories(), TEXT(", ")));
return;
}
FString Translation;
if (!German.Find(TEXT("MainMenu"), TEXT("Start"), Translation))
{
// Missing.
}
else if (Translation.IsEmpty())
{
// Present and blank - a different finding.
}
#include "LocaleGuardSubsystem.h"
#include "LocaleGuardReportLibrary.h"
if (ULocaleGuardSubsystem* Guard = GetGameInstance()->GetSubsystem<ULocaleGuardSubsystem>())
{
Guard->SetCulture(TEXT("de"));
Guard->SetPseudoLocalizationReserve(true);
const FLocaleGuardReport Report = Guard->ScanVisibleWidgets(/*RuleSet=*/nullptr);
Guard->SetOverlayVisible(Report.Findings.Num() > 0);
UE_LOG(LogTemp, Log, TEXT("%s"), *ULocaleGuardReportLibrary::SummariseReport(Report));
}
Get Game Instance Subsystem → LocaleGuardSubsystem.Format Report As Text → set your Text Block.de / ru / en) → Scan Visible Widgets./LocaleGuard/LocaleGuard/UI/WBP_LocaleGuardDemoHUD is this, built out.
// LocaleGuardEditor module only.
#include "LocaleGuardEditorSubsystem.h"
if (ULocaleGuardEditorSubsystem* Editor = GEditor->GetEditorSubsystem<ULocaleGuardEditorSubsystem>())
{
const FLocaleGuardReport Report =
Editor->ScanPath(TEXT("/Game/UI"), /*RuleSet=*/nullptr, { TEXT("de"), TEXT("ru") });
if (Report.HasAtOrAbove(ELocaleGuardSeverity::Error))
{
Editor->ExportLastReport(FString(), /*bAsJson=*/true);
}
}
Add "LocaleGuardEditor" to your editor module's PrivateDependencyModuleNames, and "LocaleGuard"
for the runtime types.
| Engine | Unreal Engine 5.8 ("EngineVersion": "5.8.0") |
| Target platform | Win64 — both modules carry "PlatformAllowList": [ "Win64" ] |
| Editor platform | Windows 10 / 11, Windows Editor |
| Build configurations verified | UnrealEditor Development, UnrealGame Development, UnrealGame Shipping — all built with RunUAT BuildPlugin -Rocket -TargetPlatforms=Win64, zero warnings |
| Project type | C++ or Blueprint-only (with the pre-built binaries) |
| Source | Full C++ source included |
| Third-party code | None. Engine modules only: Core, CoreUObject, Engine, UMG, Slate, SlateCore, DeveloperSettings, Json (runtime); UnrealEd, AssetRegistry, ToolMenus, EditorSubsystem, Projects, Localization (editor) |
| Other plugins required | None |
Runtime vs. editor. The LocaleGuard runtime module ships in a packaged Win64 game, so the
console commands and the overflow overlay work in a packaged QA build. The LocaleGuardEditor
module — window, asset scanner and commandlet — is editor-only, as an editor module must be.
The runtime overflow overlay needs a renderer. In a dedicated server or a headless process,
ULocaleGuardTextMetrics::CanMeasureText() returns false and no overflow finding is produced (rather
than a false one).
Window > Test Automation, filter LocaleGuard. Eight tests, none of which needs content, a
world or the asset registry:
| Test | Asserts |
|---|---|
LocaleGuard.Checker.FindsHardcodedLiteralButNotLocalizedText |
A literal is reported; an FText with a key is not. |
LocaleGuard.Checker.FindsMissingTranslationPerCulture |
A key absent in one target culture is reported for that culture only. |
LocaleGuard.Checker.EmptyTranslationCountsAsMissing |
A present-but-blank value is its own finding kind. |
LocaleGuard.Checker.LiteralFiltersSkipNoiseNotLanguage |
42, -- and %.1f are skipped; words are not. |
LocaleGuard.TextMetrics.ReportsOverhangAndFits |
Correct overhang on a wide string, bFits on a short one. |
LocaleGuard.RuleSet.ExcludedPathSuppressesFindings |
An exception path suppresses the finding and increments EntriesExcluded. |
LocaleGuard.RuleSet.EmptyRuleSetGivesEmptyReport |
A rule set that asks nothing reports nothing. |
LocaleGuard.Report.JsonCarriesTheBuildServerNumbers |
The JSON carries the counts and fields CI parses. |
"Every string in my project is reported as missing in German."
The German .locres was not found, and an older build of any tool would have reported exactly that.
LocaleGuard does not: if you are seeing this, the resources were found but are empty. Run the
Localization Dashboard's Compile step and check Content/Localization/<Target>/de/.
"The German culture reports nothing at all."
No .locres for it — "not checked". Look for the warning in the log; it lists the directories that
were searched.
"The editor scan finds no overflow, the game finds plenty."
Expected, and documented in §3.3. Only a UCanvasPanelSlot with Size To Content off has a
design-time width. Everything laid out by a box or a size box has no width until the layout runs —
use LocaleGuard.Scan in PIE.
"The first scan returns hundreds of hardcoded findings."
Raise Minimum Literal Length, keep Ignore Literals Without Letters on, and put debug and
developer folders into Excluded Paths. Then drop the hardcoded check to Warning until the backlog
is worked off.
"The commandlet returns 2."
The run was misconfigured, not the content. Check -RuleSet (does the asset path resolve?),
-FailOn (Info/Warning/Error) and -Format (text/json). Nothing was checked.
"Tools > Locale Guard is missing."
The editor module did not load. Confirm the plugin is enabled, that you are on Win64, and check the
log for LogLocaleGuard.
"I cannot see the demo map."
Content Browser → Settings → tick Show Plugin Content.
PlatformAllowList..locres. Uncompiled means "not checked", andURichTextBlock markup would need its own measurement pass; theUTextBlock path is what this version covers.Docs/DOCUMENTATION.md (this file) and the plugin's DocsURL.SupportURL. Please include your engine version, the pluginLocaleGuard.uplugin, and the exported JSON report when reporting a finding youVersion 1.0.0 · Unreal Engine 5.8 · Win64
Copyright 2026 Silvan Teufel. All Rights Reserved.