Write-Back Safety and Repair
Making a model generate text is easy; the hard part is not letting it destroy existing work. The plugin strings a whole guard chain along the write-back path: candidate filtering → response validation → content validation → semantic judgement → structural repair → failure isolation.
Guard 1: fill in only what is missing, never overwrite
| Entry state | Enters the translation candidates? |
|---|---|
status: initial or no target | ✅ |
status: translated, judged a self-translation (see below) | ✅ (when Retranslate Self Translations is on) |
status: translated with a normal translation | ⛔ skipped |
status: reviewed / final | ⛔ never touched |
The plugin does not offer an “overwrite existing translations” switch. To retranslate an entry, change it back to status: initial or delete its target in the .cliff — that action leaves a trace and can be reviewed.
NOTE
Only non-native cultures take part in translation; the native language is maintained by hand.
Guard 2: self-translation detection
The problem: project source code is often authored in mixed languages (English error messages + Chinese UI labels), while the CLIFF header’s source-language comes from NativeCulture. That makes “source language = target language” and the instruction self-contradictory, so the model may return the English text unchanged — that is a “self-translation”: target == source while the text does not actually belong to the target language.
Detection (FCliffAITranslator::IsSelfTranslation): target == source and the Unicode script family of the source text (Latin / CJK / Arabic / Cyrillic) does not match the target language → judged a self-translation, refused and counted as a failure.
Retranslation candidates (IsRetranslationCandidate): status is exactly translated, the entry is judged a self-translation, and Retranslate Self Translations is on → it is translated again on the next AI translation run. reviewed / final are never retranslated, even when they match the conditions.
Prompt hardening: both the system prompt and the user content state plainly that “an entry may differ from the declared source language; any entry that does not belong to the target language must be translated; an entry that already belongs to the target language is returned unchanged; apart from that case, returning the source text unchanged is forbidden”, and an extra note is appended when source == target.
Guard 3: empty translations
When the model returns "":
- The source text already belongs to the target language → fill the source text in locally (the model was supposed to return it unchanged)
- Otherwise → refuse the write-back and go through retry
The importer also refuses to write empty translations, and restores legacy empty-translation archive entries in place to the source text. The reason is that an empty translation would be compiled into an empty string, which the Slate UI renders as blank and looks like a “missed translation”.
Guard 4: response and content validation
| Check | Failure handling |
|---|---|
The response is legal JSON and entries can be taken either from a “JSON array” or from translations / results / data / items inside an object | Retry that batch (up to Max Retries times); if it still fails, warn and skip the batch |
{"translations": []} empty array | Treated as legal (nothing in that batch needs translating); no more false “response could not be parsed” |
Every item has an id that matches a candidate in this batch | That item is recorded as failed and skipped |
translation is non-empty | See “Guard 3” |
Placeholders agree: the translation has the same number of placeholders and every {…} in the source appears in the translation (nested braces supported) | Refuse that entry: Placeholder mismatch for '…'; skipped. |
| The translation is identical to the source and the source does not belong to the target language | Refuse that entry: AI returned the source unchanged for '…'; self-translation refused. |
A single failed entry does not interrupt the whole run: it is only warned about and the remaining entries are written back as usual. If failed entries remain after a round, the plugin automatically runs one more round (up to Max Retries rounds), handling only the entries still missing a translation.
Guard 5: the automatic CLIFF repair loop
AI-produced .cliff is not assumed to be legal on the first try:
① FCliffDocument::ParseAndValidate 校验
② 通过 → 进入导入
③ 失败且 Auto Repair CLIFF 开启:
把「原始 CLIFF 文本 + issue 列表 + 修复要求」发回 DeepSeek
(专用 system prompt:只修报告的问题、保持已正确内容不变、只返回 CLIFF 纯文本)
要求:只修语法/结构/占位符/必填字段;
不得改动已正确条目的 id / namespace / clan / source / 已有 target / status 语义
④ 收到修复结果后剥掉可能的 ``` 代码围栏,再次 ParseAndValidate
⑤ 最多 Max Repair Attempts 轮(默认 2)
⑥ 仍失败 → 该文件标记失败(记入 FailedFiles),**不写入、不导入**,避免污染官方数据
⑦ 修复成功后仍要过「防护三/四」的校验
Partial success and the failure criterion
| Situation | Result |
|---|---|
| Some entries fail in this round | Warn + keep writing back the successful entries; the task proceeds to the import stage as usual |
| The whole run (including all retry rounds) produced 0 translations | The commandlet errors and the task fails |
{"translations": []} empty array response | Treated as a legal result (no more false “response could not be parsed”) |
| HTTP 401 / 403 | Stop immediately and prompt to check the key |
| HTTP 429 | Retry after Retry-After or exponential backoff |
| HTTP 5xx / timeout | Retry up to Max Retries; if it still fails, skip the batch |
| No key configured | Error out directly; no request is sent |
IMPORTANT
The threshold for declaring failure is “the whole run produced 0 translations”, not “one batch failed”. This rule exists so that the
export → AI translation → importpipeline does not give up halfway because of jitter in an individual batch, which would leave already-translated content unable to reach disk.
Logging and key safety
- Logs never print the API key, the
Authorizationheader, or a full request body containing the key - Failure information contains only: target language, file path, entry id, issue category and message
- The progress window’s
Copy Log/Save Log...can archive the whole run’s log directly, which makes post-mortems easy
Isolation from official data
AI only writes .cliff; the act of writing the manifest / archive is still performed by the CliffImport step (FCliffImporter), therefore:
- AI never modifies the binary
.locresdirectly - Files whose repair failed are not imported
- At any time you can roll back by “re-importing the previous version of the
.cliff”
Related reading
- AI translation setup — model, prompt and batch parameters
- Import pipeline — the empty-translation / self-translation / effective-source rules on write-back
- Troubleshooting — common errors and how to handle them