mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 13:54:53 +08:00
@@ -4190,6 +4190,7 @@ fn dispatch_translation_tasks_filters_current_queue() {
|
||||
let output_root = temp.path().join("output");
|
||||
let current_dir = write_catalog_fixture(&state_dir, &output_root, "bundle-b2", None);
|
||||
write_textunit_task_queue_fixture(¤t_dir);
|
||||
write_textunit_index_fixture(¤t_dir);
|
||||
write_translation_task_repository_fixture(¤t_dir);
|
||||
|
||||
let envelope = dispatch_rpc_method(
|
||||
@@ -4290,7 +4291,13 @@ fn dispatch_translation_tasks_filters_current_queue() {
|
||||
Some(serde_json::json!({
|
||||
"task_id": "textunit/v-current/Bundle/a.bundle",
|
||||
"status": "completed",
|
||||
"provider_run_id": "crowdin-run-2"
|
||||
"provider": "manual",
|
||||
"provider_run_id": "manual-run-1",
|
||||
"translation_results": [{
|
||||
"unit_id": "direct:a#unit:0",
|
||||
"source_text": "こんにちは",
|
||||
"translated_text": "你好"
|
||||
}]
|
||||
})),
|
||||
),
|
||||
&state_dir,
|
||||
@@ -4305,7 +4312,16 @@ fn dispatch_translation_tasks_filters_current_queue() {
|
||||
value["data"]["entry"]["failure_reason"],
|
||||
serde_json::Value::Null
|
||||
);
|
||||
assert_eq!(value["data"]["entry"]["provider_run_id"], "crowdin-run-2");
|
||||
assert_eq!(value["data"]["entry"]["provider_run_id"], "manual-run-1");
|
||||
assert_eq!(value["data"]["entry"]["provider"], "manual");
|
||||
assert_eq!(
|
||||
value["data"]["entry"]["translation_results"][0]["unit_id"],
|
||||
"direct:a#unit:0"
|
||||
);
|
||||
assert_eq!(
|
||||
value["data"]["entry"]["translation_results"][0]["translated_text"],
|
||||
"你好"
|
||||
);
|
||||
assert!(value["data"]["entry"]["completed_unix_seconds"].is_number());
|
||||
|
||||
let envelope = dispatch_rpc_method(
|
||||
@@ -4329,7 +4345,7 @@ fn dispatch_translation_tasks_filters_current_queue() {
|
||||
assert_eq!(value["data"]["handoff"]["units"][0]["status"], "translated");
|
||||
assert_eq!(
|
||||
value["data"]["handoff"]["provider_runs"][0]["provider_run_id"],
|
||||
"crowdin-run-2"
|
||||
"manual-run-1"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TranslationTaskResultUpdateParam {
|
||||
unit_id: String,
|
||||
source_text: String,
|
||||
translated_text: String,
|
||||
}
|
||||
|
||||
pub(super) fn build_translation_tasks_report(
|
||||
state_dir: &Path,
|
||||
query: OfficialTextUnitTaskQuery,
|
||||
@@ -170,6 +177,13 @@ pub(super) fn update_translation_task_status_report(
|
||||
.or_else(|| rpc_string_param(params, "reason"))
|
||||
.map(str::to_string);
|
||||
let provider_run_id = rpc_string_param(params, "provider_run_id").map(str::to_string);
|
||||
let provider = rpc_string_param(params, "provider").map(str::to_string);
|
||||
let result_params = translation_task_result_params(params)?;
|
||||
if !result_params.is_empty() && status != TranslationTaskStatus::Completed {
|
||||
return Err(anyhow::anyhow!(
|
||||
"translation_results 只能随 completed 状态写入"
|
||||
));
|
||||
}
|
||||
let (_, version_state) = read_daemon_resource_state(state_dir)?;
|
||||
let current = version_state
|
||||
.as_ref()
|
||||
@@ -182,6 +196,22 @@ pub(super) fn update_translation_task_status_report(
|
||||
repository_path.display()
|
||||
));
|
||||
}
|
||||
let textunit_index = if result_params.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
read_textunit_index_at(¤t.resource_root)
|
||||
.map_err(anyhow::Error::msg)?
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("当前 release 缺少 TextUnit 明细索引,无法校验人工校对结果")
|
||||
})?,
|
||||
)
|
||||
};
|
||||
let result_provider = provider.clone().unwrap_or_else(|| "manual".to_string());
|
||||
let result_timestamp = unix_seconds_now();
|
||||
let result_provider_run_id = provider_run_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("manual-{result_timestamp}"));
|
||||
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
@@ -190,10 +220,36 @@ pub(super) fn update_translation_task_status_report(
|
||||
let repository = SqliteTranslationTaskRepository::open(&repository_path)
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("{error}"))?;
|
||||
repository
|
||||
.update_status(task_id, status, failure_reason, provider_run_id)
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("{error}"))
|
||||
if let Some(index) = textunit_index.as_ref() {
|
||||
let current_task = repository
|
||||
.find(task_id)
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("{error}"))?;
|
||||
let results = build_manual_translation_results(
|
||||
¤t_task,
|
||||
index,
|
||||
&result_params,
|
||||
&result_provider,
|
||||
&result_provider_run_id,
|
||||
result_timestamp,
|
||||
)?;
|
||||
repository
|
||||
.update_status_with_results(
|
||||
task_id,
|
||||
status,
|
||||
failure_reason,
|
||||
Some(result_provider_run_id),
|
||||
Some(result_provider),
|
||||
Some(&results),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("{error}"))
|
||||
} else {
|
||||
repository
|
||||
.update_status(task_id, status, failure_reason, provider_run_id)
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("{error}"))
|
||||
}
|
||||
})?;
|
||||
Ok(serde_json::json!({
|
||||
"available": true,
|
||||
@@ -215,6 +271,75 @@ pub(super) fn textunit_query_json(query: &OfficialTextUnitQuery) -> serde_json::
|
||||
})
|
||||
}
|
||||
|
||||
fn translation_task_result_params(
|
||||
params: Option<&serde_json::Value>,
|
||||
) -> anyhow::Result<Vec<TranslationTaskResultUpdateParam>> {
|
||||
let Some(value) = params
|
||||
.and_then(|params| params.get("translation_results"))
|
||||
.or_else(|| params.and_then(|params| params.get("results")))
|
||||
else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
if value.is_null() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
serde_json::from_value(value.clone())
|
||||
.map_err(|error| anyhow::anyhow!("translation_results 必须是结果数组:{error}"))
|
||||
}
|
||||
|
||||
fn build_manual_translation_results(
|
||||
task: &bat_infrastructure::PersistedTranslationTask,
|
||||
index: &bat_infrastructure::OfficialTextUnitIndex,
|
||||
params: &[TranslationTaskResultUpdateParam],
|
||||
provider: &str,
|
||||
provider_run_id: &str,
|
||||
translated_unix_seconds: u64,
|
||||
) -> anyhow::Result<Vec<bat_infrastructure::TranslationTaskUnitResult>> {
|
||||
let index_by_id = index
|
||||
.units
|
||||
.iter()
|
||||
.map(|unit| (unit.id.as_str(), unit))
|
||||
.collect::<std::collections::BTreeMap<_, _>>();
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
let mut results = Vec::with_capacity(params.len());
|
||||
for param in params {
|
||||
let unit_id = param.unit_id.trim();
|
||||
if unit_id.is_empty() {
|
||||
return Err(anyhow::anyhow!("translation_results[].unit_id 不能为空"));
|
||||
}
|
||||
if !seen.insert(unit_id.to_string()) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"translation_results 包含重复 TextUnit:{unit_id}"
|
||||
));
|
||||
}
|
||||
let unit = index_by_id
|
||||
.get(unit_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("translation_results 引用了未知 TextUnit:{unit_id}"))?;
|
||||
if unit.destination != task.task.destination
|
||||
|| unit.archive_entry != task.task.archive_entry
|
||||
{
|
||||
return Err(anyhow::anyhow!(
|
||||
"TextUnit {unit_id} 不属于翻译任务 {}",
|
||||
task.task.task_id
|
||||
));
|
||||
}
|
||||
if param.source_text != unit.source_text {
|
||||
return Err(anyhow::anyhow!(
|
||||
"TextUnit {unit_id} 的 source_text 与当前索引不一致"
|
||||
));
|
||||
}
|
||||
results.push(bat_infrastructure::TranslationTaskUnitResult {
|
||||
unit_id: unit_id.to_string(),
|
||||
source_text: param.source_text.clone(),
|
||||
translated_text: param.translated_text.clone(),
|
||||
provider: provider.to_string(),
|
||||
provider_run_id: provider_run_id.to_string(),
|
||||
translated_unix_seconds,
|
||||
});
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub(super) fn translation_task_query_json(query: &OfficialTextUnitTaskQuery) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"task_id": query.task_id.clone(),
|
||||
|
||||
Reference in New Issue
Block a user