mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 06:34:54 +08:00
@@ -26,9 +26,11 @@ use bat_infrastructure::{
|
||||
SqliteResourceRepository, SqliteTranslationTaskRepository, TranslationTaskStatus,
|
||||
UnityFsFieldPatchParams, UnityFsPatchReport, UnityFsStringFieldPatchParams,
|
||||
UnityFsTextAssetPatchParams, CROWDIN_TEXTUNIT_QUEUE_FILE, LOCALIZED_CURRENT_LINK,
|
||||
LOCALIZED_PATCH_MANIFEST_FILE, LOCALIZED_VERSIONS_DIR, LOCALIZED_VERSION_STATE_FILE,
|
||||
MAX_DOWNLOAD_CONCURRENCY, MIN_DOWNLOAD_CONCURRENCY, OFFICIAL_PARSE_CACHE_FILE,
|
||||
OFFICIAL_TEXTUNIT_INDEX_FILE, OFFICIAL_TEXTUNIT_TASK_QUEUE_FILE, PRIVATE_FILE_MODE,
|
||||
LOCALIZED_PATCH_MANIFEST_FILE, LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING,
|
||||
LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING_LABEL, LOCALIZED_VERSIONS_DIR,
|
||||
LOCALIZED_VERSION_STATE_FILE, MAX_DOWNLOAD_CONCURRENCY, MIN_DOWNLOAD_CONCURRENCY,
|
||||
OFFICIAL_PARSE_CACHE_FILE, OFFICIAL_TEXTUNIT_INDEX_FILE, OFFICIAL_TEXTUNIT_TASK_QUEUE_FILE,
|
||||
PRIVATE_FILE_MODE,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -81,8 +83,8 @@ use translation_query::{
|
||||
};
|
||||
use workflow_commands::{
|
||||
run_parse_clear_cache, run_parse_once, run_publish_localized, run_repack, run_translate_once,
|
||||
run_translation_get, run_translation_set, run_translation_task_update, run_translation_unset,
|
||||
run_translation_validate,
|
||||
run_translation_get, run_translation_proofread, run_translation_set,
|
||||
run_translation_task_update, run_translation_unset, run_translation_validate,
|
||||
};
|
||||
|
||||
const EXIT_ERROR: i32 = 1;
|
||||
@@ -223,6 +225,10 @@ fn run() -> anyhow::Result<i32> {
|
||||
run_translation_task_update(&options)?;
|
||||
Ok(0)
|
||||
}
|
||||
CliCommand::TranslationProofread => {
|
||||
run_translation_proofread(&options)?;
|
||||
Ok(0)
|
||||
}
|
||||
CliCommand::Repack => {
|
||||
run_repack(&options)?;
|
||||
Ok(0)
|
||||
@@ -540,6 +546,7 @@ enum CliCommand {
|
||||
TranslationGet,
|
||||
TranslationUnset,
|
||||
TranslationTaskUpdate,
|
||||
TranslationProofread,
|
||||
Repack,
|
||||
PublishLocalized,
|
||||
ScheduleList,
|
||||
@@ -1017,6 +1024,7 @@ const RPC_METHOD_PARSE_ERRORS: &str = "parse.errors";
|
||||
const RPC_METHOD_TRANSLATION_TASKS: &str = "translation.tasks";
|
||||
const RPC_METHOD_TRANSLATION_HANDOFF: &str = "translation.handoff";
|
||||
const RPC_METHOD_TRANSLATION_TASK_UPDATE: &str = "translation.task.update";
|
||||
const RPC_METHOD_TRANSLATION_PROOFREAD: &str = "translation.proofread";
|
||||
const RPC_METHOD_LOCALIZED_STATUS: &str = "localized.status";
|
||||
const RPC_METHOD_CATALOG_STATUS: &str = "catalog.status";
|
||||
const RPC_METHOD_CATALOG_VERSIONS: &str = "catalog.versions";
|
||||
@@ -2013,6 +2021,12 @@ fn dispatch_rpc_method(
|
||||
"translation.task.update",
|
||||
update_translation_task_status_report(state_dir, request.params.as_ref()),
|
||||
),
|
||||
RPC_METHOD_TRANSLATION_PROOFREAD => rpc_envelope_from_result(
|
||||
request_id,
|
||||
"translation.proofread",
|
||||
mark_localized_manual_proofreading_report(state_dir, &tasks.base_config)
|
||||
.and_then(|report| serde_json::to_value(report).map_err(anyhow::Error::from)),
|
||||
),
|
||||
RPC_METHOD_LOCALIZED_STATUS => rpc_envelope_from_result(
|
||||
request_id,
|
||||
"localized.status",
|
||||
@@ -2836,6 +2850,22 @@ fn build_parse_errors_report(
|
||||
}))
|
||||
}
|
||||
|
||||
fn mark_localized_manual_proofreading_report(
|
||||
state_dir: &Path,
|
||||
base_config: &OfficialUpdateConfig,
|
||||
) -> anyhow::Result<bat_infrastructure::LocalizedTranslationWorkflowReport> {
|
||||
let (_, version_state) = read_daemon_resource_state(state_dir)?;
|
||||
let official_release_id = version_state
|
||||
.as_ref()
|
||||
.and_then(|state| state.current_completed_version.as_ref())
|
||||
.map(|record| record.id.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("没有可标记人工校对状态的当前官方 release"))?;
|
||||
bat_infrastructure::mark_localized_manual_proofreading(
|
||||
&base_config.localized_output_root,
|
||||
official_release_id,
|
||||
)
|
||||
}
|
||||
|
||||
/// `localized.status`:当前官方版本对应的汉化 release 状态。
|
||||
fn build_localized_status_report(
|
||||
state_dir: &Path,
|
||||
@@ -2854,6 +2884,12 @@ fn build_localized_status_report(
|
||||
let current_path = localized_root.join(LOCALIZED_CURRENT_LINK);
|
||||
let state = read_localized_version_state(&localized_root)?;
|
||||
let mut localized_release_status = "not_localized";
|
||||
let mut translation_workflow_status = None;
|
||||
let mut translation_workflow_status_code = None;
|
||||
let mut translation_workflow_status_phase = None;
|
||||
let mut translation_workflow_status_terminal = None;
|
||||
let mut translation_workflow_status_retryable = None;
|
||||
let mut translation_workflow_label = None;
|
||||
let mut published_version_path = None;
|
||||
let mut matches_current_official_release = false;
|
||||
let mut current_points_to_published_version = false;
|
||||
@@ -2873,6 +2909,23 @@ fn build_localized_status_report(
|
||||
matches_current_official_release = official_version_id
|
||||
.as_deref()
|
||||
.is_some_and(|id| localized_state.official_release_id == id);
|
||||
if matches_current_official_release
|
||||
&& localized_state.translation_workflow_status()
|
||||
== Some(LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING)
|
||||
{
|
||||
let (status, code, phase, terminal, retryable) =
|
||||
flow_status_fields(ReleaseFlowStatusCode::TranslationManualProofreading);
|
||||
translation_workflow_status = Some(status);
|
||||
translation_workflow_status_code = Some(code);
|
||||
translation_workflow_status_phase = Some(phase);
|
||||
translation_workflow_status_terminal = Some(terminal);
|
||||
translation_workflow_status_retryable = Some(retryable);
|
||||
translation_workflow_label = Some(
|
||||
localized_state
|
||||
.translation_workflow_label()
|
||||
.unwrap_or(LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING_LABEL),
|
||||
);
|
||||
}
|
||||
if official_version_id.is_some() && !matches_current_official_release {
|
||||
flow_status_code = ReleaseFlowStatusCode::LocalizedStale;
|
||||
}
|
||||
@@ -2915,6 +2968,12 @@ fn build_localized_status_report(
|
||||
"status_terminal": status_terminal,
|
||||
"status_retryable": status_retryable,
|
||||
"localized_release_status": localized_release_status,
|
||||
"translation_workflow_status": translation_workflow_status,
|
||||
"translation_workflow_status_code": translation_workflow_status_code,
|
||||
"translation_workflow_status_phase": translation_workflow_status_phase,
|
||||
"translation_workflow_status_terminal": translation_workflow_status_terminal,
|
||||
"translation_workflow_status_retryable": translation_workflow_status_retryable,
|
||||
"translation_workflow_label": translation_workflow_label,
|
||||
"official_current_version_id": official_version_id,
|
||||
"localized_output_root": localized_root,
|
||||
"state_path": state_path,
|
||||
@@ -4144,6 +4203,24 @@ impl HumanReport for LocalizedPatchReport {
|
||||
}
|
||||
}
|
||||
|
||||
impl HumanReport for bat_infrastructure::LocalizedTranslationWorkflowReport {
|
||||
fn print_human(&self) -> anyhow::Result<()> {
|
||||
print_title("汉化工作流状态");
|
||||
print_field("命令", self.command);
|
||||
print_field("状态", self.status);
|
||||
print_field("官方 release", &self.official_release_id);
|
||||
print_optional_field("汉化 release", self.current_release_id.as_deref());
|
||||
print_field("汉化发布状态", &self.localized_release_status);
|
||||
print_field("工作流状态", &self.translation_workflow_status);
|
||||
print_field("工作流状态码", self.translation_workflow_status_code);
|
||||
print_field("工作流标签", self.translation_workflow_label);
|
||||
print_field("允许发布", format_bool(self.publish_allowed));
|
||||
print_path_field("汉化输出目录", &self.localized_output_root);
|
||||
print_path_field("状态文件", &self.state_path);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn print_human_json_value(value: &serde_json::Value) -> anyhow::Result<()> {
|
||||
if value.get("running").is_some() && value.get("state_dir").is_some() {
|
||||
print_title("后台状态");
|
||||
@@ -7329,6 +7406,39 @@ fn parse_args_with_env(
|
||||
options.progress = false;
|
||||
options.banner = false;
|
||||
}
|
||||
CliCommand::TranslationProofread => {
|
||||
if options.watch || options.daemon || options.daemon_child {
|
||||
return Err(anyhow::anyhow!("i18n proofread 只支持单次执行或 RPC 调用"));
|
||||
}
|
||||
if options.config.force
|
||||
|| options.config.dry_run
|
||||
|| options.run_count.is_some()
|
||||
|| options.sync_option_explicit
|
||||
|| options.proxy_option_explicit
|
||||
|| tools_are_non_default(&options.config, &options.env_baseline_config)
|
||||
{
|
||||
return Err(anyhow::anyhow!(
|
||||
"i18n proofread 只接受 --output、--localized-output、--resource-root、--state-dir 和 --json/--human"
|
||||
));
|
||||
}
|
||||
if options.translation_file.is_some()
|
||||
|| options.translation_id.is_some()
|
||||
|| options.translation_text.is_some()
|
||||
|| options.translation_text_file.is_some()
|
||||
|| options.translation_failure_reason.is_some()
|
||||
|| options.translation_provider_run_id.is_some()
|
||||
|| options.localized_release_id.is_some()
|
||||
|| options.repack_spec.is_some()
|
||||
|| options.query_option_explicit
|
||||
|| options.schedule_option_explicit
|
||||
{
|
||||
return Err(anyhow::anyhow!(
|
||||
"i18n proofread 不接受工作台、任务、查询、调度或发布 release 参数"
|
||||
));
|
||||
}
|
||||
options.progress = false;
|
||||
options.banner = false;
|
||||
}
|
||||
CliCommand::ScheduleList
|
||||
| CliCommand::ScheduleAdd
|
||||
| CliCommand::ScheduleUpdate
|
||||
@@ -7530,6 +7640,7 @@ fn parse_translation_command(
|
||||
"unset" | "clear" => CliCommand::TranslationUnset,
|
||||
"validate" => CliCommand::TranslationValidate,
|
||||
"publish" => CliCommand::PublishLocalized,
|
||||
"proofread" => CliCommand::TranslationProofread,
|
||||
"tasks" => CliCommand::TranslationTasks,
|
||||
"handoff" => CliCommand::TranslationHandoff,
|
||||
"status" => CliCommand::LocalizedStatus,
|
||||
@@ -7688,12 +7799,19 @@ fn print_usage(binary: &str) {
|
||||
eprintln!(" parse run Parse current official release");
|
||||
eprintln!(" parse clear-cache Clear regenerable parse and translation queue files");
|
||||
eprintln!(" parse repack Repack a UnityFS bundle from a JSON spec");
|
||||
eprintln!(" parse schedule Manage parse schedules");
|
||||
eprintln!(" i18n run Refresh offline translation work");
|
||||
eprintln!(" i18n export Export an editable translation workbench");
|
||||
eprintln!(" i18n set Update one translation workbench entry");
|
||||
eprintln!(" i18n get Show one translation workbench entry");
|
||||
eprintln!(" i18n unset Clear one translated workbench entry");
|
||||
eprintln!(" i18n validate Validate workbench against the current official release");
|
||||
eprintln!(" i18n proofread Mark localized workflow as manual proofreading");
|
||||
eprintln!(
|
||||
" i18n tasks / i18n task list / i18n task status Query current offline TextUnit translation task status"
|
||||
);
|
||||
eprintln!(" i18n handoff Query current translation handoff");
|
||||
eprintln!(" i18n status Show localized release status for current official release");
|
||||
eprintln!(" i18n task update Update one provider worker task status");
|
||||
eprintln!(" i18n publish Publish a localized release");
|
||||
eprintln!(" i18n schedule Manage translation schedules");
|
||||
@@ -7727,6 +7845,7 @@ fn print_usage(binary: &str) {
|
||||
eprintln!(" {binary} --auto-discover --daemon");
|
||||
eprintln!(" {binary} res pull --auto-discover --run-count 3 --interval 1h");
|
||||
eprintln!(" {binary} parse run --force --resource-root /tmp/bat-release");
|
||||
eprintln!(" {binary} parse schedule list --state-dir /tmp/bat-schedule");
|
||||
eprintln!(" {binary} i18n export --translation-file /tmp/bat-workbench.json");
|
||||
eprintln!(
|
||||
" {binary} i18n get --translation-file /tmp/bat-workbench.json --translation-id unit-1"
|
||||
@@ -7734,6 +7853,10 @@ fn print_usage(binary: &str) {
|
||||
eprintln!(
|
||||
" {binary} i18n unset --translation-file /tmp/bat-workbench.json --translation-id unit-1"
|
||||
);
|
||||
eprintln!(" {binary} i18n proofread --json");
|
||||
eprintln!(" {binary} i18n tasks --json");
|
||||
eprintln!(" {binary} i18n handoff --json");
|
||||
eprintln!(" {binary} i18n status --json");
|
||||
eprintln!(" {binary} i18n publish --translation-file /tmp/bat-workbench.json --force");
|
||||
eprintln!(" {binary} status");
|
||||
eprintln!(" {binary} refresh --force --json");
|
||||
@@ -7779,7 +7902,7 @@ fn print_usage(binary: &str) {
|
||||
eprintln!(" --run-count <N> Run pull/parse/translate/publish N times");
|
||||
eprintln!(" --once Explicitly select one run");
|
||||
eprintln!(" --resource-root <DIR> Use an explicit published official release root");
|
||||
eprintln!(" --translation-file <PATH> Translation workbench JSON file");
|
||||
eprintln!(" --translation-file <PATH> / --workbench <PATH> Translation workbench JSON file");
|
||||
eprintln!(" --translation-id <ID> TextUnit ID for i18n set");
|
||||
eprintln!(" --translated-text <TEXT> Inline translation for i18n set");
|
||||
eprintln!(" --translated-file <PATH> UTF-8 translation file for i18n set");
|
||||
@@ -7847,8 +7970,10 @@ fn print_usage(binary: &str) {
|
||||
eprintln!(" --quiet-up-to-date Suppress clean up-to-date reports");
|
||||
eprintln!(" --no-quiet-up-to-date Always print reports");
|
||||
eprintln!(" --tail <N> Log lines for logs command (default: 200)");
|
||||
eprintln!(" --schedule-id <ID> Schedule identifier");
|
||||
eprintln!(" --schedule-action <ACTION> Schedule action (pull/run/repack/publish)");
|
||||
eprintln!(" --schedule-id <ID> / --id <ID> Schedule identifier");
|
||||
eprintln!(
|
||||
" --schedule-action <ACTION> / --action <ACTION> Schedule action (pull/run/repack/publish)"
|
||||
);
|
||||
eprintln!(" --schedule-at-unix <SECONDS> First execution time");
|
||||
eprintln!(" --schedule-delay <DURATION> Delay first execution from now");
|
||||
eprintln!(" --schedule-every <DURATION> Period between executions");
|
||||
|
||||
@@ -254,6 +254,34 @@ fn grouped_workflow_commands_use_short_top_level_aliases() {
|
||||
.unwrap();
|
||||
assert_eq!(options.command, CliCommand::TranslationUnset);
|
||||
|
||||
let options = parse(&[
|
||||
"bat",
|
||||
"i18n",
|
||||
"proofread",
|
||||
"--output",
|
||||
"/tmp/bat-resources",
|
||||
"--localized-output",
|
||||
"/tmp/bat-localized",
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(options.command, CliCommand::TranslationProofread);
|
||||
|
||||
let options = parse(&[
|
||||
"bat",
|
||||
"i18n",
|
||||
"proofread",
|
||||
"--resource-root",
|
||||
"/tmp/bat-resources/versions/v-current",
|
||||
"--localized-output",
|
||||
"/tmp/bat-localized",
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(options.command, CliCommand::TranslationProofread);
|
||||
assert_eq!(
|
||||
options.resource_root,
|
||||
Some(PathBuf::from("/tmp/bat-resources/versions/v-current"))
|
||||
);
|
||||
|
||||
let options = parse(&[
|
||||
"bat",
|
||||
"i18n",
|
||||
@@ -286,6 +314,25 @@ fn grouped_workflow_commands_use_short_top_level_aliases() {
|
||||
Some("provider-run-1")
|
||||
);
|
||||
assert!(parse(&["bat", "i18n", "task", "update", "--task-id", "task-only",]).is_err());
|
||||
assert!(parse(&[
|
||||
"bat",
|
||||
"i18n",
|
||||
"workbench",
|
||||
"proofread",
|
||||
"--translation-file",
|
||||
"/tmp/workbench.json",
|
||||
"--translation-id",
|
||||
"unit-1",
|
||||
])
|
||||
.is_err());
|
||||
assert!(parse(&[
|
||||
"bat",
|
||||
"i18n",
|
||||
"proofread",
|
||||
"--translation-file",
|
||||
"/tmp/workbench.json",
|
||||
])
|
||||
.is_err());
|
||||
assert!(parse(&[
|
||||
"bat",
|
||||
"i18n",
|
||||
@@ -355,7 +402,7 @@ fn translation_workbench_commands_read_update_and_clear_entries() {
|
||||
"bat",
|
||||
"i18n",
|
||||
"set",
|
||||
"--translation-file",
|
||||
"--workbench",
|
||||
&path_arg,
|
||||
"--translation-id",
|
||||
"unit-1",
|
||||
@@ -424,6 +471,49 @@ fn grouped_command_long_aliases_and_schedule_options_are_accepted() {
|
||||
assert_eq!(options.schedule_count, Some(4));
|
||||
assert_eq!(options.schedule_args, vec!["--auto-discover"]);
|
||||
|
||||
let options = parse(&[
|
||||
"bat",
|
||||
"res",
|
||||
"schedule",
|
||||
"add",
|
||||
"--state-dir",
|
||||
"/tmp/bat-schedule",
|
||||
"--id",
|
||||
"alias-pull",
|
||||
"--action",
|
||||
"pull",
|
||||
"--schedule-delay",
|
||||
"1s",
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(options.command, CliCommand::ScheduleAdd);
|
||||
assert_eq!(options.schedule_id.as_deref(), Some("alias-pull"));
|
||||
assert_eq!(options.schedule_action.as_deref(), Some("pull"));
|
||||
|
||||
let options = parse(&[
|
||||
"bat",
|
||||
"parse",
|
||||
"schedule",
|
||||
"list",
|
||||
"--state-dir",
|
||||
"/tmp/bat-parse-schedule",
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(options.command, CliCommand::ScheduleList);
|
||||
assert_eq!(options.schedule_group.as_deref(), Some("parse"));
|
||||
|
||||
let options = parse(&[
|
||||
"bat",
|
||||
"resource",
|
||||
"schedule",
|
||||
"list",
|
||||
"--state-dir",
|
||||
"/tmp/bat-resource-schedule",
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(options.command, CliCommand::ScheduleList);
|
||||
assert_eq!(options.schedule_group.as_deref(), Some("res"));
|
||||
|
||||
let options = parse(&[
|
||||
"bat",
|
||||
"translate",
|
||||
@@ -438,6 +528,36 @@ fn grouped_command_long_aliases_and_schedule_options_are_accepted() {
|
||||
assert_eq!(options.schedule_group.as_deref(), Some("i18n"));
|
||||
assert!(options.config.force);
|
||||
assert_eq!(options.schedule_max_runs, Some(2));
|
||||
|
||||
let options = parse(&["bat", "translation", "tasks", "--json"]).unwrap();
|
||||
assert_eq!(options.command, CliCommand::TranslationTasks);
|
||||
|
||||
let options = parse(&["bat", "translation", "task", "list", "--json"]).unwrap();
|
||||
assert_eq!(options.command, CliCommand::TranslationTasks);
|
||||
|
||||
let options = parse(&["bat", "translation", "task", "status", "--json"]).unwrap();
|
||||
assert_eq!(options.command, CliCommand::TranslationTasks);
|
||||
|
||||
let options = parse(&["bat", "translation", "handoff", "--json"]).unwrap();
|
||||
assert_eq!(options.command, CliCommand::TranslationHandoff);
|
||||
|
||||
let options = parse(&["bat", "translation", "status", "--json"]).unwrap();
|
||||
assert_eq!(options.command, CliCommand::LocalizedStatus);
|
||||
|
||||
let options = parse(&[
|
||||
"bat",
|
||||
"translation",
|
||||
"task",
|
||||
"update",
|
||||
"--state-dir",
|
||||
"/tmp/bat-state",
|
||||
"--task-id",
|
||||
"textunit/v-current/TextAssets/Scenario.json",
|
||||
"--task-status",
|
||||
"running",
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(options.command, CliCommand::TranslationTaskUpdate);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1100,6 +1220,9 @@ fn parses_management_and_resource_commands() {
|
||||
assert_eq!(options.command, expected);
|
||||
}
|
||||
|
||||
let options = parse(&["bat", "resource", "status"]).unwrap();
|
||||
assert_eq!(options.command, CliCommand::ResourceIndex);
|
||||
|
||||
let index = parse(&[
|
||||
"bat",
|
||||
"resource-index",
|
||||
@@ -3067,12 +3190,28 @@ fn write_catalog_fixture(
|
||||
output_root: &Path,
|
||||
current_bundle: &str,
|
||||
previous_bundle: Option<&str>,
|
||||
) -> PathBuf {
|
||||
write_catalog_fixture_with_localized(
|
||||
state_dir,
|
||||
output_root,
|
||||
&output_root.with_file_name("localized"),
|
||||
current_bundle,
|
||||
previous_bundle,
|
||||
)
|
||||
}
|
||||
|
||||
fn write_catalog_fixture_with_localized(
|
||||
state_dir: &Path,
|
||||
output_root: &Path,
|
||||
localized_output_root: &Path,
|
||||
current_bundle: &str,
|
||||
previous_bundle: Option<&str>,
|
||||
) -> PathBuf {
|
||||
fs::create_dir_all(state_dir).unwrap();
|
||||
fs::create_dir_all(output_root).unwrap();
|
||||
write_daemon_status_file(
|
||||
&daemon_status_path(state_dir),
|
||||
&test_daemon_status_file(state_dir, output_root),
|
||||
&test_daemon_status_file_with_localized(state_dir, output_root, localized_output_root),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -4090,6 +4229,7 @@ fn dispatch_localized_status_verifies_current_release_pointer() {
|
||||
official_release_id: "v-current".to_string(),
|
||||
current_release_id: Some("v-current".to_string()),
|
||||
status: "localized".to_string(),
|
||||
translation_workflow_status: None,
|
||||
updated_unix_seconds: 123,
|
||||
})
|
||||
.unwrap(),
|
||||
@@ -4138,6 +4278,165 @@ fn dispatch_localized_status_verifies_current_release_pointer() {
|
||||
assert_eq!(value["data"]["patch_manifest_matches_release"], true);
|
||||
assert_eq!(value["data"]["patch_file_count"], 0);
|
||||
assert_eq!(value["data"]["patch_text_asset_operation_count"], 0);
|
||||
assert_eq!(
|
||||
value["data"]["translation_workflow_status"],
|
||||
serde_json::Value::Null
|
||||
);
|
||||
assert_eq!(
|
||||
value["data"]["published_version_path"].as_str().unwrap(),
|
||||
localized_version.to_string_lossy()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatch_translation_proofread_marks_state_for_dashboard() {
|
||||
let temp = tempfile::TempDir::new().unwrap();
|
||||
let state_dir = temp.path().join("state");
|
||||
let output_root = temp.path().join("output");
|
||||
let localized_root = temp.path().join("localized");
|
||||
write_catalog_fixture_with_localized(
|
||||
&state_dir,
|
||||
&output_root,
|
||||
&localized_root,
|
||||
"bundle-b2",
|
||||
None,
|
||||
);
|
||||
let tasks = test_task_context_with_config(OfficialUpdateConfig {
|
||||
localized_output_root: localized_root.clone(),
|
||||
..OfficialUpdateConfig::default()
|
||||
});
|
||||
|
||||
let envelope = dispatch_rpc_method(
|
||||
&rpc_request("translation.proofread", None),
|
||||
&state_dir,
|
||||
&new_daemon_control(),
|
||||
&tasks,
|
||||
"req-proofread-1".to_string(),
|
||||
);
|
||||
let value = serde_json::to_value(&envelope).unwrap();
|
||||
assert_eq!(value["ok"], true);
|
||||
assert_eq!(
|
||||
value["data"]["translation_workflow_status"],
|
||||
"manual_proofreading"
|
||||
);
|
||||
assert_eq!(
|
||||
value["data"]["translation_workflow_status_code"],
|
||||
"translation.manual_proofreading"
|
||||
);
|
||||
assert_eq!(value["data"]["translation_workflow_label"], "人工校对中");
|
||||
assert_eq!(value["data"]["publish_allowed"], false);
|
||||
|
||||
let envelope = dispatch_rpc_method(
|
||||
&rpc_request("localized.status", None),
|
||||
&state_dir,
|
||||
&new_daemon_control(),
|
||||
&tasks,
|
||||
"req-proofread-status-1".to_string(),
|
||||
);
|
||||
let value = serde_json::to_value(&envelope).unwrap();
|
||||
assert_eq!(value["ok"], true);
|
||||
assert_eq!(value["data"]["status_code"], "localized.pending");
|
||||
assert_eq!(
|
||||
value["data"]["translation_workflow_status_code"],
|
||||
"translation.manual_proofreading"
|
||||
);
|
||||
assert_eq!(value["data"]["translation_workflow_label"], "人工校对中");
|
||||
assert_eq!(
|
||||
value["data"]["state"]["translation_workflow_status"],
|
||||
"manual_proofreading"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn localized_status_keeps_published_release_during_manual_proofreading() {
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
let temp = tempfile::TempDir::new().unwrap();
|
||||
let state_dir = temp.path().join("state");
|
||||
let output_root = temp.path().join("output");
|
||||
let localized_root = temp.path().join("localized");
|
||||
write_catalog_fixture_with_localized(
|
||||
&state_dir,
|
||||
&output_root,
|
||||
&localized_root,
|
||||
"bundle-b2",
|
||||
None,
|
||||
);
|
||||
let localized_version = localized_root
|
||||
.join(LOCALIZED_VERSIONS_DIR)
|
||||
.join("v-current-auto");
|
||||
fs::create_dir_all(&localized_version).unwrap();
|
||||
symlink(
|
||||
Path::new(LOCALIZED_VERSIONS_DIR).join("v-current-auto"),
|
||||
localized_root.join(LOCALIZED_CURRENT_LINK),
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
localized_root.join(LOCALIZED_VERSION_STATE_FILE),
|
||||
serde_json::to_vec(&bat_infrastructure::LocalizedVersionState {
|
||||
state_version: bat_infrastructure::LOCALIZED_VERSION_STATE_VERSION,
|
||||
official_release_id: "v-current".to_string(),
|
||||
current_release_id: Some("v-current-auto".to_string()),
|
||||
status: "localized".to_string(),
|
||||
translation_workflow_status: None,
|
||||
updated_unix_seconds: 123,
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
localized_version.join(LOCALIZED_PATCH_MANIFEST_FILE),
|
||||
serde_json::to_vec(&bat_infrastructure::LocalizedPatchManifest {
|
||||
manifest_version: bat_infrastructure::LOCALIZED_PATCH_MANIFEST_VERSION,
|
||||
official_release_id: "v-current".to_string(),
|
||||
localized_release_id: "v-current-auto".to_string(),
|
||||
generated_unix_seconds: 124,
|
||||
file_count: 0,
|
||||
text_asset_operation_count: 0,
|
||||
files: Vec::new(),
|
||||
rollback: bat_infrastructure::LocalizedPatchRollbackInfo {
|
||||
previous_current_target: None,
|
||||
remove_version_path: localized_version.clone(),
|
||||
},
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let tasks = test_task_context_with_config(OfficialUpdateConfig {
|
||||
localized_output_root: localized_root.clone(),
|
||||
..OfficialUpdateConfig::default()
|
||||
});
|
||||
|
||||
let envelope = dispatch_rpc_method(
|
||||
&rpc_request("translation.proofread", None),
|
||||
&state_dir,
|
||||
&new_daemon_control(),
|
||||
&tasks,
|
||||
"req-proofread-2".to_string(),
|
||||
);
|
||||
let value = serde_json::to_value(&envelope).unwrap();
|
||||
assert_eq!(value["ok"], true);
|
||||
assert_eq!(value["data"]["publish_allowed"], true);
|
||||
|
||||
let envelope = dispatch_rpc_method(
|
||||
&rpc_request("localized.status", None),
|
||||
&state_dir,
|
||||
&new_daemon_control(),
|
||||
&tasks,
|
||||
"req-proofread-status-2".to_string(),
|
||||
);
|
||||
let value = serde_json::to_value(&envelope).unwrap();
|
||||
assert_eq!(value["ok"], true);
|
||||
assert_eq!(value["data"]["status"], "published");
|
||||
assert_eq!(value["data"]["status_code"], "localized.published");
|
||||
assert_eq!(value["data"]["localized_release_status"], "localized");
|
||||
assert_eq!(
|
||||
value["data"]["translation_workflow_status_code"],
|
||||
"translation.manual_proofreading"
|
||||
);
|
||||
assert_eq!(value["data"]["translation_workflow_label"], "人工校对中");
|
||||
assert_eq!(value["data"]["current_points_to_published_version"], true);
|
||||
assert_eq!(
|
||||
value["data"]["published_version_path"].as_str().unwrap(),
|
||||
localized_version.to_string_lossy()
|
||||
@@ -4168,6 +4467,7 @@ fn dispatch_localized_status_rejects_stale_release_state() {
|
||||
official_release_id: "v-old".to_string(),
|
||||
current_release_id: Some("v-old".to_string()),
|
||||
status: "localized".to_string(),
|
||||
translation_workflow_status: None,
|
||||
updated_unix_seconds: 123,
|
||||
})
|
||||
.unwrap(),
|
||||
|
||||
@@ -235,6 +235,24 @@ pub(super) fn run_translation_task_update(options: &CliOptions) -> anyhow::Resul
|
||||
print_json_value(options.output_format, &report)
|
||||
}
|
||||
|
||||
pub(super) fn run_translation_proofread(options: &CliOptions) -> anyhow::Result<()> {
|
||||
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, RPC_METHOD_TRANSLATION_PROOFREAD, None)?;
|
||||
print_json_value(options.output_format, &report)?;
|
||||
return Ok(());
|
||||
}
|
||||
let (_, official_release_id) = current_official_release(options)?;
|
||||
let report = bat_infrastructure::mark_localized_manual_proofreading(
|
||||
&options.config.localized_output_root,
|
||||
&official_release_id,
|
||||
)?;
|
||||
print_report(options.output_format, &report)
|
||||
}
|
||||
|
||||
pub(super) fn run_repack(options: &CliOptions) -> anyhow::Result<()> {
|
||||
let spec = options
|
||||
.repack_spec
|
||||
|
||||
@@ -46,12 +46,15 @@ pub use import::{
|
||||
ResourceImportService,
|
||||
};
|
||||
pub use localized_patch::{
|
||||
read_localized_patch_manifest_at, read_localized_version_state, LocalizedPatchConfig,
|
||||
mark_localized_manual_proofreading, read_localized_patch_manifest_at,
|
||||
read_localized_version_state, write_localized_version_state, LocalizedPatchConfig,
|
||||
LocalizedPatchFile, LocalizedPatchIntegrity, LocalizedPatchManifest, LocalizedPatchOperation,
|
||||
LocalizedPatchReport, LocalizedPatchRollbackInfo, LocalizedPatchService,
|
||||
LocalizedTextAssetPatch, LocalizedVersionState, LOCALIZED_CURRENT_LINK,
|
||||
LOCALIZED_PATCH_MANIFEST_FILE, LOCALIZED_PATCH_MANIFEST_VERSION, LOCALIZED_STAGING_DIR,
|
||||
LOCALIZED_VERSIONS_DIR, LOCALIZED_VERSION_STATE_FILE,
|
||||
LocalizedTextAssetPatch, LocalizedTranslationWorkflowReport, LocalizedVersionState,
|
||||
LOCALIZED_CURRENT_LINK, LOCALIZED_PATCH_MANIFEST_FILE, LOCALIZED_PATCH_MANIFEST_VERSION,
|
||||
LOCALIZED_STAGING_DIR, LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING,
|
||||
LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING_LABEL, LOCALIZED_VERSIONS_DIR,
|
||||
LOCALIZED_VERSION_STATE_FILE, LOCALIZED_VERSION_STATE_VERSION,
|
||||
};
|
||||
pub use official_changes::{
|
||||
read_resource_change_set_at, write_crowdin_translation_handoff_at,
|
||||
|
||||
@@ -23,6 +23,12 @@ pub const LOCALIZED_VERSION_STATE_FILE: &str = "localized-version-state.json";
|
||||
pub const LOCALIZED_PATCH_MANIFEST_FILE: &str = "localized-patch-manifest.json";
|
||||
/// Current localized patch manifest schema version.
|
||||
pub const LOCALIZED_PATCH_MANIFEST_VERSION: u32 = 1;
|
||||
/// Current localized version state schema version.
|
||||
pub const LOCALIZED_VERSION_STATE_VERSION: u32 = 1;
|
||||
/// Stable marker for localized releases that are under human proofreading.
|
||||
pub const LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING: &str = "manual_proofreading";
|
||||
/// Human label for `LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING`.
|
||||
pub const LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING_LABEL: &str = "人工校对中";
|
||||
|
||||
/// One patch operation against a bundle in an official release.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -94,6 +100,7 @@ impl LocalizedPatchConfig {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct LocalizedVersionState {
|
||||
/// State schema version.
|
||||
#[serde(default = "default_localized_version_state_version")]
|
||||
pub state_version: u32,
|
||||
/// Official release ID used as the patch source.
|
||||
pub official_release_id: String,
|
||||
@@ -101,10 +108,59 @@ pub struct LocalizedVersionState {
|
||||
pub current_release_id: Option<String>,
|
||||
/// Stable status label.
|
||||
pub status: String,
|
||||
/// Review/proofreading workflow marker independent from publish status.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub translation_workflow_status: Option<String>,
|
||||
/// Last update time.
|
||||
pub updated_unix_seconds: u64,
|
||||
}
|
||||
|
||||
impl LocalizedVersionState {
|
||||
/// Returns the stable translation workflow status, if set.
|
||||
pub fn translation_workflow_status(&self) -> Option<&str> {
|
||||
self.translation_workflow_status.as_deref()
|
||||
}
|
||||
|
||||
/// Returns the user-facing translation workflow label, if set.
|
||||
pub fn translation_workflow_label(&self) -> Option<&'static str> {
|
||||
match self.translation_workflow_status() {
|
||||
Some(LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING) => {
|
||||
Some(LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING_LABEL)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Report produced when changing localized translation workflow state.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct LocalizedTranslationWorkflowReport {
|
||||
/// Stable command name.
|
||||
pub command: &'static str,
|
||||
/// Stable operation status.
|
||||
pub status: &'static str,
|
||||
/// Localized output root.
|
||||
pub localized_output_root: PathBuf,
|
||||
/// State file written under the localized output root.
|
||||
pub state_path: PathBuf,
|
||||
/// Official release associated with the workflow state.
|
||||
pub official_release_id: String,
|
||||
/// Current published localized release ID, when one exists.
|
||||
pub current_release_id: Option<String>,
|
||||
/// Stable localized publication label, such as `localized`.
|
||||
pub localized_release_status: String,
|
||||
/// Stable translation workflow marker.
|
||||
pub translation_workflow_status: String,
|
||||
/// Stable lifecycle status code for read-side callers.
|
||||
pub translation_workflow_status_code: &'static str,
|
||||
/// Human label for the workflow status.
|
||||
pub translation_workflow_label: &'static str,
|
||||
/// Whether an existing localized release remains publishable after the change.
|
||||
pub publish_allowed: bool,
|
||||
/// Last update time written to the state file.
|
||||
pub updated_unix_seconds: u64,
|
||||
}
|
||||
|
||||
/// One changed file in a localized patch manifest.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct LocalizedPatchFile {
|
||||
@@ -295,6 +351,10 @@ impl LocalizedPatchService {
|
||||
.localized_output_root
|
||||
.join(LOCALIZED_VERSION_STATE_FILE);
|
||||
let patch_manifest_path = version_path.join(LOCALIZED_PATCH_MANIFEST_FILE);
|
||||
let previous_state = read_localized_version_state(&config.localized_output_root)?;
|
||||
let translation_workflow_status = previous_state
|
||||
.filter(|state| state.official_release_id == config.release_id)
|
||||
.and_then(|state| state.translation_workflow_status);
|
||||
|
||||
if version_path.exists() {
|
||||
let message = if config.force {
|
||||
@@ -373,10 +433,11 @@ impl LocalizedPatchService {
|
||||
)?;
|
||||
|
||||
let state = LocalizedVersionState {
|
||||
state_version: 1,
|
||||
state_version: LOCALIZED_VERSION_STATE_VERSION,
|
||||
official_release_id: config.release_id.clone(),
|
||||
current_release_id: Some(config.published_release_id().to_string()),
|
||||
status: "localized".to_string(),
|
||||
translation_workflow_status,
|
||||
updated_unix_seconds: unix_seconds_now(),
|
||||
};
|
||||
write_file_atomic(
|
||||
@@ -415,7 +476,7 @@ pub fn read_localized_version_state(
|
||||
return Ok(None);
|
||||
};
|
||||
let state: LocalizedVersionState = serde_json::from_slice(&bytes)?;
|
||||
if state.state_version != 1 {
|
||||
if state.state_version != LOCALIZED_VERSION_STATE_VERSION {
|
||||
return Err(anyhow::anyhow!(
|
||||
"不支持的汉化版本状态 schema:{},当前版本=1",
|
||||
state.state_version
|
||||
@@ -424,6 +485,70 @@ pub fn read_localized_version_state(
|
||||
Ok(Some(state))
|
||||
}
|
||||
|
||||
/// Writes the localized version state atomically.
|
||||
pub fn write_localized_version_state(
|
||||
localized_output_root: &Path,
|
||||
state: &LocalizedVersionState,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
ensure_safe_directory_path(localized_output_root, "汉化输出目录")
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let path = localized_output_root.join(LOCALIZED_VERSION_STATE_FILE);
|
||||
write_file_atomic(
|
||||
&path,
|
||||
&serde_json::to_vec_pretty(state)?,
|
||||
STATE_FILE_MODE,
|
||||
"汉化版本状态",
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Marks the current localized workflow as under manual proofreading without
|
||||
/// changing the published localized release pointer.
|
||||
pub fn mark_localized_manual_proofreading(
|
||||
localized_output_root: &Path,
|
||||
official_release_id: &str,
|
||||
) -> anyhow::Result<LocalizedTranslationWorkflowReport> {
|
||||
let mut state = read_localized_version_state(localized_output_root)?.unwrap_or_else(|| {
|
||||
LocalizedVersionState {
|
||||
state_version: LOCALIZED_VERSION_STATE_VERSION,
|
||||
official_release_id: official_release_id.to_string(),
|
||||
current_release_id: None,
|
||||
status: "not_localized".to_string(),
|
||||
translation_workflow_status: None,
|
||||
updated_unix_seconds: 0,
|
||||
}
|
||||
});
|
||||
if state.official_release_id != official_release_id {
|
||||
return Err(anyhow::anyhow!(
|
||||
"汉化状态 release={} 与当前官方 release={} 不一致;请先重新发布或指定匹配的官方 release",
|
||||
state.official_release_id,
|
||||
official_release_id
|
||||
));
|
||||
}
|
||||
state.state_version = LOCALIZED_VERSION_STATE_VERSION;
|
||||
state.translation_workflow_status =
|
||||
Some(LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING.to_string());
|
||||
state.updated_unix_seconds = unix_seconds_now();
|
||||
let state_path = write_localized_version_state(localized_output_root, &state)?;
|
||||
|
||||
Ok(LocalizedTranslationWorkflowReport {
|
||||
command: "translation-proofread",
|
||||
status: "updated",
|
||||
localized_output_root: localized_output_root.to_path_buf(),
|
||||
state_path,
|
||||
official_release_id: state.official_release_id.clone(),
|
||||
current_release_id: state.current_release_id.clone(),
|
||||
localized_release_status: state.status.clone(),
|
||||
translation_workflow_status: LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING.to_string(),
|
||||
translation_workflow_status_code:
|
||||
crate::ReleaseFlowStatusCode::TranslationManualProofreading.as_str(),
|
||||
translation_workflow_label: LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING_LABEL,
|
||||
publish_allowed: state.status == "localized" && state.current_release_id.is_some(),
|
||||
updated_unix_seconds: state.updated_unix_seconds,
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads a localized patch manifest from a published version directory.
|
||||
pub fn read_localized_patch_manifest_at(
|
||||
version_path: &Path,
|
||||
@@ -754,6 +879,10 @@ fn default_patch_manifest_version() -> u32 {
|
||||
LOCALIZED_PATCH_MANIFEST_VERSION
|
||||
}
|
||||
|
||||
fn default_localized_version_state_version() -> u32 {
|
||||
LOCALIZED_VERSION_STATE_VERSION
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -846,6 +975,7 @@ mod tests {
|
||||
serde_json::from_slice(&fs::read(report.state_path).unwrap()).unwrap();
|
||||
assert_eq!(state.status, "localized");
|
||||
assert_eq!(state.current_release_id.as_deref(), Some("release-1"));
|
||||
assert_eq!(state.translation_workflow_status(), None);
|
||||
assert!(report.patch_manifest_path.is_file());
|
||||
assert_eq!(report.manifest.file_count, 0);
|
||||
assert_eq!(report.integrity.verified_changed_file_count, 0);
|
||||
@@ -857,6 +987,62 @@ mod tests {
|
||||
assert_eq!(manifest.rollback.previous_current_target, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn localized_state_reads_legacy_file_without_workflow_status() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let localized = temp.path().join("localized");
|
||||
fs::create_dir_all(&localized).unwrap();
|
||||
fs::write(
|
||||
localized.join(LOCALIZED_VERSION_STATE_FILE),
|
||||
br#"{
|
||||
"state_version": 1,
|
||||
"official_release_id": "release-1",
|
||||
"current_release_id": "release-1",
|
||||
"status": "localized",
|
||||
"updated_unix_seconds": 123
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let state = read_localized_version_state(&localized).unwrap().unwrap();
|
||||
|
||||
assert_eq!(state.status, "localized");
|
||||
assert_eq!(state.translation_workflow_status(), None);
|
||||
assert_eq!(state.translation_workflow_label(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manual_proofreading_marker_preserves_published_state() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let localized = temp.path().join("localized");
|
||||
fs::create_dir_all(&localized).unwrap();
|
||||
write_localized_version_state(
|
||||
&localized,
|
||||
&LocalizedVersionState {
|
||||
state_version: LOCALIZED_VERSION_STATE_VERSION,
|
||||
official_release_id: "release-1".to_string(),
|
||||
current_release_id: Some("release-1-auto".to_string()),
|
||||
status: "localized".to_string(),
|
||||
translation_workflow_status: None,
|
||||
updated_unix_seconds: 123,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let report = mark_localized_manual_proofreading(&localized, "release-1").unwrap();
|
||||
let state = read_localized_version_state(&localized).unwrap().unwrap();
|
||||
|
||||
assert_eq!(report.translation_workflow_label, "人工校对中");
|
||||
assert_eq!(
|
||||
state.translation_workflow_status(),
|
||||
Some(LOCALIZED_TRANSLATION_STATUS_MANUAL_PROOFREADING)
|
||||
);
|
||||
assert_eq!(state.translation_workflow_label(), Some("人工校对中"));
|
||||
assert_eq!(state.status, "localized");
|
||||
assert_eq!(state.current_release_id.as_deref(), Some("release-1-auto"));
|
||||
assert!(report.publish_allowed);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn failed_patch_publish_cleans_staging_and_unpublished_version() {
|
||||
|
||||
@@ -63,6 +63,9 @@ pub enum ReleaseFlowStatusCode {
|
||||
/// Translation tasks have been queued to local offline handoff files.
|
||||
#[serde(rename = "translation.queued_offline")]
|
||||
TranslationQueuedOffline,
|
||||
/// Human proofreading is in progress for the observed localized release.
|
||||
#[serde(rename = "translation.manual_proofreading")]
|
||||
TranslationManualProofreading,
|
||||
/// Localized publication is blocked because there is no official release.
|
||||
#[serde(rename = "localized.blocked_official")]
|
||||
LocalizedBlockedOfficial,
|
||||
@@ -105,6 +108,7 @@ impl ReleaseFlowStatusCode {
|
||||
Self::TranslationUnavailable => "translation.unavailable",
|
||||
Self::TranslationHandoffPreparing => "translation.handoff_preparing",
|
||||
Self::TranslationQueuedOffline => "translation.queued_offline",
|
||||
Self::TranslationManualProofreading => "translation.manual_proofreading",
|
||||
Self::LocalizedBlockedOfficial => "localized.blocked_official",
|
||||
Self::LocalizedPending => "localized.pending",
|
||||
Self::LocalizedStale => "localized.stale",
|
||||
@@ -137,6 +141,7 @@ impl ReleaseFlowStatusCode {
|
||||
Self::TranslationUnavailable => "unavailable",
|
||||
Self::TranslationHandoffPreparing => "handoff_preparing",
|
||||
Self::TranslationQueuedOffline => "queued_offline",
|
||||
Self::TranslationManualProofreading => "manual_proofreading",
|
||||
Self::LocalizedBlockedOfficial => "blocked_official",
|
||||
Self::LocalizedPending => "pending",
|
||||
Self::LocalizedStale => "stale",
|
||||
@@ -167,6 +172,7 @@ impl ReleaseFlowStatusCode {
|
||||
"translation.unavailable" => Self::TranslationUnavailable,
|
||||
"translation.handoff_preparing" => Self::TranslationHandoffPreparing,
|
||||
"translation.queued_offline" => Self::TranslationQueuedOffline,
|
||||
"translation.manual_proofreading" => Self::TranslationManualProofreading,
|
||||
"localized.blocked_official" => Self::LocalizedBlockedOfficial,
|
||||
"localized.pending" => Self::LocalizedPending,
|
||||
"localized.stale" => Self::LocalizedStale,
|
||||
@@ -197,7 +203,8 @@ impl ReleaseFlowStatusCode {
|
||||
| Self::ParseCompletedWithErrors => "parse",
|
||||
Self::TranslationUnavailable
|
||||
| Self::TranslationHandoffPreparing
|
||||
| Self::TranslationQueuedOffline => "translation",
|
||||
| Self::TranslationQueuedOffline
|
||||
| Self::TranslationManualProofreading => "translation",
|
||||
Self::LocalizedBlockedOfficial
|
||||
| Self::LocalizedPending
|
||||
| Self::LocalizedStale
|
||||
@@ -288,6 +295,7 @@ mod tests {
|
||||
ReleaseFlowStatusCode::TranslationUnavailable,
|
||||
ReleaseFlowStatusCode::TranslationHandoffPreparing,
|
||||
ReleaseFlowStatusCode::TranslationQueuedOffline,
|
||||
ReleaseFlowStatusCode::TranslationManualProofreading,
|
||||
ReleaseFlowStatusCode::LocalizedBlockedOfficial,
|
||||
ReleaseFlowStatusCode::LocalizedPending,
|
||||
ReleaseFlowStatusCode::LocalizedStale,
|
||||
|
||||
Reference in New Issue
Block a user