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:

KeyRequiredDescription
SourcePathThe target’s localization data directory (Content/Localization/<Target>)
ManifestNamee.g. Game.manifest
ArchiveNamee.g. Game.archive
NativeCultureThe native language, e.g. zh-Hans
CulturesToGenerateThe 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
  • extension and warning issues do not affect the import; they are only recorded
  • Layout consistency between file name/directory and header is checked here too (header clan must equal the file name, header target-language must 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 GenerateLocRes resolves 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 statusWrite behaviour
initialManifest only; no archive translation is written
translated / reviewed / finalWrites the archive translation for the matching culture

Two guards before the write:

  1. Empty targets are refused: an empty target string 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.
  2. Self-translations are refused: target == source while 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 NativeText rule, 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’s FText::Format does 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 .locres the 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:

  1. <file name>.cliffmap.json in the same directory as the imported file
  2. <manifest stem>.cliffmap.json one directory up
  3. Neither → generate the Key from the Clan to Namespace mapping + <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

SituationBehaviour
One .cliff fails validationThat file fails and prints every issue; the whole batch fails
The directory scan finds no .cliffError 「没有找到可导入的 .cliff 文件。」
A required setting is missingError naming the missing item (e.g. Missing required CLIFF import settings in section '…')
Compilation / save failsThe 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 .cliff again produces no duplicate entries and accumulates no duplicate contexts. To roll back, use the official Translation Editor or import the previous .cliff again.