导入管线
导入是 CliffImport + GenerateTextLocalizationResource 两个官方步骤的组合:前者把 .cliff 变成 manifest / archive,后者把 archive 编译成 .locmeta / .locres。
配置项
导入命令let 从 GatherText 配置的当前步骤读取:
| 键 | 必需 | 说明 |
|---|---|---|
SourcePath | ✅ | Target 的本地化数据目录(Content/Localization/<Target>) |
ManifestName | ✅ | 如 Game.manifest |
ArchiveName | ✅ | 如 Game.archive |
NativeCulture | ✅ | 原生语言,如 zh-Hans |
CulturesToGenerate | ✅ | 本次要写入的 culture(可重复出现) |
CliffFilePath | ✅* | 单个 .cliff 的绝对路径(可重复出现) |
CliffDirectory + CliffImportCulture | ✅* | 目录批量导入:递归扫描目录内 .cliff,只挑选匹配指定 culture 的文件 |
* CliffFilePath 与 CliffDirectory 二者取一。
解析与校验
每个文件先整篇读入并交给 FCliffDocument::ParseAndValidate:
- 有任何 error 级 issue(
syntax/semantic/vocabulary/icu/id)→ 该文件导入失败,日志逐条打印行号与类别 extension与warning级 issue 不影响导入,只记录- 文件名/目录与 header 的布局一致性也在这一步检查(
header clan必须等于文件名,header target-language必须等于目录名或文件名中的语言段)
manifest 写入
对每条 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.* 元数据是语义的唯一持久位置:UE 本身没有 type / emotion / status 字段,导出时正是靠这些元数据把语义还原回 CLIFF。
IMPORTANT
同一身份(Namespace + Key + Source)不会新增重复 context,而是把元数据挂到已存在的 context 上。官方
GenerateLocRes按 manifest context 的元数据逐一解析 archive,重复且元数据不一致的 context 会让同一个 key 解析出两个不同译文(冲突,先到先得),并且重复导入会不断累积垃圾 context。
archive 写入
CLIFF status | 写入行为 |
|---|---|
initial | 只写 manifest,不写 archive 译文 |
translated / reviewed / final | 写对应 culture 的 archive 译文 |
写入前的两道防护:
- 空译文拒绝写回:
target为空字符串时不写 archive。若源文本本身已属于目标语言,则在本地回填源文本(模型本应原样返回);否则视为失败。历史上遗留的空译文条目会在导入时就地恢复为源文本,避免编译出空字符串让 Slate UI 显示空白。 - 自翻译拒绝写回:
target == source且源文本的 Unicode 脚本族不属于目标语言时判为「自翻译」,不写入。(例如 zh-Hans 目标下的英文"DeepSeek response has no message content.")
生效源与顺序无关性
UE 以源文本作为翻译身份(PO 语义),官方 Translation Editor 又把原生译文当作外语条目的显示源(ELocTextExportSourceMethod::NativeText)。这带来一个经典陷阱:一旦翻译了源语言,所有外语条目的 Source 与「生效源」不再一致,Translation Editor 会把它们全部标为「需要检查」。
插件的处理:
- 非原生语言的 archive 条目按同一
NativeText规则记录 Source,使判定口径与官方一致; - 导入源语言时,自动把所有外语 archive 条目的 Source 重写为新的生效源(译文不动),旧版无元数据的条目就地规范化并补元数据;
- 导入任务内原生语言排在最前。
因此:只导入源语言、只导入外语、或一次混合导入,单次运行即可收敛,不再需要「先翻源语言再统一导入」的两阶段工作流。
编译与热刷新
导入步骤之后,官方 GenerateTextLocalizationResource 步骤(同一 ini 的 GatherTextStep1):
FTextLocalizationResourceGenerator::GenerateLocMeta(LocTextHelper, ResourceName, LocMeta)
FTextLocalizationResourceGenerator::GenerateLocRes(LocTextHelper, Culture, EGenerateLocResFlags::None, …)
↓ 写 .locmeta 与每个 culture 的 .locres(含 NativeCulture)
FTextLocalizationManager::Get().UpdateFromLocalizationResource(LocRes)
↓ 编辑器立即看到新译文,无需重启
EGenerateLocResFlags::None:关闭格式校验,ICU 文本原样透传(UE 的FText::Format不实现 ICU 运行时求值)。- 编译的 culture 列表包含 NativeCulture:插件自带 UI 文本使用原生语言,若不重建原生
.locres,编辑器界面文本会丢失或回退。 - 任务全部结束后,工具箱广播
LocalizationDelegates::OnLocalizationTargetDataUpdated,Localization Dashboard 的字数与状态随之刷新。
侧车还原
导入时的 Key 还原顺序:
- 与被导入文件同目录的
<文件名>.cliffmap.json - 上一级目录的
<Manifest 基础名>.cliffmap.json - 都没有 → 用
Clan to Namespace映射 +<group-path>.<entry-id>生成 Key,并记录告警
侧车中的 id 字段(canonical ID)与当前文档的 namespace.clan.group.entry 完全一致时才生效,因此可以对同一个侧车文件追加多语言、多次导出的记录而不会串味。
失败语义
| 情况 | 行为 |
|---|---|
某个 .cliff 校验失败 | 该文件失败并打印全部 issue;整批任务失败 |
目录扫描不到 .cliff | 报错「没有找到可导入的 .cliff 文件。」 |
| 缺少必填配置 | 报错并指出缺失项(如 Missing required CLIFF import settings in section '…') |
| 编译/保存失败 | 官方 FLocTextHelper 的错误原样抛出,日志含路径与原因 |
| 单条条目问题(空译文/自翻译/占位符) | 只告警并跳过该条,不中断整次导入 |
TIP
导入是幂等的:对同一份
.cliff重复导入不会产生重复条目,也不会累积重复 context。需要回滚时,用官方 Translation Editor 或重新导入旧版.cliff即可。