mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 08:54:55 +08:00
feat(translation): add Rust Translation Memory and config migration
This commit is contained in:
@@ -161,6 +161,12 @@ pub struct TranslationTaskUnitResult {
|
||||
pub source_text: String,
|
||||
/// Provider-produced or human-supplied translation.
|
||||
pub translated_text: String,
|
||||
/// Result source kind.
|
||||
#[serde(default)]
|
||||
pub source_kind: TranslationTaskResultSourceKind,
|
||||
/// Trusted Translation Memory record used for this result, when applicable.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub translation_memory_record_id: Option<String>,
|
||||
/// Provider identifier.
|
||||
pub provider: String,
|
||||
/// Provider run that produced this result.
|
||||
@@ -169,6 +175,30 @@ pub struct TranslationTaskUnitResult {
|
||||
pub translated_unix_seconds: u64,
|
||||
}
|
||||
|
||||
/// Source of one persisted TextUnit translation result.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TranslationTaskResultSourceKind {
|
||||
/// Result returned by the configured provider.
|
||||
#[default]
|
||||
Provider,
|
||||
/// Result submitted through the manual task update interface.
|
||||
Manual,
|
||||
/// Result reused from a trusted Translation Memory entry.
|
||||
TranslationMemory,
|
||||
}
|
||||
|
||||
impl TranslationTaskResultSourceKind {
|
||||
/// Returns the stable JSON label.
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Provider => "provider",
|
||||
Self::Manual => "manual",
|
||||
Self::TranslationMemory => "translation_memory",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One provider execution associated with one or more translation units.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProviderRun {
|
||||
@@ -247,12 +277,18 @@ pub fn build_translation_handoff(
|
||||
Vec::new(),
|
||||
)
|
||||
});
|
||||
let unit_status = match state.0 {
|
||||
TranslationTaskStatus::Queued => TranslationUnitStatus::Queued,
|
||||
TranslationTaskStatus::Running => TranslationUnitStatus::Translating,
|
||||
TranslationTaskStatus::Failed => TranslationUnitStatus::Failed,
|
||||
TranslationTaskStatus::Completed => TranslationUnitStatus::Translated,
|
||||
TranslationTaskStatus::Skipped => TranslationUnitStatus::Skipped,
|
||||
let unit_status = if state.5.is_empty() {
|
||||
match state.0 {
|
||||
TranslationTaskStatus::Queued => TranslationUnitStatus::Queued,
|
||||
TranslationTaskStatus::Running => TranslationUnitStatus::Translating,
|
||||
TranslationTaskStatus::Failed => TranslationUnitStatus::Failed,
|
||||
TranslationTaskStatus::Completed => TranslationUnitStatus::Translated,
|
||||
TranslationTaskStatus::Skipped => TranslationUnitStatus::Skipped,
|
||||
}
|
||||
} else {
|
||||
// A task can retain successful TM hits while the remaining provider
|
||||
// units are failed or waiting for retry.
|
||||
TranslationUnitStatus::Translated
|
||||
};
|
||||
let unit = TranslationUnit {
|
||||
unit_id: task.task_id.clone(),
|
||||
@@ -1168,11 +1204,29 @@ impl SqliteTranslationTaskRepository {
|
||||
pub async fn fail_claim(
|
||||
&self,
|
||||
failure: TranslationTaskFailure,
|
||||
) -> Result<PersistedTranslationTask> {
|
||||
self.fail_claim_with_results(failure, &[]).await
|
||||
}
|
||||
|
||||
/// Records a provider failure while retaining any already-resolved TextUnit
|
||||
/// results, such as trusted Translation Memory hits.
|
||||
pub async fn fail_claim_with_results(
|
||||
&self,
|
||||
failure: TranslationTaskFailure,
|
||||
translation_results: &[TranslationTaskUnitResult],
|
||||
) -> Result<PersistedTranslationTask> {
|
||||
let now = unix_seconds_now_i64();
|
||||
let next_attempt = failure
|
||||
.next_attempt_unix_seconds
|
||||
.map(|value| i64::try_from(value).unwrap_or(i64::MAX));
|
||||
let translation_results_json = if translation_results.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
serde_json::to_string(translation_results)
|
||||
.map_err(|error| bat_core::Error::Serialization(error.to_string()))?,
|
||||
)
|
||||
};
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
UPDATE translation_tasks
|
||||
@@ -1183,11 +1237,12 @@ impl SqliteTranslationTaskRepository {
|
||||
lease_expires_unix_seconds = NULL,
|
||||
failure_class = ?4,
|
||||
failure_retryable = ?5,
|
||||
next_attempt_unix_seconds = ?6
|
||||
next_attempt_unix_seconds = ?6,
|
||||
translation_results_json = COALESCE(?7, translation_results_json)
|
||||
WHERE task_id = ?1
|
||||
AND worker_status = 'running'
|
||||
AND lease_owner = ?7
|
||||
AND provider_run_id = ?8
|
||||
AND lease_owner = ?8
|
||||
AND provider_run_id = ?9
|
||||
"#,
|
||||
)
|
||||
.bind(&failure.task_id)
|
||||
@@ -1196,6 +1251,7 @@ impl SqliteTranslationTaskRepository {
|
||||
.bind(&failure.failure_class)
|
||||
.bind(if failure.retryable { 1_i64 } else { 0_i64 })
|
||||
.bind(next_attempt)
|
||||
.bind(translation_results_json)
|
||||
.bind(&failure.worker_id)
|
||||
.bind(&failure.provider_run_id)
|
||||
.execute(&self.pool)
|
||||
@@ -1747,6 +1803,8 @@ mod tests {
|
||||
unit_id: "unit-a".to_string(),
|
||||
source_text: "source".to_string(),
|
||||
translated_text: "manual translation".to_string(),
|
||||
source_kind: TranslationTaskResultSourceKind::Manual,
|
||||
translation_memory_record_id: None,
|
||||
provider: "manual".to_string(),
|
||||
provider_run_id: "manual-run-1".to_string(),
|
||||
translated_unix_seconds: 321,
|
||||
@@ -1864,6 +1922,8 @@ mod tests {
|
||||
unit_id: "unit-a".to_string(),
|
||||
source_text: "source".to_string(),
|
||||
translated_text: "translated".to_string(),
|
||||
source_kind: TranslationTaskResultSourceKind::Provider,
|
||||
translation_memory_record_id: None,
|
||||
provider: "mock".to_string(),
|
||||
provider_run_id: second_run.clone(),
|
||||
translated_unix_seconds: 1,
|
||||
|
||||
Reference in New Issue
Block a user