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("翻译结果:"));