Export Pipeline

Export turns the official localization data (manifest + archive, or a compiled .locres) into one .cliff file per culture, and additionally writes the original-key sidecar.

Settings

KeyRequiredDescription
SourcePathThe target’s localization data directory
ManifestName / ArchiveNamee.g. Game.manifest / Game.archive
NativeCultureThe native language
CulturesToGenerateThe cultures to export (may appear more than once)
DestinationPathThe output directory, laid out as <DestinationPath>/<Culture>/<Clan>.cliff
CliffNamespaceThe CLIFF header’s namespace (taken from the CLIFF Namespace project setting)
SourceLanguageOverrides the header’s source-language (defaults to NativeCulture)

Data sources

PathWhen it is usedCapability
FLocTextHelper::LoadAll (manifest + every culture archive)Main pathComplete: source text, translations, cliff.* metadata and SourceLocation are all there
ExportFromLocResFile (reads .locres directly)When only the binary artefacts existDegraded: source text is recovered from the manifest in the same directory; entries that cannot be recovered are warned about one by one and land in sentence

WARNING

.locres holds only a SourceStringHash and the translation — no source text and no metadata. A purely binary export necessarily loses the type / emotion / status distinctions and the original Key, so it is only advisable for consistency verification (“can the binary be read back?”), never as the everyday export path.

Clan and file partitioning

File-level clan (written into the header and the file name): the manifest stem, kebab-cased — that is, one .cliff file per culture:

ManifestNameheader clanFile name
Game.manifestcliff-gamecliff-game.cliff
CliffLocalizationSuiteRuntime.manifestcliff-localization-suite-runtimecliff-localization-suite-runtime.cliff

If the kebab-cased result is not a legal name (it must start with a letter and contain only [a-z0-9-]), it falls back to cliff.

Entry-level clan: every entry additionally resolves a semantic clan, written into the sidecar’s clan field (used for auditing and restoration):

  1. cliff.clan metadata (KeyMetadataObj of the archive entry) — the most reliable, coming from the previous import
  2. Namespace reverse mapping table (the inverse lookup of Clan to Namespace)
  3. kebab-slug fallback: convert the UE Namespace to lower-case kebab-case (UObjectDisplayNamesu-object-display-names) and log the warning: UE namespace '…' has no semantic clan mapping; exported as clan '…'.

TIP

Rule 3 should only ever be a safety net. If the export result is full of slugged clan names, the mapping table is missing entries — add the Clan to Namespace entries and you are done. See semantic mapping.

NOTE

There is only one file name, <manifest stem>.cliff, rather than one file per clan: CLIFF’s file unit is (namespace, clan, target-language), so the plugin treats one culture of an entire target as one clan file, and entries distinguish their semantic level by group path.

Group path and entry ID

CLIFF names only allow lower-case kebab-case ([a-z][a-z0-9-]*), while a UE Key is frequently the source text itself (containing Chinese, |, capitals, /). Export generates them in this priority order:

  1. Metadata: reuse cliff.group / cliff.entry when they are legal (guarantees a stable round trip)
  2. Derivation from SourceLocation: take the last segment of the code path (/Script/MyGame.GlyphAnimationLayer:AddShadowColorAtglyph-animation-layer), cut off the metadata description after [, and strip extensions such as .cpp/.h/.uasset and a trailing _C
  3. Key segmentation: treat | and / in the Key as level separators, kebab-case each segment, use the last segment as the entry ID and join the rest into the group path
  4. Stable hash ID: when the Key is a 32-digit hexadecimal GUID use key-<lower-case hex>; when nothing can be derived from the Key use entry-<8-digit hex of CRC32(Namespace|Key)>

Entries are de-duplicated by Namespace|Key inside one culture, so an identity appears exactly once. The original key is not lost — it is written into the sidecar and restored verbatim on import. Example:

UE 输入:Namespace = "UObjectCategory"
        Key       = "MyGame|层|字形微调|阴影染色"
        SourceLocation = "/Script/MyGame.GlyphAnimationLayer:AddShadowColorAt [Function Category meta-data]"

CLIFF 输出(header: namespace: my-game, clan: category):
[glyph-animation-layer]
<add-shadow-color-at>
source: "MyGame|层|字形微调|阴影染色"
type: label
status: translated
reference: ["/Script/MyGame.GlyphAnimationLayer:AddShadowColorAt"]

侧车:my-game.category.glyph-animation-layer.add-shadow-color-at
      ↔ (UObjectCategory, MyGame|层|字形微调|阴影染色)

Semantic field backfill

CLIFF fieldSource priority
typecliff.type metadata ② Namespace to Type mapping table ③ nothing → sentence + warning
emotioncliff.emotion metadata ② Namespace to Emotion mapping table ③ nothing → the type default (dialogue/monologue/idiom[neutral], everything else → [objective])
statuscliff.status metadata ② translation present → translated; no translation → initial
contextcliff.context metadata ② SourceLocation + SiteDescription + DevNotes concatenated
referenceSourceLocation (file:line or asset path)
max-widthExported only when it exists in the metadata (UE has no corresponding source)
reviewerExported only when it exists in the metadata

NOTE

The official review model has only the binary “has a translation whose source matches = reviewed” semantics, so with metadata missing the exported status can only land on translated / initial. The reviewed / final distinction depends entirely on the cliff.status metadata and the sidecar written by the previous import.

Text fidelity

  • source / target are written verbatim; ICU braces, placeholders and HTML / rich-text markup are never rewritten
  • Strings go through the CLIFF escape table (\n, \t, \", \\); every other character is preserved as-is
  • The output is UTF-8 without BOM with LF line endings, and fields use the canonical form (key: value, one space after the colon)

Output layout and sidecar

<DestinationPath>/
├── Game.cliffmap.json              # 原键侧车(本次导出的全部条目)
├── zh-Hans/
│   └── cliff-game.cliff             # 一个 culture 一个文件
└── ja/
    └── cliff-game.cliff

Sidecar structure:

{
  "version": 1,
  "namespace": "my-game",
  "entries": [
    {
      "id": "my-game.category.glyph-animation-layer.add-shadow-color-at",
      "namespace": "UObjectCategory",
      "key": "MyGame|层|字形微调|阴影染色",
      "clan": "category",
      "culture": "zh-Hans"
    }
  ]
}

Field descriptions and restoration rules: see the original-key sidecar.

Self-check and failure

  1. Every serialized document first goes through FCliffDocument::ParseAndValidate
  2. Any error-level issue → the export fails and lists the issues (with line numbers)
  3. The commandlet summarises: CLIFF export succeeded: <documents> documents, <entries> entries -> '<DestinationPath>'.

TIP

Export is the review entry point: .cliff is plain text with stable, diff-friendly file names, so consider putting the export directory (together with the sidecar) under version control and letting translation and review happen in Git rather than in binary assets.