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
| Key | Required | Description |
|---|---|---|
SourcePath | ✅ | The target’s localization data directory |
ManifestName / ArchiveName | ✅ | e.g. Game.manifest / Game.archive |
NativeCulture | ✅ | The native language |
CulturesToGenerate | ✅ | The cultures to export (may appear more than once) |
DestinationPath | ✅ | The output directory, laid out as <DestinationPath>/<Culture>/<Clan>.cliff |
CliffNamespace | ✅ | The CLIFF header’s namespace (taken from the CLIFF Namespace project setting) |
SourceLanguage | ⛔ | Overrides the header’s source-language (defaults to NativeCulture) |
Data sources
| Path | When it is used | Capability |
|---|---|---|
FLocTextHelper::LoadAll (manifest + every culture archive) | Main path | Complete: source text, translations, cliff.* metadata and SourceLocation are all there |
ExportFromLocResFile (reads .locres directly) | When only the binary artefacts exist | Degraded: 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
.locresholds only aSourceStringHashand the translation — no source text and no metadata. A purely binary export necessarily loses thetype/emotion/statusdistinctions 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:
| ManifestName | header clan | File name |
|---|---|---|
Game.manifest | cliff-game | cliff-game.cliff |
CliffLocalizationSuiteRuntime.manifest | cliff-localization-suite-runtime | cliff-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):
cliff.clanmetadata (KeyMetadataObjof the archive entry) — the most reliable, coming from the previous importNamespacereverse mapping table (the inverse lookup ofClan to Namespace)- kebab-slug fallback: convert the UE Namespace to lower-case kebab-case (
UObjectDisplayNames→u-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 Namespaceentries 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:
- Metadata: reuse
cliff.group/cliff.entrywhen they are legal (guarantees a stable round trip) - Derivation from
SourceLocation: take the last segment of the code path (/Script/MyGame.GlyphAnimationLayer:AddShadowColorAt→glyph-animation-layer), cut off the metadata description after[, and strip extensions such as.cpp/.h/.uassetand a trailing_C - 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 - 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 useentry-<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 field | Source priority |
|---|---|
type | ① cliff.type metadata ② Namespace to Type mapping table ③ nothing → sentence + warning |
emotion | ① cliff.emotion metadata ② Namespace to Emotion mapping table ③ nothing → the type default (dialogue/monologue/idiom → [neutral], everything else → [objective]) |
status | ① cliff.status metadata ② translation present → translated; no translation → initial |
context | ① cliff.context metadata ② SourceLocation + SiteDescription + DevNotes concatenated |
reference | SourceLocation (file:line or asset path) |
max-width | Exported only when it exists in the metadata (UE has no corresponding source) |
reviewer | Exported 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
statuscan only land ontranslated/initial. Thereviewed/finaldistinction depends entirely on thecliff.statusmetadata and the sidecar written by the previous import.
Text fidelity
source/targetare 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
- Every serialized document first goes through
FCliffDocument::ParseAndValidate - Any error-level issue → the export fails and lists the issues (with line numbers)
- The commandlet summarises:
CLIFF export succeeded: <documents> documents, <entries> entries -> '<DestinationPath>'.
TIP
Export is the review entry point:
.cliffis 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.