AI 翻译与命令let

FCliffAITranslator

struct CLIFFLOCALIZATIONSUITEEDITOR_API FCliffAITranslationStats
{
    int32 CandidateCount = 0;   // 进入请求的候选条目累计(含重跑轮次)
    int32 TranslatedCount = 0;  // 成功写回并置 translated
    int32 SkippedCount = 0;     // 已有 target 且非重译候选
    int32 FailedCount = 0;      // 无回包/空译文/占位符不符/自翻译/修复后仍非法/写盘失败
    int32 RepairCount = 0;      // 成功修复次数
    int32 BatchCount = 0;       // 批次数
    TArray<FString> FailedFiles;
};

class CLIFFLOCALIZATIONSUITEEDITOR_API FCliffAITranslator
{
public:
    static bool TranslateCliffDirectory(const FString& InCliffDirectory,
                                       const TArray<FString>& InCultures,
                                       const UCliffLocalizationSuiteSettings* InSettings,
                                       FCliffAITranslationStats& OutStats,
                                       FText& OutError,
                                       int32 InRetryDepth = 0);

    static bool TestConnection(const UCliffLocalizationSuiteSettings* InSettings,
                               FString& OutMessage, FText& OutError);

    static bool IsSelfTranslation(const FString& InSource, const FString& InTarget, const FString& InTargetLanguage);
    static bool IsRetranslationCandidate(const FString& InSource, const FString& InTarget, const FString& InStatus,
                                         const FString& InTargetLanguage, const bool bEnabled);
};

示例

const UCliffLocalizationSuiteSettings* Settings = GetDefault<UCliffLocalizationSuiteSettings>();

// 1) 连接自检
FString Message; FText Error;
if (!FCliffAITranslator::TestConnection(Settings, Message, Error))
{
    UE_LOG(LogTemp, Error, TEXT("DeepSeek: %s"), *Error.ToString());
}

// 2) 翻译一个目录下的全部 .cliff(按 culture 筛选)
FCliffAITranslationStats Stats;
if (FCliffAITranslator::TranslateCliffDirectory(
        FPaths::ProjectSavedDir() / TEXT("CliffAITranslate/Game"),
        { TEXT("ja"), TEXT("ko") }, Settings, Stats, Error))
{
    UE_LOG(LogTemp, Log, TEXT("translated %d / candidates %d, failed %d, repairs %d"),
        Stats.TranslatedCount, Stats.CandidateCount, Stats.FailedCount, Stats.RepairCount);
}

// 3) 判定辅助函数
const bool bSelf = FCliffAITranslator::IsSelfTranslation(
    TEXT("DeepSeek response has no message content."), TEXT("DeepSeek response has no message content."), TEXT("zh-Hans")); // true
const bool bCandidate = FCliffAITranslator::IsRetranslationCandidate(
    TEXT("Play"), TEXT("Play"), TEXT("translated"), TEXT("zh-Hans"), /*bEnabled=*/true); // true

行为契约

规则
候选筛选source 且无 target;或 status == "translated" 且判定为自翻译且开关开启
永不触碰reviewed / final
空译文源文属目标语言 → 本地填源文;否则拒绝写回
自翻译拒绝写回并计入失败
占位符数量相等且源文每个 {…} 都出现在译文中,否则拒绝写回
重试单批重试最多 MaxRetries 次(间隔 0.5s 递增);整轮后仍有失败则再跑(最多 MaxRetries 轮)
修复写回后 ParseAndValidate 失败且 bAutoRepairCliff → 最多 MaxRepairAttempts 轮;仍失败则不写盘并记入 FailedFiles
整体失败仅当「候选数 > 0 且译文数为 0」;部分成功只告警
并发批次按 MaxConcurrentCultures 分块并发发出后统一等待
Key 解析环境变量 DEEPSEEK_API_KEY 优先,其次设置字段;日志与配置中不出现 Key
脚本判定拉丁 / CJK / 阿拉伯 / 西里尔四个族,按目标语言前缀(zh/ja/koar/fa/he/urru/uk/bg/sr,其余视为拉丁)判定是否「已属目标语言」

GatherText 命令let

UCLASS()
class CLIFFLOCALIZATIONSUITEEDITOR_API UCliffImportCommandlet : public UGatherTextCommandletBase
{
    virtual int32 Main(const FString& Params) override;
    virtual EGatherTextCommandletPhase GetPhase() const override { return EGatherTextCommandletPhase::Import; }
};

UCLASS()
class CLIFFLOCALIZATIONSUITEEDITOR_API UCliffExportCommandlet : public UGatherTextCommandletBase
{
    virtual int32 Main(const FString& Params) override;
    virtual EGatherTextCommandletPhase GetPhase() const override { return EGatherTextCommandletPhase::Export; }
};

UCLASS()
class CLIFFLOCALIZATIONSUITEEDITOR_API UCliffAITranslateCommandlet : public UGatherTextCommandletBase
{
    virtual int32 Main(const FString& Params) override;
    virtual EGatherTextCommandletPhase GetPhase() const override { return EGatherTextCommandletPhase::Import; }
};
命令letCommandletClass必需配置键返回
UCliffImportCommandletCliffImportSourcePath / ManifestName / ArchiveName / NativeCulture,以及 CliffFilePath[]CliffDirectory0 成功,-1 失败
UCliffExportCommandletCliffExportSourcePath / DestinationPath / ManifestName / ArchiveName / NativeCulture / CliffNamespace0 / -1
UCliffAITranslateCommandletCliffAITranslateSourcePath / DestinationPath / CulturesToGenerate[]0 / -1

它们由官方执行器以 -Config="<ini>" -Section="GatherTextStep{N}" 调用;缺任一参数即报 No config or section specified. 并返回 -1。键的完整清单见命令与命令let

工具箱入口(Editor-only)

namespace FCliffEditorToolbox
{
    bool BuildImportSettings(const ULocalizationTarget*, FCliffImportSettings&, FText&);
    bool BuildExportSettings(const ULocalizationTarget*, FCliffExportSettings&, FText&);

    bool ImportCliffFile(const ULocalizationTarget*, const FString& InCliffFilePath, FCliffImportStats&, TArray<FCliffValidationIssue>&, FText&);
    bool ImportCliffFileOfficial(const ULocalizationTarget*, const FString& InCliffFilePath, FText&);
    bool ImportCliffFilesOfficial(const ULocalizationTarget*, const TArray<FString>&, FText&);

    bool ExportTargetToDirectory(const ULocalizationTarget*, const FString& InOutputDirectory, TArray<FCliffExportDocument>&, FCliffExportStats&, FText&);
    bool ExportTargetToDirectoryOfficial(const ULocalizationTarget*, const FString& InOutputDirectory, FText&);

    bool AITranslateTargetsOfficial(const ULocalizationTarget*, const TArray<FString>& InCultures, const FString& InSourceLanguage, FText&);
    bool AITranslateTargetsOfficial(const TArray<ULocalizationTarget*>&, const TArray<FString>&, const FString&, FText&);

    bool ExportTargetsToDirectoryOfficial(const TArray<ULocalizationTarget*>&, const FString&, FText&);
    bool ImportCliffDirectoryOfficialForTargets(const TArray<ULocalizationTarget*>&, const FString&, FText&);

    void ImportFromDialog(const TWeakObjectPtr<ULocalizationTarget>&);
    void ImportFromDialog(const TWeakObjectPtr<ULocalizationTargetSet>&);
    void ExportToDialog(const TWeakObjectPtr<ULocalizationTarget>&);
    void ExportToDialog(const TWeakObjectPtr<ULocalizationTargetSet>&);
    void AITranslateFromDialog(const TWeakObjectPtr<ULocalizationTarget>&);
    void AITranslateFromDialog(const TWeakObjectPtr<ULocalizationTargetSet>&);
    void NotifyTargetSetAction(const FText& InActionName);
}
命名说明
…Official 后缀走官方 GatherText 管线(生成配置、起子进程、跑报告、刷 Dashboard),GUI 与 CLI 用的就是这些
无后缀(ImportCliffFile / ExportTargetToDirectory直接调用导入器/导出器,不起子进程,适合工具与测试
*FromDialogDashboard 按钮的实际入口:弹目录或 Culture 选择窗口,然后调度上面的官方入口

CliffLocalizationServiceProvider

class FCliffLocalizationServiceProvider : public ILocalizationServiceProvider
{
    const FName& GetName() const override;              // "CliffLocalizationSuite"
    const FText GetDisplayName() const override;        // "CLIFF Localization Suite"
    FText GetStatusText() const override;               // "CLIFF import/export is ready."
    bool IsEnabled() const override;                    // true
    bool IsAvailable() const override;                  // true
    void CustomizeTargetToolbar(TSharedRef<FExtender>&, TWeakObjectPtr<ULocalizationTarget>) const override;
    void CustomizeTargetSetToolbar(TSharedRef<FExtender>&, TWeakObjectPtr<ULocalizationTargetSet>) const override;
};

模块启动时注册为 "LocalizationService" modular feature 并设为当前 Provider(否则 Dashboard 会快照默认 Provider,按钮不会出现)。

相关阅读