From 826c9e93751e42cd0939c81af9df793610fd0b55 Mon Sep 17 00:00:00 2001 From: liujianqiang Date: Mon, 9 Feb 2026 10:04:15 +0800 Subject: [PATCH] feat: add bidirectional translation support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhanced translation functionality to support both Chinese-to-English and English-to-Chinese translation directions Added command-line options --to-english and --to-chinese for explicit direction control Implemented configurable default translation direction via config command Updated translation prompts for different directions with specific formatting rules Added text wrapping for Chinese content to maintain proper formatting Modified AI service interface to accept translation direction parameter Updated documentation with new features and examples Log: Added bidirectional translation support with configurable direction Influence: 1. Test Chinese-to-English translation with various Chinese text inputs 2. Test English-to-Chinese translation with different English content 3. Verify translation direction configuration persistence 4. Test command-line options for explicit translation direction control 5. Validate translation quality and format preservation 6. Test mixed content handling (Chinese and English in same text) 7. Verify configuration settings for default translation direction feat: 添加双向翻译支持 增强翻译功能,支持中译英和英译中双向翻译 添加命令行选项 --to-english 和 --to-chinese 用于显式控制翻译方向 实现可通过配置命令设置默认翻译方向 为不同翻译方向更新提示词,包含特定的格式规则 添加中文文本自动换行以保持格式规范 修改 AI 服务接口以接受翻译方向参数 更新文档,包含新功能和示例 Log: 新增双向翻译支持,可配置翻译方向 Influence: 1. 测试中译英功能,使用各种中文文本输入 2. 测试英译中功能,使用不同英文内容 3. 验证翻译方向配置的持久性 4. 测试命令行选项的显式翻译方向控制 5. 验证翻译质量和格式保持 6. 测试混合内容处理(中英文混合文本) 7. 验证默认翻译方向的配置设置 Fixes: zccrs/git-commit-helper#16 --- README.md | 64 ++++++++++++++++++++++++++++++++++++++---- src/ai_service.rs | 50 +++++++++++++++++++++++++-------- src/commit.rs | 2 +- src/config.rs | 20 +++++++++++-- src/git.rs | 7 +++-- src/main.rs | 71 +++++++++++++++++++++++++++++++++++++++-------- 6 files changed, 179 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index ba3a230..5962d52 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,10 @@ - 自动审查代码变更 - 性能和安全建议 - 可通过参数禁用 -- 🌏 中英双语 - - 自动检测中文内容 +- 🌏 双向翻译 + - 支持中文翻译为英文(默认) + - 支持英文翻译为中文 + - 可配置默认翻译方向 - 智能中英互译 - 保持格式规范 - 📋 测试建议 @@ -173,6 +175,10 @@ a line exceeds the recommended value of git. ### 翻译命令 +翻译命令支持双向翻译,可以进行中译英或英译中。 + +#### 基本用法 + 使用翻译命令有三种方式: ```bash # 方式1:指定文件路径 @@ -188,11 +194,32 @@ git-commit-helper translate /path/to/existing/file # 文件路径 命令会自动判断参数内容:如果是一个存在的文件路径则读取文件内容进行翻译,否则将参数内容作为文本进行翻译。 +#### 翻译方向控制 + +默认情况下,翻译命令使用配置文件中设置的默认翻译方向(默认为中译英)。你可以通过以下方式控制翻译方向: + +```bash +# 中译英(使用默认方向或显式指定) +git-commit-helper translate "这是中文内容" +git-commit-helper translate --to-english "这是中文内容" + +# 英译中 +git-commit-helper translate --to-chinese "This is English content" + +# 设置默认翻译方向 +git-commit-helper config --set-translate-direction to-english # 默认中译英 +git-commit-helper config --set-translate-direction to-chinese # 默认英译中 +``` + +支持的翻译方向值: +- `to-english` 或 `chinese-to-english` 或 `中译英`:中文翻译为英文 +- `to-chinese` 或 `english-to-chinese` 或 `英译中`:英文翻译为中文 + ### 命令概览 | 命令 | 说明 | 示例 | |------|------|------| -| config | 配置 AI 服务 | `git-commit-helper config [--set-only-chinese /--set-only-english ]` | +| config | 配置 AI 服务 | `git-commit-helper config [--set-only-chinese /--set-only-english /--set-translate-direction ]` | | show | 显示当前配置 | `git-commit-helper show` | | install | 安装 Git Hook | `git-commit-helper install [-f]` | | ai add | 添加 AI 服务 | `git-commit-helper ai add` | @@ -202,7 +229,7 @@ git-commit-helper translate /path/to/existing/file # 文件路径 | ai set-timeout | 设置请求超时 | `git-commit-helper ai set-timeout -s 30` | | ai list | 列出所有服务 | `git-commit-helper ai list` | | ai test | 测试指定服务 | `git-commit-helper ai test [-t "测试文本"]` | -| translate | 翻译内容 | `git-commit-helper translate [-f 文件] [-t 文本]` | +| translate | 翻译内容 | `git-commit-helper translate [-f 文件] [-t 文本] [--to-english\|--to-chinese]` | | commit | 生成提交信息 | `git-commit-helper commit [-t 类型] [-m 描述] [-a] [--amend] [--no-review/--no-influence/--no-log/--only-chinese/--only-english] [--issues ISSUE...]` | | ai-review | 管理 AI 代码审查 | `git-commit-helper ai-review [--enable/--disable/--status]` | @@ -223,7 +250,18 @@ git-commit-helper translate /path/to/existing/file # 文件路径 ```bash # 配置 git-commit-helper config [选项] - --set-only-chinese 设置默认是否只使用中文提交信息 + --set-only-chinese 设置默认是否只使用中文提交信息 + --set-only-english 设置默认是否只使用英文提交信息 + --set-translate-direction 设置默认翻译方向 + 可选值: to-english(中译英), to-chinese(英译中) + +# 翻译内容 +git-commit-helper translate [选项] [内容] + -f, --file 指定要翻译的文件路径 + -t, --text 指定要翻译的文本内容 + --to-english 翻译为英文(中译英) + --to-chinese 翻译为中文(英译中) + [内容] 直接提供要翻译的文本或文件路径(智能判断) # 远程代码审查 git-commit-helper @@ -258,6 +296,22 @@ git-commit-helper commit [选项] 示例: ```bash +# 翻译示例 +# 使用默认翻译方向(默认为中译英) +git-commit-helper translate "这是一段中文内容" + +# 显式指定中译英 +git-commit-helper translate --to-english "这是一段中文内容" + +# 英译中 +git-commit-helper translate --to-chinese "This is English content" + +# 设置默认翻译方向为中译英 +git-commit-helper config --set-translate-direction to-english + +# 设置默认翻译方向为英译中 +git-commit-helper config --set-translate-direction to-chinese + # 生成提交信息 git-commit-helper commit diff --git a/src/ai_service.rs b/src/ai_service.rs index bf145a4..32bd1be 100644 --- a/src/ai_service.rs +++ b/src/ai_service.rs @@ -13,9 +13,9 @@ use copilot_client::CopilotClient; #[async_trait] pub trait AiService: Send + Sync { - async fn translate(&self, text: &str) -> anyhow::Result { + async fn translate(&self, text: &str, direction: &crate::config::TranslateDirection) -> anyhow::Result { // 使用翻译的 prompt - let system_prompt = get_translation_prompt(text); + let system_prompt = get_translation_prompt(text, direction); Ok(self.chat(&system_prompt, text).await?) } @@ -218,9 +218,12 @@ fn wrap_chinese_text(text: &str, max_width: usize) -> String { result } -fn get_translation_prompt(text: &str) -> String { - let prompt = format!( - r#"You are a professional translator. Please translate the following Chinese text to English. +fn get_translation_prompt(text: &str, direction: &crate::config::TranslateDirection) -> String { + use crate::config::TranslateDirection; + + let prompt = match direction { + TranslateDirection::ChineseToEnglish => format!( + r#"You are a professional translator. Please translate the following Chinese text to English. Important rules: 1. Keep all English content, numbers, and English punctuation unchanged 2. Do not translate any content inside English double quotes @@ -237,7 +240,30 @@ fn get_translation_prompt(text: &str) -> String { Text to translate: {}"#, - wrap_chinese_text(text, 72)); + wrap_chinese_text(text, 72) + ), + TranslateDirection::EnglishToChinese => format!( + r#"You are a professional translator. Please translate the following English text to Chinese. + Important rules: + 1. Keep all Chinese content, numbers, and Chinese punctuation unchanged + 2. Do not translate any content inside quotes + 3. Maintain the original text structure and formatting + 4. Only return the Chinese translation, DO NOT include the original English text + 5. Keep simple and concise, no need to rewrite or expand the content + 6. Use appropriate Chinese punctuation marks + + Example response format: + feat: 添加外部插件支持 + + 1. 实现插件加载机制 + 2. 添加插件配置接口 + 3. 设置插件发现路径: "/plugins" + + Text to translate: + {}"#, + text + ), + }; debug!("生成的提示词:\n{}", prompt); prompt @@ -725,7 +751,7 @@ pub async fn create_translator(config: &Config) -> anyhow::Result anyhow::Result { +pub async fn translate_with_fallback(config: &Config, text: &str, direction: &crate::config::TranslateDirection) -> anyhow::Result { let mut tried_services = Vec::new(); // 如果已设置环境变量,直接返回原文 @@ -734,7 +760,7 @@ pub async fn translate_with_fallback(config: &Config, text: &str) -> anyhow::Res } debug!("尝试使用默认服务 {:?}", config.default_service); - if let Some(result) = try_translate(&config.default_service, config, text).await { + if let Some(result) = try_translate(&config.default_service, config, text, direction).await { return result; } tried_services.push(config.default_service.clone()); @@ -745,7 +771,7 @@ pub async fn translate_with_fallback(config: &Config, text: &str) -> anyhow::Res } debug!("尝试使用备选服务 {:?}", service_config.service); - if let Some(result) = try_translate(&service_config.service, config, text).await { + if let Some(result) = try_translate(&service_config.service, config, text, direction).await { return result; } tried_services.push(service_config.service.clone()); @@ -753,7 +779,7 @@ pub async fn translate_with_fallback(config: &Config, text: &str) -> anyhow::Res while let Some(service) = select_retry_service(config, &tried_services)? { debug!("用户选择使用 {:?} 重试", service); - if let Some(result) = try_translate(&service, config, text).await { + if let Some(result) = try_translate(&service, config, text, direction).await { return result; } tried_services.push(service); @@ -762,12 +788,12 @@ pub async fn translate_with_fallback(config: &Config, text: &str) -> anyhow::Res Err(anyhow::anyhow!("所有AI服务均失败")) } -async fn try_translate(service: &AIService, config: &Config, text: &str) -> Option> { +async fn try_translate(service: &AIService, config: &Config, text: &str, direction: &crate::config::TranslateDirection) -> Option> { let service_config = config.services.iter() .find(|s| s.service == *service)?; let translator = create_translator_for_service(service_config).await.ok()?; - match translator.translate(text).await { + match translator.translate(text, direction).await { Ok(result) => Some(Ok(result)), Err(e) => { warn!("{:?} 服务翻译失败: {}", service, e); diff --git a/src/commit.rs b/src/commit.rs index 5af5494..4105a8f 100644 --- a/src/commit.rs +++ b/src/commit.rs @@ -959,7 +959,7 @@ pub async fn generate_commit_suggestion(commit_types: &[String], user_descriptio None => get_staged_diff()? }; - let message = translator.translate(&prompt).await?.to_string(); + let message = translator.translate(&prompt, &config::TranslateDirection::ChineseToEnglish).await?.to_string(); // 移除各种 AI 返回的元信息标记 let message = message diff --git a/src/config.rs b/src/config.rs index c7e07c8..4b59b9f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -26,6 +26,8 @@ pub struct Config { pub only_chinese: bool, // 是否默认只使用中文 #[serde(default = "default_only_english")] pub only_english: bool, // 是否默认只使用英文 + #[serde(default = "default_translate_direction")] + pub translate_direction: TranslateDirection, // 默认翻译方向 } // 添加默认值函数 @@ -37,6 +39,16 @@ fn default_only_english() -> bool { false } +fn default_translate_direction() -> TranslateDirection { + TranslateDirection::ChineseToEnglish +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub enum TranslateDirection { + ChineseToEnglish, // 中译英(默认) + EnglishToChinese, // 英译中 +} + #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct GerritConfig { pub username: Option, @@ -89,6 +101,7 @@ impl Config { gerrit: None, only_chinese: false, // 默认关闭 only_english: false, // 默认关闭 + translate_direction: default_translate_direction(), // 默认中译英 } } @@ -293,6 +306,7 @@ impl Config { gerrit: None, only_chinese: false, // 默认关闭 only_english: false, // 默认关闭 + translate_direction: default_translate_direction(), // 默认中译英 }; // 确保配置目录存在 @@ -322,9 +336,10 @@ impl Config { gerrit: None, only_chinese: false, only_english: false, + translate_direction: default_translate_direction(), }; let translator = ai_service::create_translator(&test_config).await?; - match translator.translate("这是一个测试消息,用于验证翻译功能是否正常。").await { + match translator.translate("这是一个测试消息,用于验证翻译功能是否正常。", &TranslateDirection::ChineseToEnglish).await { Ok(result) => { println!("\n测试结果:"); println!("原文: 这是一个测试消息,用于验证翻译功能是否正常。"); @@ -496,11 +511,12 @@ impl Config { gerrit: None, only_chinese: false, only_english: false, + translate_direction: default_translate_direction(), }; let translator = ai_service::create_translator(&test_config).await?; let text = "这是一个测试消息,用于验证翻译功能是否正常。"; debug!("开始发送翻译请求"); - match translator.translate(text).await { + match translator.translate(text, &TranslateDirection::ChineseToEnglish).await { Ok(result) => { debug!("收到翻译响应"); println!("\n测试结果:"); diff --git a/src/git.rs b/src/git.rs index 75be2f6..d9952b7 100644 --- a/src/git.rs +++ b/src/git.rs @@ -1,6 +1,7 @@ use crate::commit::CommitMessage; use crate::ai_service; use crate::review; +use crate::config::{self, TranslateDirection}; use dialoguer::Confirm; use log::{debug, info}; use std::path::Path; @@ -57,14 +58,14 @@ pub async fn process_commit_msg(path: &Path, no_review: bool) -> anyhow::Result< info!("开始翻译流程,默认使用 {:?} 服务", config.default_service); - // 翻译标题 - let en_title = ai_service::translate_with_fallback(&config, &msg.title).await?; + // 翻译标题(中译英) + let en_title = ai_service::translate_with_fallback(&config, &msg.title, &TranslateDirection::ChineseToEnglish).await?; let en_title = wrap_text(&en_title, MAX_LINE_LENGTH); let original_title = msg.title.clone(); // 翻译正文(如果有的话) let (en_body, cn_body) = if let Some(body) = &msg.body { - let en_body = ai_service::translate_with_fallback(&config, body).await?; + let en_body = ai_service::translate_with_fallback(&config, body, &TranslateDirection::ChineseToEnglish).await?; (Some(wrap_text(&en_body, MAX_LINE_LENGTH)), Some(body.clone())) } else { (None, None) diff --git a/src/main.rs b/src/main.rs index 2d8c751..a957999 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,6 +42,9 @@ enum Commands { /// 设置默认是否只使用英文提交信息,true: 仅英文,false: 中英双语 #[arg(long = "set-only-english", help = "设置是否默认只使用英文提交信息,true: 仅英文,false: 中英双语")] only_english: Option, + /// 设置默认翻译方向:to-english(中译英,默认)或 to-chinese(英译中) + #[arg(long = "set-translate-direction", help = "设置默认翻译方向:to-english(中译英)或 to-chinese(英译中)")] + translate_direction: Option, }, /// 显示当前配置信息 Show, @@ -60,7 +63,7 @@ enum Commands { #[command(subcommand)] command: ServiceCommands, }, - /// 翻译中文内容为英文 + /// 翻译内容 Translate { /// 要翻译的文件路径 #[arg(short, long)] @@ -70,6 +73,12 @@ enum Commands { text: Option, /// 要翻译的内容 content: Option, + /// 翻译为中文(英译中) + #[arg(long = "to-chinese", conflicts_with = "to_english")] + to_chinese: bool, + /// 翻译为英文(中译英,默认) + #[arg(long = "to-english", conflicts_with = "to_chinese")] + to_english: bool, }, /// 生成提交信息 #[command(name = "commit")] @@ -205,14 +214,16 @@ async fn main() -> Result<()> { }; match cli.command { - Some(Commands::Config { only_chinese, only_english }) => { + Some(Commands::Config { only_chinese, only_english, translate_direction }) => { + let mut config = config::Config::load().unwrap_or_else(|_| config::Config::new()); + let mut config_changed = false; + if let Some(only_chinese) = only_chinese { - let mut config = config::Config::load().unwrap_or_else(|_| config::Config::new()); config.only_chinese = only_chinese; if only_chinese { config.only_english = false; // 如果设置为仅中文,则清除仅英文标志 } - config.save()?; + config_changed = true; let language_mode = if config.only_chinese { "仅中文" } else if config.only_english { @@ -221,14 +232,14 @@ async fn main() -> Result<()> { "中英双语" }; println!("{}", Style::green(&format!("已将默认提交信息语言设置为: {}", language_mode))); - Ok(()) - } else if let Some(only_english) = only_english { - let mut config = config::Config::load().unwrap_or_else(|_| config::Config::new()); + } + + if let Some(only_english) = only_english { config.only_english = only_english; if only_english { config.only_chinese = false; // 如果设置为仅英文,则清除仅中文标志 } - config.save()?; + config_changed = true; let language_mode = if config.only_chinese { "仅中文" } else if config.only_english { @@ -237,6 +248,27 @@ async fn main() -> Result<()> { "中英双语" }; println!("{}", Style::green(&format!("已将默认提交信息语言设置为: {}", language_mode))); + } + + if let Some(direction_str) = translate_direction { + let direction = match direction_str.as_str() { + "to-english" | "chinese-to-english" | "中译英" => config::TranslateDirection::ChineseToEnglish, + "to-chinese" | "english-to-chinese" | "英译中" => config::TranslateDirection::EnglishToChinese, + _ => { + return Err(anyhow::anyhow!("无效的翻译方向,请使用 'to-english'(中译英)或 'to-chinese'(英译中)")); + } + }; + config.translate_direction = direction.clone(); + config_changed = true; + let direction_name = match direction { + config::TranslateDirection::ChineseToEnglish => "中译英", + config::TranslateDirection::EnglishToChinese => "英译中", + }; + println!("{}", Style::green(&format!("已将默认翻译方向设置为: {}", direction_name))); + } + + if config_changed { + config.save()?; Ok(()) } else { config::Config::interactive_config().await?; @@ -351,7 +383,7 @@ async fn main() -> Result<()> { let translator = ai_service::create_translator_for_service(service).await?; let test_text = text.unwrap_or_else(|| "这是一个测试消息,用于验证翻译功能是否正常。".to_string()); debug!("开始发送翻译请求"); - match translator.translate(&test_text).await { + match translator.translate(&test_text, &config::TranslateDirection::ChineseToEnglish).await { Ok(result) => { debug!("收到翻译响应"); println!("{}", Style::separator()); @@ -379,7 +411,7 @@ async fn main() -> Result<()> { } } } - Some(Commands::Translate { file, text, content }) => { + Some(Commands::Translate { file, text, content, to_chinese, to_english }) => { let config = config::Config::load()?; if config.services.is_empty() { return Err(anyhow::anyhow!("没有配置任何 AI 服务,请先添加服务")); @@ -401,11 +433,26 @@ async fn main() -> Result<()> { return Err(anyhow::anyhow!("请提供要翻译的内容")); }; + // 确定翻译方向 + let direction = if to_chinese { + config::TranslateDirection::EnglishToChinese + } else if to_english { + config::TranslateDirection::ChineseToEnglish + } else { + // 使用配置文件中的默认方向 + config.translate_direction.clone() + }; + + let direction_str = match direction { + config::TranslateDirection::ChineseToEnglish => "中译英", + config::TranslateDirection::EnglishToChinese => "英译中", + }; + let service = config.get_default_service()?; - println!("{}", Style::title(&format!("正在使用 {:?} 服务进行翻译...", service.service))); + println!("{}", Style::title(&format!("正在使用 {:?} 服务进行翻译({})...", service.service, direction_str))); let translator = ai_service::create_translator_for_service(service).await?; - match translator.translate(&content).await { + match translator.translate(&content, &direction).await { Ok(result) => { println!("{}", Style::separator()); println!("{}", Style::title("翻译结果:"));