feat(translation): add Rust Translation Memory and config migration

This commit is contained in:
2026-09-06 22:48:51 +08:00
parent 93f4bc69b3
commit 7d6389806b
18 changed files with 4414 additions and 384 deletions
+6
View File
@@ -4,6 +4,7 @@ pub mod game_client;
pub mod game_version;
pub mod resource;
pub mod translation;
pub mod translation_memory;
pub use game_client::{ClientStatus, GameClient, GameRegion};
pub use game_version::{GameVersion, UnityVersion};
@@ -14,3 +15,8 @@ pub use translation::{
ExtractedText, SourceText, TextContext, TextMetadata, TextSource, TranslatedText,
TranslationStatus,
};
pub use translation_memory::{
TranslationMemoryContext, TranslationMemoryDraft, TranslationMemoryEntry,
TranslationMemoryMatch, TranslationMemoryMatchKind, TranslationMemorySourceKind,
TranslationMemorySourceTrace, TranslationMemorySummary, TranslationMemoryTrustStatus,
};
+228
View File
@@ -0,0 +1,228 @@
//! Translation Memory 领域对象。
use std::collections::BTreeMap;
/// TM 记录的来源类型。
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TranslationMemorySourceKind {
/// 来自 provider 输出。
Provider,
/// 来自人工确认。
Manual,
/// 来自外部导入。
Imported,
}
impl TranslationMemorySourceKind {
/// 返回稳定的持久化标签。
pub const fn as_str(&self) -> &'static str {
match self {
Self::Provider => "provider",
Self::Manual => "manual",
Self::Imported => "imported",
}
}
}
/// TM 记录的可信状态。
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TranslationMemoryTrustStatus {
/// 候选记录,不能自动复用。
Candidate,
/// 已确认可信,可在强匹配时自动复用。
Trusted,
/// 已被后续记录取代。
Superseded,
/// 已明确拒绝。
Rejected,
}
impl TranslationMemoryTrustStatus {
/// 返回稳定的持久化标签。
pub const fn as_str(&self) -> &'static str {
match self {
Self::Candidate => "candidate",
Self::Trusted => "trusted",
Self::Superseded => "superseded",
Self::Rejected => "rejected",
}
}
}
/// TM 查询结果的匹配类型。
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TranslationMemoryMatchKind {
/// 原始 source 和上下文都完全匹配,且记录可信,可自动复用。
StrongExact,
/// 原始 source 完全匹配,但上下文不同或不足,不能自动复用。
CandidateExact,
/// 原始 source 匹配,但上下文不兼容,不能自动复用。
SourceOnly,
}
impl TranslationMemoryMatchKind {
/// 返回稳定的查询结果标签。
pub const fn as_str(&self) -> &'static str {
match self {
Self::StrongExact => "strong_exact",
Self::CandidateExact => "candidate_exact",
Self::SourceOnly => "source_only",
}
}
}
/// 稳定的上下文键值。
pub type TranslationMemoryContext = BTreeMap<String, String>;
/// TM 记录的 TextUnit / provider 溯源信息。
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct TranslationMemorySourceTrace {
/// 源官方 release ID。
pub official_release_id: String,
/// 来源 TextUnit ID。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub unit_id: Option<String>,
/// 来源任务 ID。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub task_id: Option<String>,
/// 源资源 destination。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub destination: Option<String>,
/// 源 ZIP/archive entry。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub archive_entry: Option<String>,
/// Unity serialized file。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub serialized_file: Option<String>,
/// Unity object path ID。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path_id: Option<i64>,
/// Unity class ID。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub class_id: Option<i32>,
/// TypeTree 字段路径。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub field_path: Option<String>,
/// TextUnit format。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
/// TextAsset 名称。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub asset_name: Option<String>,
/// TextUnit 来源类型。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text_source_kind: Option<String>,
/// 源 URL。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_url: Option<String>,
}
/// TM 记录的候选输入。
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct TranslationMemoryDraft {
/// 原始 source text。
pub source_text: String,
/// 稳定上下文。
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub source_context: TranslationMemoryContext,
/// 译文。
pub translated_text: String,
/// 译文来源类型。
pub translation_source_kind: TranslationMemorySourceKind,
/// 源官方 release。
pub official_release_id: String,
/// 原始 TextUnit / provider 溯源。
pub source_trace: TranslationMemorySourceTrace,
/// provider。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
/// provider run。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_run_id: Option<String>,
/// 创建时间。
pub observed_unix_seconds: u64,
}
/// 持久化 TM 记录。
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct TranslationMemoryEntry {
/// 稳定记录 ID。
pub record_id: String,
/// 原始 source text。
pub source_text: String,
/// source text hash。
pub source_hash: String,
/// 保守归一化后的 source text,仅用于辅助查询。
pub normalized_source_text: String,
/// 稳定上下文。
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub source_context: TranslationMemoryContext,
/// 上下文 hash。
pub source_context_hash: String,
/// 译文。
pub translated_text: String,
/// 译文来源类型。
pub translation_source_kind: TranslationMemorySourceKind,
/// 当前可信状态。
pub trust_status: TranslationMemoryTrustStatus,
/// 源官方 release。
pub official_release_id: String,
/// 原始 TextUnit / provider 溯源。
pub source_trace: TranslationMemorySourceTrace,
/// provider。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
/// provider run。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_run_id: Option<String>,
/// 创建时间。
pub created_unix_seconds: u64,
/// 更新时间。
pub updated_unix_seconds: u64,
/// 可信确认时间。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trusted_unix_seconds: Option<u64>,
/// 可信确认人。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trusted_by: Option<String>,
/// 可信确认说明。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trusted_reason: Option<String>,
/// 该记录替代了哪条记录。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub supersedes_record_id: Option<String>,
/// 该记录被哪条记录替代。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub superseded_by_record_id: Option<String>,
}
/// TM 查询结果。
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct TranslationMemoryMatch {
/// 记录本体。
pub entry: TranslationMemoryEntry,
/// 匹配类型。
pub match_kind: TranslationMemoryMatchKind,
/// 是否允许自动复用。
pub can_auto_reuse: bool,
}
/// TM 仓储摘要。
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct TranslationMemorySummary {
/// schema 版本。
pub schema_version: u32,
/// 记录总数。
pub record_count: u64,
/// 可信记录数。
pub trusted_count: u64,
/// 候选记录数。
pub candidate_count: u64,
/// 已替代记录数。
pub superseded_count: u64,
/// 已拒绝记录数。
pub rejected_count: u64,
}
+2
View File
@@ -4,8 +4,10 @@
pub mod cas_repository;
pub mod resource_repository;
pub mod translation_memory_repository;
pub mod translation_repository;
pub use cas_repository::CasRepository;
pub use resource_repository::ResourceRepository;
pub use translation_memory_repository::TranslationMemoryRepository;
pub use translation_repository::TranslationRepository;
@@ -0,0 +1,47 @@
//! Translation Memory 仓储契约。
use crate::domain::{
TranslationMemoryContext, TranslationMemoryDraft, TranslationMemoryEntry,
TranslationMemoryMatch, TranslationMemorySummary,
};
use async_trait::async_trait;
/// 跨 official release 持久化的 Translation Memory 仓储。
///
/// 该契约只描述 V1 的精确查询和明确人工确认。仓储实现不得把
/// `TranslationTaskStatus::Completed` 或 provider 成功隐式解释为 trusted。
#[async_trait]
pub trait TranslationMemoryRepository: Send + Sync {
/// 保存一条 provider/manual/imported 译文候选。
///
/// 相同 source、上下文、译文、来源 release 和来源类型的重复写入必须幂等;
/// 已 trusted 的记录不得被普通候选静默覆盖。
async fn upsert_candidate(
&self,
draft: TranslationMemoryDraft,
) -> crate::Result<TranslationMemoryEntry>;
/// 按原始 source text 和上下文查询精确匹配。
///
/// 实现可以返回 source 归一化后但原文不同的辅助候选,但这类结果不能自动复用。
async fn find_matches(
&self,
source_text: &str,
source_context: &TranslationMemoryContext,
limit: usize,
) -> crate::Result<Vec<TranslationMemoryMatch>>;
/// 显式确认一条记录为 trusted。
async fn confirm(
&self,
record_id: &str,
reviewer: &str,
reason: Option<String>,
) -> crate::Result<TranslationMemoryEntry>;
/// 按稳定记录 ID 读取一条 TM 记录。
async fn find(&self, record_id: &str) -> crate::Result<TranslationMemoryEntry>;
/// 读取数据库和记录统计。
async fn summary(&self) -> crate::Result<TranslationMemorySummary>;
}