导入管线

导入是 CliffImport + GenerateTextLocalizationResource 两个官方步骤的组合:前者把 .cliff 变成 manifest / archive,后者把 archive 编译成 .locmeta / .locres

配置项

导入命令let 从 GatherText 配置的当前步骤读取:

必需说明
SourcePathTarget 的本地化数据目录(Content/Localization/<Target>
ManifestNameGame.manifest
ArchiveNameGame.archive
NativeCulture原生语言,如 zh-Hans
CulturesToGenerate本次要写入的 culture(可重复出现)
CliffFilePath✅*单个 .cliff 的绝对路径(可重复出现)
CliffDirectory + CliffImportCulture✅*目录批量导入:递归扫描目录内 .cliff,只挑选匹配指定 culture 的文件

* CliffFilePathCliffDirectory 二者取一。

解析与校验

每个文件先整篇读入并交给 FCliffDocument::ParseAndValidate

  • 有任何 error 级 issue(syntax / semantic / vocabulary / icu / id)→ 该文件导入失败,日志逐条打印行号与类别
  • extensionwarning 级 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 译文

写入前的两道防护:

  1. 空译文拒绝写回target 为空字符串时不写 archive。若源文本本身已属于目标语言,则在本地回填源文本(模型本应原样返回);否则视为失败。历史上遗留的空译文条目会在导入时就地恢复为源文本,避免编译出空字符串让 Slate UI 显示空白。
  2. 自翻译拒绝写回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 还原顺序:

  1. 与被导入文件同目录的 <文件名>.cliffmap.json
  2. 上一级目录的 <Manifest 基础名>.cliffmap.json
  3. 都没有 → 用 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 即可。