Architecture Overview
The plugin has one guiding rule: CLIFF is a step of the official localization pipeline, not a replacement for it. It therefore never patches the engine, never writes .locres binaries itself and never builds its own Dashboard window — it plugs into extension points the engine already exposes.
Goals and constraints
| Goal | How it is achieved |
|---|---|
| No engine modifications | Only public Core / Developer / Editor types; zero engine diff |
| No bypassing official data | Writes go through the official FLocTextHelper and FTextLocalizationResourceGenerator |
| Native to the official workflow | Registers an official LocalizationService provider; buttons are injected into the official Dashboard toolbar |
| Headless capable | Import, export and AI translation are all UGatherTextCommandletBase subclasses drivable by -run=GatherText |
| No runtime dependencies | The Runtime module has no editor dependencies, no Python and no HTTP |
Module layout
Plugins/CliffLocalizationSuite/
├── CliffLocalizationSuite.uplugin
├── Content/Localization/ # bundled translations (7 targets × 15 languages)
└── Source/
├── CliffLocalizationSuite/ # Runtime: data model and parsing
│ ├── Public/Cliff/CliffDocument.h
│ ├── Public/Cliff/CliffSemanticMapping.h
│ ├── Public/Cliff/CliffSerializer.h
│ ├── Public/Settings/CliffLocalizationSuiteSettings.h
│ └── Private/… # parser, validator, settings, tests
└── CliffLocalizationSuiteEditor/ # Editor: pipeline integration
├── Public/Cliff/CliffImporter.h
├── Public/Cliff/CliffExporter.h
├── Public/AI/CliffAITranslator.h
├── Public/Commandlets/CliffGatherCommandlets.h
└── Private/… # toolbox, provider, progress window, AI client, tests
| Module | Type | Loading phase | Responsibility |
|---|---|---|---|
CliffLocalizationSuite | Runtime | Default | CLIFF data model, parser, validator, serializer, semantic mapping, UDeveloperSettings |
CliffLocalizationSuiteEditor | Editor | Default | Importer, exporter, AI translation, Dashboard integration, GatherText commandlets, console commands |
Dependencies
// Runtime: pure data and algorithms, safe for Game targets
PublicDependencyModuleNames: Core, CoreUObject, Engine, DeveloperSettings
// Editor: official pipeline and editor UI
PublicDependencyModuleNames: CliffLocalizationSuite, Core, CoreUObject, Engine,
LocalizationService, Slate, SlateCore
PrivateDependencyModuleNames: ApplicationCore, DesktopPlatform, EngineSettings, HTTP, InputCore,
Json, Localization, LocalizationCommandletExecution, UnrealEd
NOTE
The Runtime module does not depend on
UnrealEd,AssetToolsorSlate, so Game and Shipping targets package cleanly. The Editor module isType: Editorand never ships at runtime.
Official integration points
The UE 5.8 localization pipeline is: editor action → generated ini → GatherText subprocess runs steps → manifest / archive → FTextLocalizationResourceGenerator produces .locmeta / .locres → FTextLocalizationManager lookups.
There is no registration API for third-party file formats (PO and CSV are hard-coded commandlet behaviour inside the ini the Dashboard generates), so the plugin uses four official extension points:
| # | Extension point | Official type | How the plugin uses it |
|---|---|---|---|
| 1 | GatherText step discovery | GatherTextStep{N} + CommandletClass in the ini, reflected by UGatherTextCommandlet | CliffImport / CliffExport / CliffAITranslate become ordinary pipeline steps |
| 2 | Localization Service Provider | The ILocalizationServiceProvider modular feature (registered as "LocalizationService") | Injects the CLIFF buttons into the official Dashboard target and target-set toolbars |
| 3 | Official compilation | FTextLocalizationResourceGenerator::GenerateLocMeta/GenerateLocRes | .locmeta / .locres are produced by the engine, never by the plugin |
| 4 | Official refresh | FTextLocalizationManager::UpdateFromLocalizationResource, LocalizationDelegates::OnLocalizationTargetDataUpdated | Import refreshes live text and the Dashboard cache immediately |
Key classes
| Class / struct | Module | Purpose |
|---|---|---|
FCliffDocument | Runtime | Parse entry point: Parse / ParseAndValidate / Validate / GetCanonicalId |
FCliffHeader / FCliffGroup / FCliffEntry | Runtime | The CLIFF data model |
FCliffValidationIssue / ECliffValidationCategory | Runtime | Seven issue categories; IsError() excludes extension and warning |
FCliffSemanticMapping | Runtime | clan ↔ UE Namespace and Namespace → type / emotion tables |
FCliffSerializer | Runtime | Canonical CLIFF 1.0 serialization (UTF-8 without BOM, LF) |
UCliffLocalizationSuiteSettings | Runtime | Project Settings (Identity / Semantic Mapping / Workflow / AI Translation) |
FCliffImporter | Editor | .cliff → manifest / archive → compile → live refresh |
FCliffExporter | Editor | manifest + archive (or .locres) → grouped .cliff plus a key sidecar |
FCliffEditorToolbox | Editor | Dashboard buttons, file dialogs, GatherText config generation, task assembly |
FCliffLocalizationServiceProvider | Editor | The official provider implementation (toolbar injection) |
FCliffAITranslator | Editor | DeepSeek orchestration, self-translation detection, connection test |
UCliffImportCommandlet / UCliffExportCommandlet / UCliffAITranslateCommandlet | Editor | The three official GatherText steps |
FCliffCommandletExecutorUI | Editor | The official-style Slate progress window |
Trade-offs
Why a custom commandlet plus provider injection instead of a bespoke window
The official Dashboard offers no way to add a new format button, but it does expose the ILocalizationServiceProvider toolbar hook and GatherText step discovery. Going through them keeps the logs identical to the official ones (LogGatherTextCommandlet sectioned output), keeps subprocess behaviour identical, and lets CI reuse the same ini configuration. The cost is that button behaviour is bound to the Dashboard’s target/target-set context.
Why a sidecar is mandatory
.locres stores only a SourceStringHash — no source text and no metadata — while UE keys are frequently the source text itself (Chinese, |, capitals and / are all legal), which cannot be a CLIFF name. The exporter therefore generates stable IDs and records “original UE Namespace / Key ↔ canonical ID” in .cliffmap.json, which the importer replays for a lossless round trip.
Why status is degraded on export
The official review model is binary — “has a translation whose source matches ⇒ reviewed” — with no reviewed / final distinction and no emotion. The plugin therefore stores the CLIFF four-state status and emotion in the archive’s KeyMetadataObj (cliff.status, cliff.emotion) and in the sidecar; where that information is missing, status degrades to the official semantics (no translation → initial, translation present → treated as reviewed).
Why ICU is passed through verbatim
UE’s FText::Format only understands plural / ordinal / gender / hpp and does not evaluate ICU MessageFormat at runtime. The plugin therefore stores and returns ICU strings unchanged and compiles .locres with EGenerateLocResFlags::None to disable format validation; CLIFF-side brace balancing still runs during parsing.
Bundled localization data
The plugin’s own UI strings ship with it. The .uplugin declares seven targets:
| Target | Loading policy |
|---|---|
CliffLocalizationSuiteRuntime | Always |
CliffLocalizationSuiteEditor | Editor |
CliffLocalizationSuiteEditorTutorials | Never |
CliffLocalizationSuitePropertyNames | PropertyNames |
CliffLocalizationSuiteToolTips | ToolTips |
CliffLocalizationSuiteKeywords | Editor |
CliffLocalizationSuiteCategory | Editor |
Each target covers native en plus 15 languages (zh-Hans, en, ja, ko, es, pt, ar, pl, de, ru, fr, pt-BR, tr, es-419, it) under Content/Localization/. This is exactly why the AI pipeline compiles the native culture too — otherwise the editor UI’s own language .locres would never be rebuilt.
Next
- Data flow — the three chains step by step
- Import pipeline and export pipeline
- API: runtime class reference