feat(glossary): 实现 Rust Glossary V1

This commit is contained in:
2026-09-07 22:38:53 +08:00
parent 8fc93b8f39
commit 94483ff14d
42 changed files with 4543 additions and 92 deletions
@@ -1,6 +1,6 @@
use super::report_output::print_json_value;
use super::*;
use bat_core::domain::TranslationMemoryContext;
use bat_core::domain::{GlossaryOverride, TranslationMemoryContext};
use bat_core::repositories::TranslationMemoryRepository;
use std::collections::BTreeMap;
@@ -9,6 +9,8 @@ struct TranslationTaskResultUpdateParam {
unit_id: String,
source_text: String,
translated_text: String,
#[serde(default)]
glossary_override: Option<GlossaryOverride>,
}
pub(super) fn build_translation_tasks_report(
@@ -170,6 +172,7 @@ pub(super) fn build_translation_handoff_report(
pub(super) fn update_translation_task_status_report(
state_dir: &Path,
params: Option<&serde_json::Value>,
configured_glossary_path: Option<&Path>,
) -> anyhow::Result<serde_json::Value> {
let task_id = rpc_string_param(params, "task_id")
.ok_or_else(|| anyhow::anyhow!("translation.task.update 缺少 task_id"))?;
@@ -229,6 +232,22 @@ pub(super) fn update_translation_task_status_report(
.find(task_id)
.await
.map_err(|error| anyhow::anyhow!("{error}"))?;
let glossary_path = configured_glossary_path
.map(PathBuf::from)
.unwrap_or_else(|| {
bat_infrastructure::SqliteGlossaryRepository::repository_path(
&current.resource_root,
)
});
let glossary = if std::fs::symlink_metadata(&glossary_path).is_ok() {
Some(
bat_infrastructure::SqliteGlossaryRepository::open(&glossary_path)
.await
.map_err(|error| anyhow::anyhow!("打开 Glossary 数据库失败:{error}"))?,
)
} else {
None
};
let results = build_manual_translation_results(
&current_task,
index,
@@ -236,7 +255,9 @@ pub(super) fn update_translation_task_status_report(
&result_provider,
&result_provider_run_id,
result_timestamp,
)?;
glossary.as_ref(),
)
.await?;
repository
.update_status_with_results(
task_id,
@@ -291,13 +312,14 @@ fn translation_task_result_params(
.map_err(|error| anyhow::anyhow!("translation_results 必须是结果数组:{error}"))
}
fn build_manual_translation_results(
async 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,
glossary: Option<&bat_infrastructure::SqliteGlossaryRepository>,
) -> anyhow::Result<Vec<bat_infrastructure::TranslationTaskUnitResult>> {
let index_by_id = index
.units
@@ -332,6 +354,53 @@ fn build_manual_translation_results(
"TextUnit {unit_id} 的 source_text 与当前索引不一致"
));
}
let glossary_qa = if let Some(glossary) = glossary {
let context = bat_infrastructure::translation_memory_context(
&unit.destination,
unit.archive_entry.as_deref(),
unit.serialized_file.as_deref(),
unit.path_id,
unit.class_id,
unit.field_path.as_deref(),
unit.format.as_deref(),
unit.asset_name.as_deref(),
unit.text_source_kind.as_deref(),
&unit.context,
);
Some(
glossary
.diagnose(&unit.source_text, &context)
.await
.map_err(|error| anyhow::anyhow!("Glossary QA 失败:{error}"))?
.check_translation(&param.translated_text),
)
} else {
None
};
if let Some(qa) = glossary_qa.as_ref().filter(|qa| qa.status.is_blocked()) {
let Some(override_record) = param.glossary_override.as_ref() else {
return Err(anyhow::anyhow!(
"TextUnit {} 的 Glossary QA blocked;必须提供 glossary_override",
unit_id
));
};
if override_record.reviewer.trim().is_empty()
|| override_record.reason.trim().is_empty()
|| override_record.provenance.trim().is_empty()
|| override_record.confirmed_unix_seconds == 0
{
return Err(anyhow::anyhow!(
"TextUnit {} 的 glossary_override 不完整或 confirmed_unix_seconds 无效",
unit_id
));
}
let _ = qa;
} else if param.glossary_override.is_some() {
return Err(anyhow::anyhow!(
"TextUnit {} 不能为非 blocking Glossary QA 指定 override",
unit_id
));
}
results.push(bat_infrastructure::TranslationTaskUnitResult {
unit_id: unit_id.to_string(),
source_text: param.source_text.clone(),
@@ -341,6 +410,8 @@ fn build_manual_translation_results(
provider: provider.to_string(),
provider_run_id: provider_run_id.to_string(),
translated_unix_seconds,
glossary_qa,
glossary_override: param.glossary_override.clone(),
});
}
Ok(results)