mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 12:45:17 +08:00
feat(translation): add Rust Translation Memory and config migration
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
use super::report_output::print_json_value;
|
||||
use super::*;
|
||||
use bat_core::domain::TranslationMemoryContext;
|
||||
use bat_core::repositories::TranslationMemoryRepository;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TranslationTaskResultUpdateParam {
|
||||
@@ -332,6 +336,8 @@ fn build_manual_translation_results(
|
||||
unit_id: unit_id.to_string(),
|
||||
source_text: param.source_text.clone(),
|
||||
translated_text: param.translated_text.clone(),
|
||||
source_kind: bat_infrastructure::TranslationTaskResultSourceKind::Manual,
|
||||
translation_memory_record_id: None,
|
||||
provider: provider.to_string(),
|
||||
provider_run_id: provider_run_id.to_string(),
|
||||
translated_unix_seconds,
|
||||
@@ -355,3 +361,241 @@ pub(super) fn translation_task_query_json(query: &OfficialTextUnitTaskQuery) ->
|
||||
"has_failure_reason": query.has_failure_reason,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn run_translation_memory_command(options: &CliOptions) -> anyhow::Result<()> {
|
||||
let method = match options.command {
|
||||
CliCommand::TranslationMemorySummary => RPC_METHOD_TRANSLATION_MEMORY_SUMMARY,
|
||||
CliCommand::TranslationMemoryQuery => RPC_METHOD_TRANSLATION_MEMORY_QUERY,
|
||||
CliCommand::TranslationMemoryConfirm => RPC_METHOD_TRANSLATION_MEMORY_CONFIRM,
|
||||
_ => return Err(anyhow::anyhow!("不是 Translation Memory 命令")),
|
||||
};
|
||||
if daemon_rpc_available(&options.state_dir)
|
||||
&& options.resource_root.is_none()
|
||||
&& !options.output_explicit
|
||||
{
|
||||
let _control_lock = DaemonControlLock::acquire(&options.state_dir)?;
|
||||
let report = daemon_rpc_call(
|
||||
&options.state_dir,
|
||||
method,
|
||||
translation_memory_cli_params(options)?,
|
||||
)?;
|
||||
print_json_value(options.output_format, &report)?;
|
||||
return Ok(());
|
||||
}
|
||||
let path = translation_memory_cli_path(options)?;
|
||||
let report = match options.command {
|
||||
CliCommand::TranslationMemorySummary => build_translation_memory_summary_report(&path)?,
|
||||
CliCommand::TranslationMemoryQuery => {
|
||||
let source_text = options
|
||||
.translation_memory_source_text
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("TM query 必须指定 --tm-source-text"))?;
|
||||
let context = parse_translation_memory_context(
|
||||
options.translation_memory_context_json.as_deref(),
|
||||
)?;
|
||||
build_translation_memory_query_report(
|
||||
&path,
|
||||
source_text,
|
||||
&context,
|
||||
options.query_limit,
|
||||
)?
|
||||
}
|
||||
CliCommand::TranslationMemoryConfirm => {
|
||||
let record_id = options
|
||||
.translation_memory_record_id
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("TM confirm 必须指定 --tm-record-id"))?;
|
||||
let reviewer = options
|
||||
.translation_memory_reviewer
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("TM confirm 必须指定 --tm-reviewer"))?;
|
||||
build_translation_memory_confirm_report(
|
||||
&path,
|
||||
record_id,
|
||||
reviewer,
|
||||
options.translation_memory_reason.clone(),
|
||||
)?
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
print_json_value(options.output_format, &report)
|
||||
}
|
||||
|
||||
fn translation_memory_cli_path(options: &CliOptions) -> anyhow::Result<std::path::PathBuf> {
|
||||
if let Some(path) = options.translation_memory_path.as_ref() {
|
||||
return lexical_absolute(path).map_err(anyhow::Error::msg);
|
||||
}
|
||||
let resource_root = options
|
||||
.resource_root
|
||||
.as_deref()
|
||||
.map(lexical_absolute)
|
||||
.transpose()
|
||||
.map_err(anyhow::Error::msg)?
|
||||
.unwrap_or(active_official_resource_root(&options.config.output_root)?);
|
||||
Ok(bat_infrastructure::translation_memory_repository_path(
|
||||
&resource_root,
|
||||
))
|
||||
}
|
||||
|
||||
fn translation_memory_cli_params(
|
||||
options: &CliOptions,
|
||||
) -> anyhow::Result<Option<serde_json::Value>> {
|
||||
let mut params = serde_json::Map::new();
|
||||
if let Some(path) = options.translation_memory_path.as_ref() {
|
||||
params.insert(
|
||||
"translation_memory_path".to_string(),
|
||||
serde_json::json!(path),
|
||||
);
|
||||
}
|
||||
match options.command {
|
||||
CliCommand::TranslationMemorySummary => {}
|
||||
CliCommand::TranslationMemoryQuery => {
|
||||
let source_text = options
|
||||
.translation_memory_source_text
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("TM query 必须指定 --tm-source-text"))?;
|
||||
let context = parse_translation_memory_context(
|
||||
options.translation_memory_context_json.as_deref(),
|
||||
)?;
|
||||
params.insert("source_text".to_string(), serde_json::json!(source_text));
|
||||
params.insert("source_context".to_string(), serde_json::json!(context));
|
||||
params.insert("limit".to_string(), serde_json::json!(options.query_limit));
|
||||
}
|
||||
CliCommand::TranslationMemoryConfirm => {
|
||||
let record_id = options
|
||||
.translation_memory_record_id
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("TM confirm 必须指定 --tm-record-id"))?;
|
||||
let reviewer = options
|
||||
.translation_memory_reviewer
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("TM confirm 必须指定 --tm-reviewer"))?;
|
||||
params.insert("record_id".to_string(), serde_json::json!(record_id));
|
||||
params.insert("reviewer".to_string(), serde_json::json!(reviewer));
|
||||
if let Some(reason) = options.translation_memory_reason.as_deref() {
|
||||
params.insert("reason".to_string(), serde_json::json!(reason));
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
Ok(Some(serde_json::Value::Object(params)))
|
||||
}
|
||||
|
||||
pub(super) fn build_translation_memory_summary_report(
|
||||
path: &std::path::Path,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
if !sqlite_file_exists_no_symlink(path, "Translation Memory 数据库")? {
|
||||
return Ok(serde_json::json!({
|
||||
"available": false,
|
||||
"path": path,
|
||||
"reason": "database_missing",
|
||||
}));
|
||||
}
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
let summary = runtime.block_on(async {
|
||||
let repository = bat_infrastructure::SqliteTranslationMemoryRepository::open(path)
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("{error}"))?;
|
||||
repository
|
||||
.summary()
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("{error}"))
|
||||
})?;
|
||||
Ok(serde_json::json!({
|
||||
"available": true,
|
||||
"path": path,
|
||||
"schema_version": summary.schema_version,
|
||||
"summary": summary,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) fn build_translation_memory_query_report(
|
||||
path: &std::path::Path,
|
||||
source_text: &str,
|
||||
source_context: &TranslationMemoryContext,
|
||||
limit: usize,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
if source_text.trim().is_empty() {
|
||||
return Err(anyhow::anyhow!("TM query 的 source_text 不能为空"));
|
||||
}
|
||||
if !(1..=1000).contains(&limit) {
|
||||
return Err(anyhow::anyhow!("TM query 的 limit 必须在 1..=1000 范围内"));
|
||||
}
|
||||
if !sqlite_file_exists_no_symlink(path, "Translation Memory 数据库")? {
|
||||
return Ok(serde_json::json!({
|
||||
"available": false,
|
||||
"path": path,
|
||||
"source_text": source_text,
|
||||
"source_context": source_context,
|
||||
"matches": [],
|
||||
"reason": "database_missing",
|
||||
}));
|
||||
}
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
let matches = runtime.block_on(async {
|
||||
let repository = bat_infrastructure::SqliteTranslationMemoryRepository::open(path)
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("{error}"))?;
|
||||
repository
|
||||
.find_matches(source_text, source_context, limit)
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("{error}"))
|
||||
})?;
|
||||
Ok(serde_json::json!({
|
||||
"available": true,
|
||||
"path": path,
|
||||
"source_text": source_text,
|
||||
"source_context": source_context,
|
||||
"matches": matches,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) fn build_translation_memory_confirm_report(
|
||||
path: &std::path::Path,
|
||||
record_id: &str,
|
||||
reviewer: &str,
|
||||
reason: Option<String>,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
if record_id.trim().is_empty() || reviewer.trim().is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"TM confirm 必须指定非空 record_id 和 reviewer"
|
||||
));
|
||||
}
|
||||
if !sqlite_file_exists_no_symlink(path, "Translation Memory 数据库")? {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Translation Memory 数据库不存在:{}",
|
||||
path.display()
|
||||
));
|
||||
}
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
let entry = runtime.block_on(async {
|
||||
let repository = bat_infrastructure::SqliteTranslationMemoryRepository::open(path)
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("{error}"))?;
|
||||
repository
|
||||
.confirm(record_id, reviewer, reason)
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("{error}"))
|
||||
})?;
|
||||
Ok(serde_json::json!({
|
||||
"available": true,
|
||||
"path": path,
|
||||
"entry": entry,
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_translation_memory_context(
|
||||
value: Option<&str>,
|
||||
) -> anyhow::Result<TranslationMemoryContext> {
|
||||
let Some(value) = value else {
|
||||
return Ok(BTreeMap::new());
|
||||
};
|
||||
serde_json::from_str::<TranslationMemoryContext>(value)
|
||||
.map_err(|error| anyhow::anyhow!("--tm-context-json 必须是 JSON object:{error}"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user