Import Pipeline
Import is the combination of two official steps — CliffImport + GenerateTextLocalizationResource: the first turns .cliff into manifest / archive, the second compiles the archive into .locmeta / .locres.
Settings
The import commandlet reads them from the current step of the GatherText configuration:
| Key | Required | Description |
|---|---|---|
SourcePath | ✅ | The target’s localization data directory (Content/Localization/<Target>) |
ManifestName | ✅ | e.g. Game.manifest |
ArchiveName | ✅ | e.g. Game.archive |
NativeCulture | ✅ | The native language, e.g. zh-Hans |
CulturesToGenerate | ✅ | The cultures to write this run (may appear more than once) |
CliffFilePath | ✅* | Absolute path of a single .cliff (may appear more than once) |
CliffDirectory + CliffImportCulture | ✅* | Batch directory import: recursively scan the directory for .cliff and pick only the files matching the given culture |
* CliffFilePath and CliffDirectory are mutually exclusive.
Parsing and validation
Every file is read in full and handed to FCliffDocument::ParseAndValidate:
- Any error-level issue (
syntax/semantic/vocabulary/icu/id) → that file fails to import, and the log prints the line number and category of every issue extensionandwarningissues do not affect the import; they are only recorded- Layout consistency between file name/directory and header is checked here too (
header clanmust equal the file name,header target-languagemust equal the directory name or the language segment in the file name)
Manifest write
For every entry:
Namespace = 语义映射表(clan) # 例如 display-name → UObjectDisplayNames
无显式映射时回退为 CLIFF header 的 namespace,并记录 warning
Key = sidecar 原键,否则 <group-path>.<entry-id>
Source = entry.source
Context = entry.context + SourceLocation(导入文件路径与行号)
KeyMetadataObj = { cliff.namespace, cliff.clan, cliff.group, cliff.entry,
cliff.type, cliff.emotion, cliff.status, cliff.context }
cliff.* metadata is the only persistent home of the semantics: UE itself has no type / emotion / status fields, and export relies on exactly this metadata to reconstruct the semantics back into CLIFF.
IMPORTANT
One identity (Namespace + Key + Source) never adds a duplicate context; the metadata is attached to the context that already exists. The official
GenerateLocResresolves the archive context by context using the manifest metadata, so duplicated contexts with inconsistent metadata would resolve one key into two different translations (a conflict, first writer wins), and repeated imports would keep accumulating junk contexts.
Archive write
CLIFF status | Write behaviour |
|---|---|
initial | Manifest only; no archive translation is written |
translated / reviewed / final | Writes the archive translation for the matching culture |
Two guards before the write:
- Empty targets are refused: an empty
targetstring is not written to the archive. If the source text already belongs to the target language, the source text is filled in locally (the model was supposed to return it unchanged); otherwise it counts as a failure. Legacy empty-translation entries are restored in place to the source text during import, so that compilation cannot produce empty strings that make the Slate UI render blank. - Self-translations are refused:
target == sourcewhile the Unicode script family of the source text does not belong to the target language is judged a “self-translation” and not written. (For example the English"DeepSeek response has no message content."under a zh-Hans target.)
Effective source and order independence
UE uses the source text as the translation identity (PO semantics), and the official Translation Editor in turn treats the native translation as the display source of foreign archive entries (ELocTextExportSourceMethod::NativeText). That produces a classic trap: once the source language has been translated, the Source of every foreign entry no longer matches the “effective source”, and the Translation Editor flags them all as “needs review”.
What the plugin does:
- Archive entries of non-native languages record their Source by the same
NativeTextrule, so the criterion matches the official one; - When the source language is imported, the Source of every foreign archive entry is rewritten to the new effective source (translations untouched), and legacy entries without metadata are normalised in place and given metadata;
- Inside an import task the native language is sorted first.
So importing only the source language, only foreign languages, or everything in one mixed run converges in a single run — the two-phase “translate the source language first, then import everything” workflow is no longer needed.
Compilation and live refresh
After the import step, the official GenerateTextLocalizationResource step (GatherTextStep1 of the same ini):
FTextLocalizationResourceGenerator::GenerateLocMeta(LocTextHelper, ResourceName, LocMeta)
FTextLocalizationResourceGenerator::GenerateLocRes(LocTextHelper, Culture, EGenerateLocResFlags::None, …)
↓ 写 .locmeta 与每个 culture 的 .locres(含 NativeCulture)
FTextLocalizationManager::Get().UpdateFromLocalizationResource(LocRes)
↓ 编辑器立即看到新译文,无需重启
EGenerateLocResFlags::None: format validation is disabled and ICU text passes through verbatim (UE’sFText::Formatdoes not implement ICU runtime evaluation).- The compiled culture list includes NativeCulture: the plugin’s own UI text uses the native language, and without rebuilding the native
.locresthe editor interface text would be lost or fall back. - Once all tasks finish, the toolbox broadcasts
LocalizationDelegates::OnLocalizationTargetDataUpdated, and the Localization Dashboard word counts and statuses refresh with it.
Sidecar restoration
The Key restoration order during import:
<file name>.cliffmap.jsonin the same directory as the imported file<manifest stem>.cliffmap.jsonone directory up- Neither → generate the Key from the
Clan to Namespacemapping +<group-path>.<entry-id>and log a warning
The sidecar’s id field (the canonical ID) only takes effect when it matches the current document’s namespace.clan.group.entry exactly, so extra languages and repeated exports can be appended to the same sidecar file without cross-contamination.
Failure semantics
| Situation | Behaviour |
|---|---|
One .cliff fails validation | That file fails and prints every issue; the whole batch fails |
The directory scan finds no .cliff | Error 「没有找到可导入的 .cliff 文件。」 |
| A required setting is missing | Error naming the missing item (e.g. Missing required CLIFF import settings in section '…') |
| Compilation / save fails | The official FLocTextHelper error is thrown as-is; the log carries the path and the reason |
| A single entry problem (empty target / self-translation / placeholder) | Warn only and skip that entry; the import as a whole is not interrupted |
TIP
Import is idempotent: importing the same
.cliffagain produces no duplicate entries and accumulates no duplicate contexts. To roll back, use the official Translation Editor or import the previous.cliffagain.