From 0cbd975748712e4492fdde85cdc80e5c0f9cfa3c Mon Sep 17 00:00:00 2001 From: SidneyZhang Date: Sun, 1 Feb 2026 12:06:12 +0000 Subject: [PATCH] feat: feat: add multilingual output support for commit, tag, and changelog commands --- src/commands/changelog.rs | 67 ++- src/commands/commit.rs | 55 +- src/commands/config.rs | 79 ++- src/commands/init.rs | 89 ++- src/commands/tag.rs | 67 +-- src/config/manager.rs | 40 ++ src/config/mod.rs | 86 +++ src/i18n/messages.rs | 1091 +++++++++++++++++++++++++++++++++++++ src/i18n/mod.rs | 7 + src/i18n/translator.rs | 233 ++++++++ src/main.rs | 1 + 11 files changed, 1710 insertions(+), 105 deletions(-) create mode 100644 src/i18n/messages.rs create mode 100644 src/i18n/mod.rs create mode 100644 src/i18n/translator.rs diff --git a/src/commands/changelog.rs b/src/commands/changelog.rs index 85a8a71..66a6eff 100644 --- a/src/commands/changelog.rs +++ b/src/commands/changelog.rs @@ -5,10 +5,11 @@ use colored::Colorize; use dialoguer::{Confirm, Input}; use std::path::PathBuf; -use crate::config::manager::ConfigManager; +use crate::config::{Language, manager::ConfigManager}; use crate::generator::ContentGenerator; use crate::git::find_repo; use crate::git::{changelog::*, CommitInfo, GitRepo}; +use crate::i18n::{Messages, translate_changelog_category}; /// Generate changelog #[derive(Parser)] @@ -67,6 +68,8 @@ impl ChangelogCommand { let repo = find_repo(std::env::current_dir()?.as_path())?; let manager = ConfigManager::new()?; let config = manager.config(); + let language = manager.get_language().unwrap_or(Language::English); + let messages = Messages::new(language); // Initialize changelog if requested if self.init { @@ -75,7 +78,7 @@ impl ChangelogCommand { .unwrap_or_else(|| PathBuf::from(&config.changelog.path)); init_changelog(&path)?; - println!("{} Initialized changelog at {:?}", "✓".green(), path); + println!("{}", messages.initialized_changelog(&format!("{:?}", path))); return Ok(()); } @@ -98,28 +101,28 @@ impl ChangelogCommand { v.clone() } else if !self.yes { Input::new() - .with_prompt("Version") - .default("Unreleased".to_string()) + .with_prompt(messages.version()) + .default(messages.unreleased().to_string()) .interact_text()? } else { - "Unreleased".to_string() + messages.unreleased().to_string() }; // Get commits - println!("{} Fetching commits...", "→".blue()); + println!("{}", messages.fetching_commits()); let commits = generate_from_history(&repo, self.from.as_deref(), Some(&self.to))?; if commits.is_empty() { - bail!("No commits found in the specified range"); + bail!("{}", messages.no_commits_found()); } - println!("{} Found {} commits", "✓".green(), commits.len()); + println!("{}", messages.found_commits(commits.len())); // Generate changelog let changelog = if self.generate || (config.changelog.auto_generate && !self.yes) { - self.generate_with_ai(&repo, &version, &commits).await? + self.generate_with_ai(&repo, &version, &commits, &messages).await? } else { - self.generate_with_template(format, &version, &commits)? + self.generate_with_template(format, &version, &commits, language)? }; // Output or write @@ -133,7 +136,7 @@ impl ChangelogCommand { // Preview if !self.yes { println!("\n{}", "─".repeat(60)); - println!("{}", "Changelog preview:".bold()); + println!("{}", messages.changelog_preview().bold()); println!("{}", "─".repeat(60)); // Show first 20 lines let preview: String = changelog.lines().take(20).collect::>().join("\n"); @@ -144,12 +147,12 @@ impl ChangelogCommand { println!("{}", "─".repeat(60)); let confirm = Confirm::new() - .with_prompt(&format!("Write to {:?}?", output_path)) + .with_prompt(&messages.write_to_file(&format!("{:?}", output_path))) .default(true) .interact()?; if !confirm { - println!("{}", "Cancelled.".yellow()); + println!("{}", messages.cancelled().yellow()); return Ok(()); } } @@ -163,7 +166,7 @@ impl ChangelogCommand { std::fs::write(&output_path, changelog)?; } - println!("{} Changelog written to {:?}", "✓".green(), output_path); + println!("{} {:?}", messages.changelog_written(), output_path); Ok(()) } @@ -173,11 +176,12 @@ impl ChangelogCommand { repo: &GitRepo, version: &str, commits: &[CommitInfo], + messages: &Messages, ) -> Result { let manager = ConfigManager::new()?; let config = manager.config(); - println!("{} AI is generating changelog...", "🤖"); + println!("{}", messages.ai_generating_changelog()); let generator = ContentGenerator::new(&config.llm).await?; generator.generate_changelog_entry(version, commits).await @@ -188,12 +192,43 @@ impl ChangelogCommand { format: ChangelogFormat, version: &str, commits: &[CommitInfo], + language: Language, ) -> Result { + let manager = ConfigManager::new()?; + let generator = ChangelogGenerator::new() .format(format) .include_hashes(self.include_hashes) .include_authors(self.include_authors); - generator.generate(version, Utc::now(), commits) + let changelog = generator.generate(version, Utc::now(), commits)?; + + // Translate changelog categories if configured + if !manager.keep_changelog_types_english() { + Ok(self.translate_changelog_categories(&changelog, language)) + } else { + Ok(changelog) + } + } + + fn translate_changelog_categories(&self, changelog: &str, language: Language) -> String { + let translated = changelog + .lines() + .map(|line| { + if line.starts_with("## ") || line.starts_with("### ") { + let category = line.trim_start_matches("## ").trim_start_matches("### "); + let translated_category = translate_changelog_category(category, language, false); + if line.starts_with("## ") { + format!("## {}", translated_category) + } else { + format!("### {}", translated_category) + } + } else { + line.to_string() + } + }) + .collect::>() + .join("\n"); + translated } } diff --git a/src/commands/commit.rs b/src/commands/commit.rs index e578e4c..4a10c52 100644 --- a/src/commands/commit.rs +++ b/src/commands/commit.rs @@ -3,11 +3,12 @@ use clap::Parser; use colored::Colorize; use dialoguer::{Confirm, Input, Select}; -use crate::config::manager::ConfigManager; +use crate::config::{Language, manager::ConfigManager}; use crate::config::CommitFormat; use crate::generator::ContentGenerator; use crate::git::{find_repo, GitRepo}; use crate::git::commit::{CommitBuilder, create_date_commit_message}; +use crate::i18n::{Messages, translate_commit_type}; use crate::utils::validators::get_commit_types; /// Generate and execute conventional commits @@ -79,15 +80,17 @@ impl CommitCommand { // Find git repository let repo = find_repo(std::env::current_dir()?.as_path())?; - // Check for changes - let status = repo.status_summary()?; - if status.clean && !self.amend { - bail!("No changes to commit. Working tree is clean."); - } - // Load configuration let manager = ConfigManager::new()?; let config = manager.config(); + let language = manager.get_language().unwrap_or(Language::English); + let messages = Messages::new(language); + + // Check for changes + let status = repo.status_summary()?; + if status.clean && !self.amend { + bail!("{}", messages.no_changes()); + } // Determine commit format let format = if self.conventional { @@ -101,7 +104,7 @@ impl CommitCommand { // Stage all if requested if self.all { repo.stage_all()?; - println!("{}", "✓ Staged all changes".green()); + println!("{}", messages.staged_all().green()); } // Generate or build commit message @@ -113,10 +116,10 @@ impl CommitCommand { self.create_manual_commit(format)? } else if config.commit.auto_generate && !self.yes { // AI-generated commit - self.generate_commit(&repo, format).await? + self.generate_commit(&repo, format, &messages).await? } else { // Interactive commit creation - self.create_interactive_commit(format).await? + self.create_interactive_commit(format, &messages).await? }; // Validate message @@ -132,32 +135,32 @@ impl CommitCommand { // Show commit preview if !self.yes { println!("\n{}", "─".repeat(60)); - println!("{}", "Commit preview:".bold()); + println!("{}", messages.commit_preview().bold()); println!("{}", "─".repeat(60)); println!("{}", commit_message); println!("{}", "─".repeat(60)); let confirm = Confirm::new() - .with_prompt("Do you want to proceed with this commit?") + .with_prompt(messages.proceed_commit()) .default(true) .interact()?; if !confirm { - println!("{}", "Commit cancelled.".yellow()); + println!("{}", messages.commit_cancelled().yellow()); return Ok(()); } } let result = if self.amend { if self.dry_run { - println!("\n{}", "Dry run - commit not amended.".yellow()); + println!("\n{} {}", messages.dry_run(), "- commit not amended.".yellow()); return Ok(()); } self.amend_commit(&repo, &commit_message)?; None } else { if self.dry_run { - println!("\n{}", "Dry run - commit not created.".yellow()); + println!("\n{} {}", messages.dry_run(), "- commit not created.".yellow()); return Ok(()); } CommitBuilder::new() @@ -167,9 +170,9 @@ impl CommitCommand { }; if let Some(commit_oid) = result { - println!("{} {}", "✓ Created commit".green().bold(), commit_oid.to_string()[..8].to_string().cyan()); + println!("{} {}", messages.commit_created().green().bold(), commit_oid.to_string()[..8].to_string().cyan()); } else { - println!("{} {}", "✓ Amended commit".green().bold(), "successfully"); + println!("{} {}", messages.commit_amended().green().bold(), "successfully"); } Ok(()) @@ -198,7 +201,7 @@ impl CommitCommand { builder.build_message() } - async fn generate_commit(&self, repo: &GitRepo, format: CommitFormat) -> Result { + async fn generate_commit(&self, repo: &GitRepo, format: CommitFormat, messages: &Messages) -> Result { let manager = ConfigManager::new()?; let config = manager.config(); @@ -206,7 +209,7 @@ impl CommitCommand { let generator = ContentGenerator::new(&config.llm).await .context("Failed to initialize LLM. Use --manual for manual commit.")?; - println!("{} AI is analyzing your changes...", "🤖".to_string()); + println!("{}", messages.ai_analyzing()); let generated = if self.yes { generator.generate_commit_from_repo(repo, format).await? @@ -217,42 +220,42 @@ impl CommitCommand { Ok(generated.to_conventional()) } - async fn create_interactive_commit(&self, format: CommitFormat) -> Result { + async fn create_interactive_commit(&self, format: CommitFormat, messages: &Messages) -> Result { let types = get_commit_types(format == CommitFormat::Commitlint); // Select type let type_idx = Select::new() - .with_prompt("Select commit type") + .with_prompt(messages.select_commit_type()) .items(types) .interact()?; let commit_type = types[type_idx].to_string(); // Enter scope (optional) let scope: String = Input::new() - .with_prompt("Scope (optional, press Enter to skip)") + .with_prompt(messages.scope_optional()) .allow_empty(true) .interact_text()?; let scope = if scope.is_empty() { None } else { Some(scope) }; // Enter description let description: String = Input::new() - .with_prompt("Description") + .with_prompt(messages.description()) .interact_text()?; // Breaking change let breaking = Confirm::new() - .with_prompt("Is this a breaking change?") + .with_prompt(messages.breaking_change()) .default(false) .interact()?; // Add body let add_body = Confirm::new() - .with_prompt("Add body to commit?") + .with_prompt(messages.add_body()) .default(false) .interact()?; let body = if add_body { - let body_text = crate::utils::editor::edit_content("Enter commit body...")?; + let body_text = crate::utils::editor::edit_content(messages.enter_commit_body())?; if body_text.trim().is_empty() { None } else { diff --git a/src/commands/config.rs b/src/commands/config.rs index 7219e5c..2003a70 100644 --- a/src/commands/config.rs +++ b/src/commands/config.rs @@ -3,7 +3,7 @@ use clap::{Parser, Subcommand}; use colored::Colorize; use dialoguer::{Confirm, Input, Select}; -use crate::config::manager::ConfigManager; +use crate::config::{Language, manager::ConfigManager}; use crate::config::CommitFormat; /// Mask API key with asterisks for security @@ -146,7 +146,25 @@ enum ConfigSubcommand { /// Path path: String, }, - + + /// Set output language + SetLanguage { + /// Language code (en, zh, ja, ko, es, fr, de) + language: Option, + }, + + /// Set whether to keep commit types in English + SetKeepTypesEnglish { + /// Keep types in English (true/false) + keep: bool, + }, + + /// Set whether to keep changelog types in English + SetKeepChangelogTypesEnglish { + /// Keep types in English (true/false) + keep: bool, + }, + /// Reset configuration to defaults Reset { /// Skip confirmation @@ -196,6 +214,9 @@ impl ConfigCommand { Some(ConfigSubcommand::SetCommitFormat { format }) => self.set_commit_format(format).await, Some(ConfigSubcommand::SetVersionPrefix { prefix }) => self.set_version_prefix(prefix).await, Some(ConfigSubcommand::SetChangelogPath { path }) => self.set_changelog_path(path).await, + Some(ConfigSubcommand::SetLanguage { language }) => self.set_language(language.as_deref()).await, + Some(ConfigSubcommand::SetKeepTypesEnglish { keep }) => self.set_keep_types_english(*keep).await, + Some(ConfigSubcommand::SetKeepChangelogTypesEnglish { keep }) => self.set_keep_changelog_types_english(*keep).await, Some(ConfigSubcommand::Reset { force }) => self.reset(*force).await, Some(ConfigSubcommand::Export { output }) => self.export_config(output.as_deref()).await, Some(ConfigSubcommand::Import { file }) => self.import_config(file).await, @@ -268,6 +289,12 @@ impl ConfigCommand { println!(" GPG sign: {}", if config.tag.gpg_sign { "yes".green() } else { "no".red() }); println!(" Include changelog: {}", if config.tag.include_changelog { "yes".green() } else { "no".red() }); + println!("\n{}", "Language Configuration:".bold()); + let language = manager.get_language().unwrap_or(Language::English); + println!(" Output language: {}", language.display_name().cyan()); + println!(" Keep commit types in English: {}", if manager.keep_types_english() { "yes".green() } else { "no".red() }); + println!(" Keep changelog types in English: {}", if manager.keep_changelog_types_english() { "yes".green() } else { "no".red() }); + println!("\n{}", "Changelog Configuration:".bold()); println!(" Path: {}", config.changelog.path); println!(" Auto-generate: {}", if config.changelog.auto_generate { "yes".green() } else { "no".red() }); @@ -706,6 +733,54 @@ impl ConfigCommand { Ok(()) } + async fn set_language(&self, language: Option<&str>) -> Result<()> { + let mut manager = ConfigManager::new()?; + + let language_code = if let Some(lang) = language { + lang.to_string() + } else { + let languages = vec![ + Language::English, + Language::Chinese, + Language::Japanese, + Language::Korean, + Language::Spanish, + Language::French, + Language::German, + ]; + let language_names: Vec = languages.iter().map(|l| l.display_name().to_string()).collect(); + let idx = Select::new() + .with_prompt("Select language") + .items(&language_names) + .default(0) + .interact()?; + languages[idx].to_code().to_string() + }; + + manager.set_output_language(language_code.clone()); + manager.save()?; + println!("{} Set output language to {}", "✓".green(), language_code.cyan()); + Ok(()) + } + + async fn set_keep_types_english(&self, keep: bool) -> Result<()> { + let mut manager = ConfigManager::new()?; + manager.set_keep_types_english(keep); + manager.save()?; + let status = if keep { "enabled" } else { "disabled" }; + println!("{} Keep commit types in English: {}", "✓".green(), status); + Ok(()) + } + + async fn set_keep_changelog_types_english(&self, keep: bool) -> Result<()> { + let mut manager = ConfigManager::new()?; + manager.set_keep_changelog_types_english(keep); + manager.save()?; + let status = if keep { "enabled" } else { "disabled" }; + println!("{} Keep changelog types in English: {}", "✓".green(), status); + Ok(()) + } + async fn reset(&self, force: bool) -> Result<()> { if !force { let confirm = Confirm::new() diff --git a/src/commands/init.rs b/src/commands/init.rs index f1602e0..1771ba3 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -3,9 +3,10 @@ use clap::Parser; use colored::Colorize; use dialoguer::{Confirm, Input, Select}; -use crate::config::{GitProfile}; +use crate::config::{GitProfile, Language}; use crate::config::manager::ConfigManager; use crate::config::profile::{GpgConfig, SshConfig}; +use crate::i18n::Messages; use crate::utils::validators::validate_email; /// Initialize quicommit configuration @@ -22,7 +23,9 @@ pub struct InitCommand { impl InitCommand { pub async fn execute(&self) -> Result<()> { - println!("{}", "🚀 Initializing QuiCommit...".bold().cyan()); + // Start with English messages for initialization + let messages = Messages::new(Language::English); + println!("{}", messages.initializing().bold().cyan()); let config_path = crate::config::AppConfig::default_path()?; @@ -57,9 +60,13 @@ impl InitCommand { manager.save()?; - println!("{}", "✅ QuiCommit initialized successfully!".bold().green()); - println!("\nConfig file: {}", config_path.display()); - println!("\nNext steps:"); + // Get configured language for final messages + let language = manager.get_language().unwrap_or(Language::English); + let messages = Messages::new(language); + + println!("{}", messages.init_success().bold().green()); + println!("\n{}: {}", messages.config_file(), config_path.display()); + println!("\n{}:", messages.next_steps()); println!(" 1. Create a profile: {}", "quicommit profile add".cyan()); println!(" 2. Configure LLM: {}", "quicommit config set-llm".cyan()); println!(" 3. Start committing: {}", "quicommit commit".cyan()); @@ -90,11 +97,35 @@ impl InitCommand { } async fn interactive_setup(&self, manager: &mut ConfigManager) -> Result<()> { - println!("\n{}", "Let's set up your first profile:".bold()); + let messages = Messages::new(Language::English); + println!("\n{}", messages.setup_profile().bold()); + + // Language selection + println!("\n{}", messages.select_output_language().bold()); + let languages = vec![ + Language::English, + Language::Chinese, + Language::Japanese, + Language::Korean, + Language::Spanish, + Language::French, + Language::German, + ]; + let language_names: Vec = languages.iter().map(|l| l.display_name().to_string()).collect(); + let language_idx = Select::new() + .items(&language_names) + .default(0) + .interact()?; + + let selected_language = languages[language_idx]; + manager.set_output_language(selected_language.to_code().to_string()); + + // Update messages to selected language + let messages = Messages::new(selected_language); // Profile name let profile_name: String = Input::new() - .with_prompt("Profile name") + .with_prompt(messages.profile_name()) .default("personal".to_string()) .interact_text()?; @@ -110,12 +141,12 @@ impl InitCommand { .unwrap_or_default(); let user_name: String = Input::new() - .with_prompt("Git user name") + .with_prompt(messages.git_user_name()) .default(default_name) .interact_text()?; let user_email: String = Input::new() - .with_prompt("Git user email") + .with_prompt(messages.git_user_email()) .default(default_email) .validate_with(|input: &String| { validate_email(input).map_err(|e| e.to_string()) @@ -123,18 +154,18 @@ impl InitCommand { .interact_text()?; let description: String = Input::new() - .with_prompt("Profile description (optional)") + .with_prompt(messages.profile_description()) .allow_empty(true) .interact_text()?; let is_work = Confirm::new() - .with_prompt("Is this a work profile?") + .with_prompt(messages.is_work_profile()) .default(false) .interact()?; let organization = if is_work { Some(Input::new() - .with_prompt("Organization/Company name") + .with_prompt(messages.organization_name()) .interact_text()?) } else { None @@ -142,24 +173,24 @@ impl InitCommand { // SSH configuration let setup_ssh = Confirm::new() - .with_prompt("Configure SSH key?") + .with_prompt(messages.configure_ssh()) .default(false) .interact()?; let ssh_config = if setup_ssh { - Some(self.setup_ssh_interactive().await?) + Some(self.setup_ssh_interactive(&messages).await?) } else { None }; // GPG configuration let setup_gpg = Confirm::new() - .with_prompt("Configure GPG signing?") + .with_prompt(messages.configure_gpg()) .default(false) .interact()?; let gpg_config = if setup_gpg { - Some(self.setup_gpg_interactive().await?) + Some(self.setup_gpg_interactive(&messages).await?) } else { None }; @@ -184,7 +215,7 @@ impl InitCommand { manager.set_default_profile(Some(profile_name))?; // LLM provider selection - println!("\n{}", "Select your preferred LLM provider:".bold()); + println!("\n{}", messages.select_llm_provider().bold()); let providers = vec![ "Ollama (local)", "OpenAI", @@ -213,27 +244,27 @@ impl InitCommand { // Configure API key if needed if provider == "openai" { let api_key: String = Input::new() - .with_prompt("OpenAI API key") + .with_prompt(messages.openai_api_key()) .interact_text()?; manager.set_openai_api_key(api_key); } else if provider == "anthropic" { let api_key: String = Input::new() - .with_prompt("Anthropic API key") + .with_prompt(messages.anthropic_api_key()) .interact_text()?; manager.set_anthropic_api_key(api_key); } else if provider == "kimi" { let api_key: String = Input::new() - .with_prompt("Kimi API key") + .with_prompt(messages.kimi_api_key()) .interact_text()?; manager.set_kimi_api_key(api_key); } else if provider == "deepseek" { let api_key: String = Input::new() - .with_prompt("DeepSeek API key") + .with_prompt(messages.deepseek_api_key()) .interact_text()?; manager.set_deepseek_api_key(api_key); } else if provider == "openrouter" { let api_key: String = Input::new() - .with_prompt("OpenRouter API key") + .with_prompt(messages.openrouter_api_key()) .interact_text()?; manager.set_openrouter_api_key(api_key); } @@ -241,7 +272,7 @@ impl InitCommand { Ok(()) } - async fn setup_ssh_interactive(&self) -> Result { + async fn setup_ssh_interactive(&self, messages: &Messages) -> Result { use std::path::PathBuf; let ssh_dir = dirs::home_dir() @@ -249,17 +280,17 @@ impl InitCommand { .unwrap_or_else(|| PathBuf::from("~/.ssh")); let key_path: String = Input::new() - .with_prompt("SSH private key path") + .with_prompt(messages.ssh_private_key_path()) .default(ssh_dir.join("id_rsa").display().to_string()) .interact_text()?; let has_passphrase = Confirm::new() - .with_prompt("Does this key have a passphrase?") + .with_prompt(messages.has_passphrase()) .default(false) .interact()?; let passphrase = if has_passphrase { - Some(crate::utils::password_input("SSH key passphrase")?) + Some(crate::utils::password_input(messages.ssh_key_passphrase())?) } else { None }; @@ -274,13 +305,13 @@ impl InitCommand { }) } - async fn setup_gpg_interactive(&self) -> Result { + async fn setup_gpg_interactive(&self, messages: &Messages) -> Result { let key_id: String = Input::new() - .with_prompt("GPG key ID") + .with_prompt(messages.gpg_key_id()) .interact_text()?; let use_agent = Confirm::new() - .with_prompt("Use GPG agent?") + .with_prompt(messages.use_gpg_agent()) .default(true) .interact()?; diff --git a/src/commands/tag.rs b/src/commands/tag.rs index d84c64d..8bfbebb 100644 --- a/src/commands/tag.rs +++ b/src/commands/tag.rs @@ -4,12 +4,13 @@ use colored::Colorize; use dialoguer::{Confirm, Input, Select}; use semver::Version; -use crate::config::manager::ConfigManager; +use crate::config::{Language, manager::ConfigManager}; use crate::git::{find_repo, GitRepo}; use crate::generator::ContentGenerator; use crate::git::tag::{ bump_version, get_latest_version, suggest_version_bump, TagBuilder, VersionBump, }; +use crate::i18n::Messages; /// Generate and create Git tags #[derive(Parser)] @@ -64,6 +65,8 @@ impl TagCommand { let repo = find_repo(std::env::current_dir()?.as_path())?; let manager = ConfigManager::new()?; let config = manager.config(); + let language = manager.get_language().unwrap_or(Language::English); + let messages = Messages::new(language); // Determine tag name let tag_name = if let Some(name) = &self.name { @@ -80,7 +83,7 @@ impl TagCommand { format!("{}{}", prefix, new_version) } else { // Interactive mode - self.select_version_interactive(&repo, &config.tag.version_prefix).await? + self.select_version_interactive(&repo, &config.tag.version_prefix, &messages).await? }; // Validate tag name (if it looks like a version) @@ -96,7 +99,7 @@ impl TagCommand { .interact()?; if !proceed { - bail!("Tag creation cancelled"); + bail!("{}", messages.tag_cancelled()); } } } @@ -108,16 +111,16 @@ impl TagCommand { } else if let Some(msg) = &self.message { Some(msg.clone()) } else if self.generate || (config.tag.auto_generate && !self.yes) { - Some(self.generate_tag_message(&repo, &tag_name).await?) + Some(self.generate_tag_message(&repo, &tag_name, &messages).await?) } else if !self.yes { - Some(self.input_message_interactive(&tag_name)?) + Some(self.input_message_interactive(&tag_name, &messages)?) } else { Some(format!("Release {}", tag_name)) }; // Show preview println!("\n{}", "─".repeat(60)); - println!("{}", "Tag preview:".bold()); + println!("{}", messages.tag_preview().bold()); println!("{}", "─".repeat(60)); println!("Name: {}", tag_name.cyan()); if let Some(ref msg) = message { @@ -129,18 +132,18 @@ impl TagCommand { if !self.yes { let confirm = Confirm::new() - .with_prompt("Create this tag?") + .with_prompt(messages.create_tag()) .default(true) .interact()?; if !confirm { - println!("{}", "Tag creation cancelled.".yellow()); + println!("{}", messages.tag_cancelled().yellow()); return Ok(()); } } if self.dry_run { - println!("\n{}", "Dry run - tag not created.".yellow()); + println!("\n{} {}", messages.dry_run(), "- tag not created.".yellow()); return Ok(()); } @@ -153,41 +156,41 @@ impl TagCommand { builder.execute(&repo)?; - println!("{} Created tag {}", "✓".green(), tag_name.cyan()); + println!("{} {}", messages.tag_created().green(), tag_name.cyan()); // Push if requested if self.push { - println!("{} Pushing tag to {}...", "→".blue(), &self.remote); + println!("{}", messages.pushing_tag(&self.remote)); repo.push(&self.remote, &format!("refs/tags/{}", tag_name))?; - println!("{} Pushed tag to {}", "✓".green(), &self.remote); + println!("{}", messages.pushed_tag(&self.remote)); } Ok(()) } - async fn select_version_interactive(&self, repo: &GitRepo, prefix: &str) -> Result { + async fn select_version_interactive(&self, repo: &GitRepo, prefix: &str, messages: &Messages) -> Result { loop { let latest = get_latest_version(repo, prefix)?; - println!("\n{}", "Version selection:".bold()); + println!("\n{}", messages.version_selection().bold()); if let Some(ref version) = latest { - println!("Latest version: {}{}", prefix, version); + println!("{} {}{}", messages.latest_version(), prefix, version); } else { - println!("No existing version tags found"); + println!("{}", messages.no_existing_version_tags()); } let options = vec![ - "Auto-detect bump from commits", - "Bump major version", - "Bump minor version", - "Bump patch version", - "Enter custom version", - "Enter custom tag name", + messages.auto_detect_bump(), + messages.bump_major_version(), + messages.bump_minor_version(), + messages.bump_patch_version(), + messages.enter_custom_version(), + messages.enter_custom_tag_name(), ]; let selection = Select::new() - .with_prompt("Select option") + .with_prompt(messages.select_option()) .items(&options) .default(0) .interact()?; @@ -201,10 +204,10 @@ impl TagCommand { .map(|v| bump_version(v, bump, None)) .unwrap_or_else(|| Version::new(0, 1, 0)); - println!("Suggested bump: {:?} → {}{}", bump, prefix, version); + println!("{} {:?} → {}{}", messages.suggested_bump(), bump, prefix, version); let confirm = Confirm::new() - .with_prompt("Use this version?") + .with_prompt(messages.use_this_version()) .default(true) .interact()?; @@ -233,14 +236,14 @@ impl TagCommand { } 4 => { let input: String = Input::new() - .with_prompt("Enter version (e.g., 1.2.3)") + .with_prompt(messages.enter_version()) .interact_text()?; let version = Version::parse(&input)?; return Ok(format!("{}{}", prefix, version)); } 5 => { let input: String = Input::new() - .with_prompt("Enter tag name") + .with_prompt(messages.enter_tag_name()) .interact_text()?; return Ok(input); } @@ -249,7 +252,7 @@ impl TagCommand { } } - async fn generate_tag_message(&self, repo: &GitRepo, version: &str) -> Result { + async fn generate_tag_message(&self, repo: &GitRepo, version: &str, messages: &Messages) -> Result { let manager = ConfigManager::new()?; let config = manager.config(); @@ -265,17 +268,17 @@ impl TagCommand { return Ok(format!("Release {}", version)); } - println!("{} AI is generating tag message from {} commits...", "🤖", commits.len()); + println!("{}", messages.ai_generating_tag(commits.len())); let generator = ContentGenerator::new(&config.llm).await?; generator.generate_tag_message(version, &commits).await } - fn input_message_interactive(&self, version: &str) -> Result { + fn input_message_interactive(&self, version: &str, messages: &Messages) -> Result { let default_msg = format!("Release {}", version); let use_editor = Confirm::new() - .with_prompt("Open editor for tag message?") + .with_prompt(messages.open_editor()) .default(false) .interact()?; @@ -283,7 +286,7 @@ impl TagCommand { crate::utils::editor::edit_content(&default_msg) } else { Ok(Input::new() - .with_prompt("Tag message") + .with_prompt(messages.tag_message()) .default(default_msg) .interact_text()?) } diff --git a/src/config/manager.rs b/src/config/manager.rs index 7f99400..df8323b 100644 --- a/src/config/manager.rs +++ b/src/config/manager.rs @@ -391,6 +391,46 @@ impl ConfigManager { self.modified = true; } + // Language configuration + + /// Get output language + pub fn output_language(&self) -> &str { + &self.config.language.output_language + } + + /// Set output language + pub fn set_output_language(&mut self, language: String) { + self.config.language.output_language = language; + self.modified = true; + } + + /// Get language enum from config + pub fn get_language(&self) -> Option { + super::Language::from_str(&self.config.language.output_language) + } + + /// Check if commit types should be kept in English + pub fn keep_types_english(&self) -> bool { + self.config.language.keep_types_english + } + + /// Set keep types English flag + pub fn set_keep_types_english(&mut self, keep: bool) { + self.config.language.keep_types_english = keep; + self.modified = true; + } + + /// Check if changelog types should be kept in English + pub fn keep_changelog_types_english(&self) -> bool { + self.config.language.keep_changelog_types_english + } + + /// Set keep changelog types English flag + pub fn set_keep_changelog_types_english(&mut self, keep: bool) { + self.config.language.keep_changelog_types_english = keep; + self.modified = true; + } + /// Export configuration to TOML string pub fn export(&self) -> Result { toml::to_string_pretty(&self.config) diff --git a/src/config/mod.rs b/src/config/mod.rs index 5ee9dad..2be6cc0 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -53,6 +53,10 @@ pub struct AppConfig { /// Theme settings #[serde(default)] pub theme: ThemeConfig, + + /// Language settings + #[serde(default)] + pub language: LanguageConfig, } impl Default for AppConfig { @@ -68,6 +72,7 @@ impl Default for AppConfig { repo_profiles: HashMap::new(), encrypt_sensitive: true, theme: ThemeConfig::default(), + language: LanguageConfig::default(), } } } @@ -497,6 +502,83 @@ impl Default for ThemeConfig { } } +/// Language configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LanguageConfig { + /// Output language for messages (en, zh, etc.) + #[serde(default = "default_output_language")] + pub output_language: String, + + /// Keep commit types in English + #[serde(default = "default_true")] + pub keep_types_english: bool, + + /// Keep changelog types in English + #[serde(default = "default_true")] + pub keep_changelog_types_english: bool, +} + +impl Default for LanguageConfig { + fn default() -> Self { + Self { + output_language: default_output_language(), + keep_types_english: true, + keep_changelog_types_english: true, + } + } +} + +/// Supported languages +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Language { + English, + Chinese, + Japanese, + Korean, + Spanish, + French, + German, +} + +impl Language { + pub fn from_str(s: &str) -> Option { + match s.to_lowercase().as_str() { + "en" | "english" => Some(Language::English), + "zh" | "chinese" | "zh-cn" | "zh-tw" => Some(Language::Chinese), + "ja" | "japanese" => Some(Language::Japanese), + "ko" | "korean" => Some(Language::Korean), + "es" | "spanish" => Some(Language::Spanish), + "fr" | "french" => Some(Language::French), + "de" | "german" => Some(Language::German), + _ => None, + } + } + + pub fn to_code(&self) -> &str { + match self { + Language::English => "en", + Language::Chinese => "zh", + Language::Japanese => "ja", + Language::Korean => "ko", + Language::Spanish => "es", + Language::French => "fr", + Language::German => "de", + } + } + + pub fn display_name(&self) -> &str { + match self { + Language::English => "English", + Language::Chinese => "中文", + Language::Japanese => "日本語", + Language::Korean => "한국어", + Language::Spanish => "Español", + Language::French => "Français", + Language::German => "Deutsch", + } + } +} + // Default value functions fn default_version() -> String { "1".to_string() @@ -594,6 +676,10 @@ fn default_date_format() -> String { "%Y-%m-%d".to_string() } +fn default_output_language() -> String { + "en".to_string() +} + impl AppConfig { /// Load configuration from file pub fn load(path: &Path) -> Result { diff --git a/src/i18n/messages.rs b/src/i18n/messages.rs new file mode 100644 index 0000000..de4e410 --- /dev/null +++ b/src/i18n/messages.rs @@ -0,0 +1,1091 @@ +use crate::config::Language; + +pub struct Messages { + language: Language, +} + +impl Messages { + pub fn new(language: Language) -> Self { + Self { language } + } + + pub fn initializing(&self) -> &str { + match self.language { + Language::English => "🚀 Initializing QuiCommit...", + Language::Chinese => "🚀 正在初始化 QuiCommit...", + Language::Japanese => "🚀 QuiCommitを初期化中...", + Language::Korean => "🚀 QuiCommit 초기화 중...", + Language::Spanish => "🚀 Inicializando QuiCommit...", + Language::French => "🚀 Initialisation de QuiCommit...", + Language::German => "🚀 QuiCommit wird initialisiert...", + } + } + + pub fn init_success(&self) -> &str { + match self.language { + Language::English => "✅ QuiCommit initialized successfully!", + Language::Chinese => "✅ QuiCommit 初始化成功!", + Language::Japanese => "✅ QuiCommitの初期化が完了しました!", + Language::Korean => "✅ QuiCommit 초기화 성공!", + Language::Spanish => "✅ ¡QuiCommit inicializado exitosamente!", + Language::French => "✅ QuiCommit initialisé avec succès !", + Language::German => "✅ QuiCommit erfolgreich initialisiert!", + } + } + + pub fn next_steps(&self) -> &str { + match self.language { + Language::English => "Next steps:", + Language::Chinese => "下一步:", + Language::Japanese => "次のステップ:", + Language::Korean => "다음 단계:", + Language::Spanish => "Próximos pasos:", + Language::French => "Prochaines étapes :", + Language::German => "Nächste Schritte:", + } + } + + pub fn config_file(&self) -> &str { + match self.language { + Language::English => "Config file:", + Language::Chinese => "配置文件:", + Language::Japanese => "設定ファイル:", + Language::Korean => "구성 파일:", + Language::Spanish => "Archivo de configuración:", + Language::French => "Fichier de configuration :", + Language::German => "Konfigurationsdatei:", + } + } + + pub fn setup_profile(&self) -> &str { + match self.language { + Language::English => "Let's set up your first profile:", + Language::Chinese => "让我们设置你的第一个配置文件:", + Language::Japanese => "最初のプロファイルを設定しましょう:", + Language::Korean => "첫 번째 프로필을 설정해 보겠습니다:", + Language::Spanish => "Configuremos tu primer perfil:", + Language::French => "Configurons votre premier profil :", + Language::German => "Lassen Sie uns Ihr erstes Profil einrichten:", + } + } + + pub fn profile_name(&self) -> &str { + match self.language { + Language::English => "Profile name", + Language::Chinese => "配置文件名称", + Language::Japanese => "プロファイル名", + Language::Korean => "프로필 이름", + Language::Spanish => "Nombre del perfil", + Language::French => "Nom du profil", + Language::German => "Profilname", + } + } + + pub fn git_user_name(&self) -> &str { + match self.language { + Language::English => "Git user name", + Language::Chinese => "Git 用户名", + Language::Japanese => "Gitユーザー名", + Language::Korean => "Git 사용자 이름", + Language::Spanish => "Nombre de usuario de Git", + Language::French => "Nom d'utilisateur Git", + Language::German => "Git-Benutzername", + } + } + + pub fn git_user_email(&self) -> &str { + match self.language { + Language::English => "Git user email", + Language::Chinese => "Git 用户邮箱", + Language::Japanese => "Gitユーザーメール", + Language::Korean => "Git 사용자 이메일", + Language::Spanish => "Correo de usuario de Git", + Language::French => "E-mail utilisateur Git", + Language::German => "Git-Benutzer-E-Mail", + } + } + + pub fn select_llm_provider(&self) -> &str { + match self.language { + Language::English => "Select your preferred LLM provider:", + Language::Chinese => "选择你偏好的 LLM 提供商:", + Language::Japanese => "好みのLLMプロバイダーを選択:", + Language::Korean => "선호하는 LLM 공급자를 선택하세요:", + Language::Spanish => "Selecciona tu proveedor LLM preferido:", + Language::French => "Sélectionnez votre fournisseur LLM préféré :", + Language::German => "Wählen Sie Ihren bevorzugten LLM-Anbieter:", + } + } + + pub fn commit_preview(&self) -> &str { + match self.language { + Language::English => "Commit preview:", + Language::Chinese => "提交预览:", + Language::Japanese => "コミットプレビュー:", + Language::Korean => "커밋 미리보기:", + Language::Spanish => "Vista previa del commit:", + Language::French => "Aperçu du commit :", + Language::German => "Commit-Vorschau:", + } + } + + pub fn proceed_commit(&self) -> &str { + match self.language { + Language::English => "Do you want to proceed with this commit?", + Language::Chinese => "是否继续此提交?", + Language::Japanese => "このコミットを続行しますか?", + Language::Korean => "이 커밋을 계속하시겠습니까?", + Language::Spanish => "¿Quieres proceder con este commit?", + Language::French => "Voulez-vous procéder à ce commit ?", + Language::German => "Möchten Sie mit diesem Commit fortfahren?", + } + } + + pub fn commit_created(&self) -> &str { + match self.language { + Language::English => "Created commit", + Language::Chinese => "已创建提交", + Language::Japanese => "コミットを作成しました", + Language::Korean => "커밋 생성됨", + Language::Spanish => "Commit creado", + Language::French => "Commit créé", + Language::German => "Commit erstellt", + } + } + + pub fn commit_amended(&self) -> &str { + match self.language { + Language::English => "Amended commit", + Language::Chinese => "已修改提交", + Language::Japanese => "コミットを修正しました", + Language::Korean => "커밋 수정됨", + Language::Spanish => "Commit enmendado", + Language::French => "Commit modifié", + Language::German => "Commit geändert", + } + } + + pub fn commit_cancelled(&self) -> &str { + match self.language { + Language::English => "Commit cancelled.", + Language::Chinese => "提交已取消。", + Language::Japanese => "コミットがキャンセルされました。", + Language::Korean => "커밋이 취소되었습니다.", + Language::Spanish => "Commit cancelado.", + Language::French => "Commit annulé.", + Language::German => "Commit abgebrochen.", + } + } + + pub fn tag_preview(&self) -> &str { + match self.language { + Language::English => "Tag preview:", + Language::Chinese => "标签预览:", + Language::Japanese => "タグプレビュー:", + Language::Korean => "태그 미리보기:", + Language::Spanish => "Vista previa de la etiqueta:", + Language::French => "Aperçu de l'étiquette :", + Language::German => "Tag-Vorschau:", + } + } + + pub fn create_tag(&self) -> &str { + match self.language { + Language::English => "Create this tag?", + Language::Chinese => "创建此标签?", + Language::Japanese => "このタグを作成しますか?", + Language::Korean => "이 태그를 생성하시겠습니까?", + Language::Spanish => "¿Crear esta etiqueta?", + Language::French => "Créer cette étiquette ?", + Language::German => "Diesen Tag erstellen?", + } + } + + pub fn tag_created(&self) -> &str { + match self.language { + Language::English => "Created tag", + Language::Chinese => "已创建标签", + Language::Japanese => "タグを作成しました", + Language::Korean => "태그 생성됨", + Language::Spanish => "Etiqueta creada", + Language::French => "Étiquette créée", + Language::German => "Tag erstellt", + } + } + + pub fn tag_cancelled(&self) -> &str { + match self.language { + Language::English => "Tag creation cancelled.", + Language::Chinese => "标签创建已取消。", + Language::Japanese => "タグ作成がキャンセルされました。", + Language::Korean => "태그 생성이 취소되었습니다.", + Language::Spanish => "Creación de etiqueta cancelada.", + Language::French => "Création d'étiquette annulée.", + Language::German => "Tag-Erstellung abgebrochen.", + } + } + + pub fn changelog_preview(&self) -> &str { + match self.language { + Language::English => "Changelog preview:", + Language::Chinese => "变更日志预览:", + Language::Japanese => "変更ログプレビュー:", + Language::Korean => "변경 로그 미리보기:", + Language::Spanish => "Vista previa del changelog:", + Language::French => "Aperçu du changelog :", + Language::German => "Changelog-Vorschau:", + } + } + + pub fn changelog_written(&self) -> &str { + match self.language { + Language::English => "Changelog written to", + Language::Chinese => "变更日志已写入", + Language::Japanese => "変更ログを書き込みました", + Language::Korean => "변경 로그가 작성됨", + Language::Spanish => "Changelog escrito en", + Language::French => "Changelog écrit dans", + Language::German => "Changelog geschrieben nach", + } + } + + pub fn dry_run(&self) -> &str { + match self.language { + Language::English => "Dry run", + Language::Chinese => "试运行", + Language::Japanese => "ドライラン", + Language::Korean => "드라이 런", + Language::Spanish => "Ejecución en seco", + Language::French => "Exécution à blanc", + Language::German => "Testlauf", + } + } + + pub fn no_changes(&self) -> &str { + match self.language { + Language::English => "No changes to commit. Working tree is clean.", + Language::Chinese => "没有可提交的更改。工作树是干净的。", + Language::Japanese => "コミットする変更がありません。作業ツリーはクリーンです。", + Language::Korean => "커밋할 변경 사항이 없습니다. 작업 트리가 깨끗합니다.", + Language::Spanish => "No hay cambios para hacer commit. El árbol de trabajo está limpio.", + Language::French => "Aucun changement à commiter. L'arbre de travail est propre.", + Language::German => "Keine Änderungen zum Committen. Arbeitsbaum ist sauber.", + } + } + + pub fn staged_all(&self) -> &str { + match self.language { + Language::English => "Staged all changes", + Language::Chinese => "已暂存所有更改", + Language::Japanese => "すべての変更をステージしました", + Language::Korean => "모든 변경 사항을 스테이징함", + Language::Spanish => "Todos los cambios preparados", + Language::French => "Tous les changements indexés", + Language::German => "Alle Änderungen bereitgestellt", + } + } + + pub fn ai_analyzing(&self) -> &str { + match self.language { + Language::English => "🤖 AI is analyzing your changes...", + Language::Chinese => "🤖 AI 正在分析你的更改...", + Language::Japanese => "🤖 AIが変更を分析しています...", + Language::Korean => "🤖 AI가 변경 사항을 분석 중...", + Language::Spanish => "🤖 La IA está analizando tus cambios...", + Language::French => "🤖 L'IA analyse vos modifications...", + Language::German => "🤖 KI analysiert Ihre Änderungen...", + } + } + + pub fn ai_generating_tag(&self, count: usize) -> String { + match self.language { + Language::English => format!("🤖 AI is generating tag message from {} commits...", count), + Language::Chinese => format!("🤖 AI 正在从 {} 个提交生成标签消息...", count), + Language::Japanese => format!("🤖 AIが{}個のコミットからタグメッセージを生成しています...", count), + Language::Korean => format!("🤖 AI가 {}개의 커밋에서 태그 메시지를 생성 중...", count), + Language::Spanish => format!("🤖 La IA está generando mensaje de etiqueta desde {} commits...", count), + Language::French => format!("🤖 L'IA génère le message d'étiquette à partir de {} commits...", count), + Language::German => format!("🤖 KI generiert Tag-Nachricht aus {} Commits...", count), + } + } + + pub fn ai_generating_changelog(&self) -> &str { + match self.language { + Language::English => "🤖 AI is generating changelog...", + Language::Chinese => "🤖 AI 正在生成变更日志...", + Language::Japanese => "🤖 AIが変更ログを生成しています...", + Language::Korean => "🤖 AI가 변경 로그를 생성 중...", + Language::Spanish => "🤖 La IA está generando el changelog...", + Language::French => "🤖 L'IA génère le changelog...", + Language::German => "🤖 KI generiert Changelog...", + } + } + + pub fn fetching_commits(&self) -> &str { + match self.language { + Language::English => "→ Fetching commits...", + Language::Chinese => "→ 正在获取提交...", + Language::Japanese => "→ コミットを取得中...", + Language::Korean => "→ 커밋 가져오는 중...", + Language::Spanish => "→ Obteniendo commits...", + Language::French => "→ Récupération des commits...", + Language::German => "→ Commits werden abgerufen...", + } + } + + pub fn found_commits(&self, count: usize) -> String { + match self.language { + Language::English => format!("✓ Found {} commits", count), + Language::Chinese => format!("✓ 找到 {} 个提交", count), + Language::Japanese => format!("✓ {}個のコミットが見つかりました", count), + Language::Korean => format!("✓ {}개의 커밋을 찾음", count), + Language::Spanish => format!("✓ Se encontraron {} commits", count), + Language::French => format!("✓ {} commits trouvés", count), + Language::German => format!("✓ {} Commits gefunden", count), + } + } + + pub fn version_selection(&self) -> &str { + match self.language { + Language::English => "Version selection:", + Language::Chinese => "版本选择:", + Language::Japanese => "バージョン選択:", + Language::Korean => "버전 선택:", + Language::Spanish => "Selección de versión:", + Language::French => "Sélection de version :", + Language::German => "Versionsauswahl:", + } + } + + pub fn latest_version(&self) -> &str { + match self.language { + Language::English => "Latest version:", + Language::Chinese => "最新版本:", + Language::Japanese => "最新バージョン:", + Language::Korean => "최신 버전:", + Language::Spanish => "Última versión:", + Language::French => "Dernière version :", + Language::German => "Neueste Version:", + } + } + + pub fn no_existing_version_tags(&self) -> &str { + match self.language { + Language::English => "No existing version tags found", + Language::Chinese => "未找到现有的版本标签", + Language::Japanese => "既存のバージョンタグが見つかりません", + Language::Korean => "기존 버전 태그를 찾을 수 없음", + Language::Spanish => "No se encontraron etiquetas de versión existentes", + Language::French => "Aucune étiquette de version existante trouvée", + Language::German => "Keine vorhandenen Versionstags gefunden", + } + } + + pub fn suggested_bump(&self) -> &str { + match self.language { + Language::English => "Suggested bump:", + Language::Chinese => "建议的版本升级:", + Language::Japanese => "提案されたバンプ:", + Language::Korean => "제안된 버전 증가:", + Language::Spanish => "Incremento sugerido:", + Language::French => "Bump suggéré :", + Language::German => "Vorgeschlagener Bump:", + } + } + + pub fn use_this_version(&self) -> &str { + match self.language { + Language::English => "Use this version?", + Language::Chinese => "使用此版本?", + Language::Japanese => "このバージョンを使用しますか?", + Language::Korean => "이 버전을 사용하시겠습니까?", + Language::Spanish => "¿Usar esta versión?", + Language::French => "Utiliser cette version ?", + Language::German => "Diese Version verwenden?", + } + } + + pub fn pushing_tag(&self, remote: &str) -> String { + match self.language { + Language::English => format!("→ Pushing tag to {}...", remote), + Language::Chinese => format!("→ 正在推送标签到 {}...", remote), + Language::Japanese => format!("→ タグを{}にプッシュ中...", remote), + Language::Korean => format!("→ 태그를 {}로 푸시 중...", remote), + Language::Spanish => format!("→ Enviando etiqueta a {}...", remote), + Language::French => format!("→ Envoi de l'étiquette vers {}...", remote), + Language::German => format!("→ Tag wird an {} gepusht...", remote), + } + } + + pub fn pushed_tag(&self, remote: &str) -> String { + match self.language { + Language::English => format!("✓ Pushed tag to {}", remote), + Language::Chinese => format!("✓ 已推送标签到 {}", remote), + Language::Japanese => format!("✓ タグを{}にプッシュしました", remote), + Language::Korean => format!("✓ 태그를 {}로 푸시함", remote), + Language::Spanish => format!("✓ Etiqueta enviada a {}", remote), + Language::French => format!("✓ Étiquette envoyée à {}", remote), + Language::German => format!("✓ Tag an {} gepusht", remote), + } + } + + pub fn cancelled(&self) -> &str { + match self.language { + Language::English => "Cancelled.", + Language::Chinese => "已取消。", + Language::Japanese => "キャンセルされました。", + Language::Korean => "취소됨.", + Language::Spanish => "Cancelado.", + Language::French => "Annulé.", + Language::German => "Abgebrochen.", + } + } + + pub fn select_commit_type(&self) -> &str { + match self.language { + Language::English => "Select commit type", + Language::Chinese => "选择提交类型", + Language::Japanese => "コミットタイプを選択", + Language::Korean => "커밋 유형 선택", + Language::Spanish => "Seleccionar tipo de commit", + Language::French => "Sélectionner le type de commit", + Language::German => "Commit-Typ auswählen", + } + } + + pub fn scope_optional(&self) -> &str { + match self.language { + Language::English => "Scope (optional, press Enter to skip)", + Language::Chinese => "范围(可选,按 Enter 跳过)", + Language::Japanese => "スコープ(オプション、Enterでスキップ)", + Language::Korean => "범위(선택 사항, Enter로 건너뜀)", + Language::Spanish => "Ámbito (opcional, presiona Enter para omitir)", + Language::French => "Portée (optionnel, appuyez sur Entrée pour ignorer)", + Language::German => "Bereich (optional, Enter zum Überspringen)", + } + } + + pub fn description(&self) -> &str { + match self.language { + Language::English => "Description", + Language::Chinese => "描述", + Language::Japanese => "説明", + Language::Korean => "설명", + Language::Spanish => "Descripción", + Language::French => "Description", + Language::German => "Beschreibung", + } + } + + pub fn breaking_change(&self) -> &str { + match self.language { + Language::English => "Is this a breaking change?", + Language::Chinese => "这是破坏性更改吗?", + Language::Japanese => "これは破壊的変更ですか?", + Language::Korean => "이것은 주요 변경 사항입니까?", + Language::Spanish => "¿Es este un cambio rupturista?", + Language::French => "Est-ce un changement cassant ?", + Language::German => "Ist dies ein Breaking Change?", + } + } + + pub fn add_body(&self) -> &str { + match self.language { + Language::English => "Add body to commit?", + Language::Chinese => "添加提交正文?", + Language::Japanese => "コミットに本文を追加しますか?", + Language::Korean => "커밋에 본문을 추가하시겠습니까?", + Language::Spanish => "¿Añadir cuerpo al commit?", + Language::French => "Ajouter un corps au commit ?", + Language::German => "Body zum Commit hinzufügen?", + } + } + + pub fn enter_commit_body(&self) -> &str { + match self.language { + Language::English => "Enter commit body...", + Language::Chinese => "输入提交正文...", + Language::Japanese => "コミット本文を入力...", + Language::Korean => "커밋 본문 입력...", + Language::Spanish => "Introduce el cuerpo del commit...", + Language::French => "Saisissez le corps du commit...", + Language::German => "Commit-Body eingeben...", + } + } + + pub fn open_editor(&self) -> &str { + match self.language { + Language::English => "Open editor for tag message?", + Language::Chinese => "打开编辑器编辑标签消息?", + Language::Japanese => "タグメッセージ用にエディタを開きますか?", + Language::Korean => "태그 메시지를 위해 편집기를 여시겠습니까?", + Language::Spanish => "¿Abrir editor para mensaje de etiqueta?", + Language::French => "Ouvrir l'éditeur pour le message d'étiquette ?", + Language::German => "Editor für Tag-Nachricht öffnen?", + } + } + + pub fn tag_message(&self) -> &str { + match self.language { + Language::English => "Tag message", + Language::Chinese => "标签消息", + Language::Japanese => "タグメッセージ", + Language::Korean => "태그 메시지", + Language::Spanish => "Mensaje de etiqueta", + Language::French => "Message d'étiquette", + Language::German => "Tag-Nachricht", + } + } + + pub fn version(&self) -> &str { + match self.language { + Language::English => "Version", + Language::Chinese => "版本", + Language::Japanese => "バージョン", + Language::Korean => "버전", + Language::Spanish => "Versión", + Language::French => "Version", + Language::German => "Version", + } + } + + pub fn unreleased(&self) -> &str { + match self.language { + Language::English => "Unreleased", + Language::Chinese => "未发布", + Language::Japanese => "未リリース", + Language::Korean => "릴리스되지 않음", + Language::Spanish => "Sin lanzar", + Language::French => "Non publié", + Language::German => "Unveröffentlicht", + } + } + + pub fn no_commits_found(&self) -> &str { + match self.language { + Language::English => "No commits found in the specified range", + Language::Chinese => "在指定范围内未找到提交", + Language::Japanese => "指定された範囲でコミットが見つかりませんでした", + Language::Korean => "지정된 범위에서 커밋을 찾을 수 없음", + Language::Spanish => "No se encontraron commits en el rango especificado", + Language::French => "Aucun commit trouvé dans la plage spécifiée", + Language::German => "Keine Commits im angegebenen Bereich gefunden", + } + } + + pub fn write_to_file(&self, path: &str) -> String { + match self.language { + Language::English => format!("Write to {:?}?", path), + Language::Chinese => format!("写入 {:?}?", path), + Language::Japanese => format!("{:?} に書き込みますか?", path), + Language::Korean => format!("{:?}에 쓰시겠습니까?", path), + Language::Spanish => format!("¿Escribir en {:?}?", path), + Language::French => format!("Écrire dans {:?} ?", path), + Language::German => format!("In {:?} schreiben?", path), + } + } + + pub fn initialized_changelog(&self, path: &str) -> String { + match self.language { + Language::English => format!("✓ Initialized changelog at {:?}", path), + Language::Chinese => format!("✓ 已在 {:?} 初始化变更日志", path), + Language::Japanese => format!("✓ {:?}で変更ログを初期化しました", path), + Language::Korean => format!("✓ {:?}에서 변경 로그 초기화됨", path), + Language::Spanish => format!("✓ Changelog inicializado en {:?}", path), + Language::French => format!("✓ Changelog initialisé à {:?}", path), + Language::German => format!("✓ Changelog bei {:?} initialisiert", path), + } + } + + pub fn staged_files(&self, count: usize) -> String { + match self.language { + Language::English => format!("Staged files ({}):", count), + Language::Chinese => format!("已暂存文件({}):", count), + Language::Japanese => format!("ステージされたファイル({}):", count), + Language::Korean => format!("스테이징된 파일({}):", count), + Language::Spanish => format!("Archivos preparados ({}):", count), + Language::French => format!("Fichiers indexés ({}):", count), + Language::German => format!("Bereitgestellte Dateien ({}):", count), + } + } + + pub fn generating_commit_message(&self) -> &str { + match self.language { + Language::English => "Generating commit message...", + Language::Chinese => "正在生成提交消息...", + Language::Japanese => "コミットメッセージを生成中...", + Language::Korean => "커밋 메시지 생성 중...", + Language::Spanish => "Generando mensaje de commit...", + Language::French => "Génération du message de commit...", + Language::German => "Commit-Nachricht wird generiert...", + } + } + + pub fn generated_commit_message(&self) -> &str { + match self.language { + Language::English => "Generated commit message:", + Language::Chinese => "生成的提交消息:", + Language::Japanese => "生成されたコミットメッセージ:", + Language::Korean => "생성된 커밋 메시지:", + Language::Spanish => "Mensaje de commit generado:", + Language::French => "Message de commit généré :", + Language::German => "Generierte Commit-Nachricht:", + } + } + + pub fn what_would_you_like_to_do(&self) -> &str { + match self.language { + Language::English => "What would you like to do?", + Language::Chinese => "你想做什么?", + Language::Japanese => "何をしますか?", + Language::Korean => "무엇을 하시겠습니까?", + Language::Spanish => "¿Qué te gustaría hacer?", + Language::French => "Que souhaitez-vous faire ?", + Language::German => "Was möchten Sie tun?", + } + } + + pub fn accept_and_commit(&self) -> &str { + match self.language { + Language::English => "✓ Accept and commit", + Language::Chinese => "✓ 接受并提交", + Language::Japanese => "✓ 受け入れてコミット", + Language::Korean => "✓ 수락하고 커밋", + Language::Spanish => "✓ Aceptar y hacer commit", + Language::French => "✓ Accepter et commiter", + Language::German => "✓ Akzeptieren und committen", + } + } + + pub fn regenerate(&self) -> &str { + match self.language { + Language::English => "🔄 Regenerate", + Language::Chinese => "🔄 重新生成", + Language::Japanese => "🔄 再生成", + Language::Korean => "🔄 재생성", + Language::Spanish => "🔄 Regenerar", + Language::French => "🔄 Régénérer", + Language::German => "🔄 Neu generieren", + } + } + + pub fn edit(&self) -> &str { + match self.language { + Language::English => "✏️ Edit", + Language::Chinese => "✏️ 编辑", + Language::Japanese => "✏️ 編集", + Language::Korean => "✏️ 편집", + Language::Spanish => "✏️ Editar", + Language::French => "✏️ Modifier", + Language::German => "✏️ Bearbeiten", + } + } + + pub fn copy_to_clipboard(&self) -> &str { + match self.language { + Language::English => "📋 Copy to clipboard", + Language::Chinese => "📋 复制到剪贴板", + Language::Japanese => "📋 クリップボードにコピー", + Language::Korean => "📋 클립보드에 복사", + Language::Spanish => "📋 Copiar al portapapeles", + Language::French => "📋 Copier dans le presse-papiers", + Language::German => "📋 In die Zwischenablage kopieren", + } + } + + pub fn cancel(&self) -> &str { + match self.language { + Language::English => "❌ Cancel", + Language::Chinese => "❌ 取消", + Language::Japanese => "❌ キャンセル", + Language::Korean => "❌ 취소", + Language::Spanish => "❌ Cancelar", + Language::French => "❌ Annuler", + Language::German => "❌ Abbrechen", + } + } + + pub fn regenerating(&self) -> &str { + match self.language { + Language::English => "Regenerating...", + Language::Chinese => "正在重新生成...", + Language::Japanese => "再生成中...", + Language::Korean => "재생성 중...", + Language::Spanish => "Regenerando...", + Language::French => "Régénération...", + Language::German => "Neu generieren...", + } + } + + pub fn copied_to_clipboard(&self) -> &str { + match self.language { + Language::English => "Copied to clipboard!", + Language::Chinese => "已复制到剪贴板!", + Language::Japanese => "クリップボードにコピーしました!", + Language::Korean => "클립보드에 복사됨!", + Language::Spanish => "¡Copiado al portapapeles!", + Language::French => "Copié dans le presse-papiers !", + Language::German => "In die Zwischenablage kopiert!", + } + } + + pub fn cancelled_by_user(&self) -> &str { + match self.language { + Language::English => "Cancelled by user", + Language::Chinese => "用户已取消", + Language::Japanese => "ユーザーによってキャンセルされました", + Language::Korean => "사용자가 취소함", + Language::Spanish => "Cancelado por el usuario", + Language::French => "Annulé par l'utilisateur", + Language::German => "Vom Benutzer abgebrochen", + } + } + + pub fn select_option(&self) -> &str { + match self.language { + Language::English => "Select option", + Language::Chinese => "选择选项", + Language::Japanese => "オプションを選択", + Language::Korean => "옵션 선택", + Language::Spanish => "Seleccionar opción", + Language::French => "Sélectionner une option", + Language::German => "Option auswählen", + } + } + + pub fn auto_detect_bump(&self) -> &str { + match self.language { + Language::English => "Auto-detect bump from commits", + Language::Chinese => "从提交自动检测版本升级", + Language::Japanese => "コミットからバンプを自動検出", + Language::Korean => "커밋에서 버전 증가 자동 감지", + Language::Spanish => "Detectar incremento automáticamente desde commits", + Language::French => "Détecter automatiquement le bump depuis les commits", + Language::German => "Bump automatisch aus Commits erkennen", + } + } + + pub fn bump_major_version(&self) -> &str { + match self.language { + Language::English => "Bump major version", + Language::Chinese => "升级主版本", + Language::Japanese => "メジャーバージョンを上げる", + Language::Korean => "메이저 버전 증가", + Language::Spanish => "Incrementar versión mayor", + Language::French => "Incrémenter version majeure", + Language::German => "Major-Version erhöhen", + } + } + + pub fn bump_minor_version(&self) -> &str { + match self.language { + Language::English => "Bump minor version", + Language::Chinese => "升级次版本", + Language::Japanese => "マイナーバージョンを上げる", + Language::Korean => "마이너 버전 증가", + Language::Spanish => "Incrementar versión menor", + Language::French => "Incrémenter version mineure", + Language::German => "Minor-Version erhöhen", + } + } + + pub fn bump_patch_version(&self) -> &str { + match self.language { + Language::English => "Bump patch version", + Language::Chinese => "升级补丁版本", + Language::Japanese => "パッチバージョンを上げる", + Language::Korean => "패치 버전 증가", + Language::Spanish => "Incrementar versión de parche", + Language::French => "Incrémenter version de correctif", + Language::German => "Patch-Version erhöhen", + } + } + + pub fn enter_custom_version(&self) -> &str { + match self.language { + Language::English => "Enter custom version", + Language::Chinese => "输入自定义版本", + Language::Japanese => "カスタムバージョンを入力", + Language::Korean => "사용자 지정 버전 입력", + Language::Spanish => "Ingresar versión personalizada", + Language::French => "Saisir une version personnalisée", + Language::German => "Benutzerdefinierte Version eingeben", + } + } + + pub fn enter_custom_tag_name(&self) -> &str { + match self.language { + Language::English => "Enter custom tag name", + Language::Chinese => "输入自定义标签名称", + Language::Japanese => "カスタムタグ名を入力", + Language::Korean => "사용자 지정 태그 이름 입력", + Language::Spanish => "Ingresar nombre de etiqueta personalizado", + Language::French => "Saisir un nom d'étiquette personnalisé", + Language::German => "Benutzerdefinierten Tag-Namen eingeben", + } + } + + pub fn enter_version(&self) -> &str { + match self.language { + Language::English => "Enter version (e.g., 1.2.3)", + Language::Chinese => "输入版本(例如:1.2.3)", + Language::Japanese => "バージョンを入力(例:1.2.3)", + Language::Korean => "버전 입력(예: 1.2.3)", + Language::Spanish => "Ingresar versión (ej: 1.2.3)", + Language::French => "Saisir la version (ex: 1.2.3)", + Language::German => "Version eingeben (z.B. 1.2.3)", + } + } + + pub fn enter_tag_name(&self) -> &str { + match self.language { + Language::English => "Enter tag name", + Language::Chinese => "输入标签名称", + Language::Japanese => "タグ名を入力", + Language::Korean => "태그 이름 입력", + Language::Spanish => "Ingresar nombre de etiqueta", + Language::French => "Saisir le nom de l'étiquette", + Language::German => "Tag-Namen eingeben", + } + } + + pub fn profile_description(&self) -> &str { + match self.language { + Language::English => "Profile description (optional)", + Language::Chinese => "配置文件描述(可选)", + Language::Japanese => "プロファイルの説明(オプション)", + Language::Korean => "프로필 설명(선택 사항)", + Language::Spanish => "Descripción del perfil (opcional)", + Language::French => "Description du profil (optionnel)", + Language::German => "Profilbeschreibung (optional)", + } + } + + pub fn is_work_profile(&self) -> &str { + match self.language { + Language::English => "Is this a work profile?", + Language::Chinese => "这是工作配置文件吗?", + Language::Japanese => "これはワークプロファイルですか?", + Language::Korean => "이것은 작업 프로필입니까?", + Language::Spanish => "¿Es este un perfil de trabajo?", + Language::French => "Est-ce un profil de travail ?", + Language::German => "Ist dies ein Arbeitsprofil?", + } + } + + pub fn organization_name(&self) -> &str { + match self.language { + Language::English => "Organization/Company name", + Language::Chinese => "组织/公司名称", + Language::Japanese => "組織/会社名", + Language::Korean => "조직/회사 이름", + Language::Spanish => "Nombre de organización/empresa", + Language::French => "Nom de l'organisation/entreprise", + Language::German => "Organisations-/Unternehmensname", + } + } + + pub fn configure_ssh(&self) -> &str { + match self.language { + Language::English => "Configure SSH key?", + Language::Chinese => "配置 SSH 密钥?", + Language::Japanese => "SSHキーを設定しますか?", + Language::Korean => "SSH 키를 구성하시겠습니까?", + Language::Spanish => "¿Configurar clave SSH?", + Language::French => "Configurer la clé SSH ?", + Language::German => "SSH-Schlüssel konfigurieren?", + } + } + + pub fn configure_gpg(&self) -> &str { + match self.language { + Language::English => "Configure GPG signing?", + Language::Chinese => "配置 GPG 签名?", + Language::Japanese => "GPG署名を設定しますか?", + Language::Korean => "GPG 서명을 구성하시겠습니까?", + Language::Spanish => "¿Configurar firma GPG?", + Language::French => "Configurer la signature GPG ?", + Language::German => "GPG-Signatur konfigurieren?", + } + } + + pub fn ssh_private_key_path(&self) -> &str { + match self.language { + Language::English => "SSH private key path", + Language::Chinese => "SSH 私钥路径", + Language::Japanese => "SSH秘密鍵パス", + Language::Korean => "SSH 개인 키 경로", + Language::Spanish => "Ruta de clave privada SSH", + Language::French => "Chemin de la clé privée SSH", + Language::German => "Pfad zum privaten SSH-Schlüssel", + } + } + + pub fn has_passphrase(&self) -> &str { + match self.language { + Language::English => "Does this key have a passphrase?", + Language::Chinese => "此密钥有密码吗?", + Language::Japanese => "この鍵にパスフレーズがありますか?", + Language::Korean => "이 키에 암호가 있습니까?", + Language::Spanish => "¿Esta clave tiene frase de paso?", + Language::French => "Cette clé a-t-elle une phrase de passe ?", + Language::German => "Hat dieser Schlüssel eine Passphrase?", + } + } + + pub fn ssh_key_passphrase(&self) -> &str { + match self.language { + Language::English => "SSH key passphrase", + Language::Chinese => "SSH 密钥密码", + Language::Japanese => "SSHキーパスフレーズ", + Language::Korean => "SSH 키 암호", + Language::Spanish => "Frase de paso de clave SSH", + Language::French => "Phrase de passe de clé SSH", + Language::German => "SSH-Schlüssel-Passphrase", + } + } + + pub fn gpg_key_id(&self) -> &str { + match self.language { + Language::English => "GPG key ID", + Language::Chinese => "GPG 密钥 ID", + Language::Japanese => "GPGキーID", + Language::Korean => "GPG 키 ID", + Language::Spanish => "ID de clave GPG", + Language::French => "ID de clé GPG", + Language::German => "GPG-Schlüssel-ID", + } + } + + pub fn use_gpg_agent(&self) -> &str { + match self.language { + Language::English => "Use GPG agent?", + Language::Chinese => "使用 GPG 代理?", + Language::Japanese => "GPGエージェントを使用しますか?", + Language::Korean => "GPG 에이전트를 사용하시겠습니까?", + Language::Spanish => "¿Usar agente GPG?", + Language::French => "Utiliser l'agent GPG ?", + Language::German => "GPG-Agent verwenden?", + } + } + + pub fn openai_api_key(&self) -> &str { + match self.language { + Language::English => "OpenAI API key", + Language::Chinese => "OpenAI API 密钥", + Language::Japanese => "OpenAI APIキー", + Language::Korean => "OpenAI API 키", + Language::Spanish => "Clave API de OpenAI", + Language::French => "Clé API OpenAI", + Language::German => "OpenAI-API-Schlüssel", + } + } + + pub fn anthropic_api_key(&self) -> &str { + match self.language { + Language::English => "Anthropic API key", + Language::Chinese => "Anthropic API 密钥", + Language::Japanese => "Anthropic APIキー", + Language::Korean => "Anthropic API 키", + Language::Spanish => "Clave API de Anthropic", + Language::French => "Clé API Anthropic", + Language::German => "Anthropic-API-Schlüssel", + } + } + + pub fn kimi_api_key(&self) -> &str { + match self.language { + Language::English => "Kimi API key", + Language::Chinese => "Kimi API 密钥", + Language::Japanese => "Kimi APIキー", + Language::Korean => "Kimi API 키", + Language::Spanish => "Clave API de Kimi", + Language::French => "Clé API Kimi", + Language::German => "Kimi-API-Schlüssel", + } + } + + pub fn deepseek_api_key(&self) -> &str { + match self.language { + Language::English => "DeepSeek API key", + Language::Chinese => "DeepSeek API 密钥", + Language::Japanese => "DeepSeek APIキー", + Language::Korean => "DeepSeek API 키", + Language::Spanish => "Clave API de DeepSeek", + Language::French => "Clé API DeepSeek", + Language::German => "DeepSeek-API-Schlüssel", + } + } + + pub fn openrouter_api_key(&self) -> &str { + match self.language { + Language::English => "OpenRouter API key", + Language::Chinese => "OpenRouter API 密钥", + Language::Japanese => "OpenRouter APIキー", + Language::Korean => "OpenRouter API 키", + Language::Spanish => "Clave API de OpenRouter", + Language::French => "Clé API OpenRouter", + Language::German => "OpenRouter-API-Schlüssel", + } + } + + pub fn model(&self) -> &str { + match self.language { + Language::English => "Model", + Language::Chinese => "模型", + Language::Japanese => "モデル", + Language::Korean => "모델", + Language::Spanish => "Modelo", + Language::French => "Modèle", + Language::German => "Modell", + } + } + + pub fn base_url(&self) -> &str { + match self.language { + Language::English => "Base URL (optional)", + Language::Chinese => "基础 URL(可选)", + Language::Japanese => "ベースURL(オプション)", + Language::Korean => "기본 URL(선택 사항)", + Language::Spanish => "URL base (opcional)", + Language::French => "URL de base (optionnel)", + Language::German => "Basis-URL (optional)", + } + } + + pub fn configuration_updated(&self) -> &str { + match self.language { + Language::English => "✓ Configuration updated", + Language::Chinese => "✓ 配置已更新", + Language::Japanese => "✓ 設定を更新しました", + Language::Korean => "✓ 구성이 업데이트됨", + Language::Spanish => "✓ Configuración actualizada", + Language::French => "✓ Configuration mise à jour", + Language::German => "✓ Konfiguration aktualisiert", + } + } + + pub fn set_value(&self, key: &str, value: &str) -> String { + match self.language { + Language::English => format!("✓ Set {} = {}", key, value), + Language::Chinese => format!("✓ 设置 {} = {}", key, value), + Language::Japanese => format!("✓ {} = {} を設定", key, value), + Language::Korean => format!("✓ {} = {} 설정됨", key, value), + Language::Spanish => format!("✓ Establecer {} = {}", key, value), + Language::French => format!("✓ Définir {} = {}", key, value), + Language::German => format!("✓ {} = {} festlegen", key, value), + } + } + + pub fn select_output_language(&self) -> &str { + match self.language { + Language::English => "Select output language:", + Language::Chinese => "选择输出语言:", + Language::Japanese => "出力言語を選択:", + Language::Korean => "출력 언어 선택:", + Language::Spanish => "Seleccionar idioma de salida:", + Language::French => "Sélectionner la langue de sortie :", + Language::German => "Ausgabesprache auswählen:", + } + } +} diff --git a/src/i18n/mod.rs b/src/i18n/mod.rs new file mode 100644 index 0000000..4b07f2c --- /dev/null +++ b/src/i18n/mod.rs @@ -0,0 +1,7 @@ +pub mod messages; +pub mod translator; + +pub use messages::Messages; +pub use translator::Translator; +pub use translator::translate_commit_type; +pub use translator::translate_changelog_category; diff --git a/src/i18n/translator.rs b/src/i18n/translator.rs new file mode 100644 index 0000000..ed1c097 --- /dev/null +++ b/src/i18n/translator.rs @@ -0,0 +1,233 @@ +use crate::config::Language; + +pub struct Translator { + language: Language, + keep_types_english: bool, + keep_changelog_types_english: bool, +} + +impl Translator { + pub fn new(language: Language, keep_types_english: bool, keep_changelog_types_english: bool) -> Self { + Self { + language, + keep_types_english, + keep_changelog_types_english, + } + } + + pub fn translate_commit_type(&self, commit_type: &str) -> String { + if self.keep_types_english { + return commit_type.to_string(); + } + + match self.language { + Language::English => commit_type.to_string(), + Language::Chinese => self.translate_commit_type_zh(commit_type), + Language::Japanese => self.translate_commit_type_ja(commit_type), + Language::Korean => self.translate_commit_type_ko(commit_type), + Language::Spanish => self.translate_commit_type_es(commit_type), + Language::French => self.translate_commit_type_fr(commit_type), + Language::German => self.translate_commit_type_de(commit_type), + } + } + + pub fn translate_changelog_category(&self, category: &str) -> String { + if self.keep_changelog_types_english { + return category.to_string(); + } + + match self.language { + Language::English => category.to_string(), + Language::Chinese => self.translate_changelog_category_zh(category), + Language::Japanese => self.translate_changelog_category_ja(category), + Language::Korean => self.translate_changelog_category_ko(category), + Language::Spanish => self.translate_changelog_category_es(category), + Language::French => self.translate_changelog_category_fr(category), + Language::German => self.translate_changelog_category_de(category), + } + } + + fn translate_commit_type_zh(&self, commit_type: &str) -> String { + match commit_type { + "feat" => "新功能".to_string(), + "fix" => "修复".to_string(), + "docs" => "文档".to_string(), + "style" => "样式".to_string(), + "refactor" => "重构".to_string(), + "perf" => "性能".to_string(), + "test" => "测试".to_string(), + "build" => "构建".to_string(), + "ci" => "CI".to_string(), + "chore" => "杂项".to_string(), + "revert" => "回滚".to_string(), + _ => commit_type.to_string(), + } + } + + fn translate_commit_type_ja(&self, commit_type: &str) -> String { + match commit_type { + "feat" => "機能".to_string(), + "fix" => "修正".to_string(), + "docs" => "ドキュメント".to_string(), + "style" => "スタイル".to_string(), + "refactor" => "リファクタリング".to_string(), + "perf" => "パフォーマンス".to_string(), + "test" => "テスト".to_string(), + "build" => "ビルド".to_string(), + "ci" => "CI".to_string(), + "chore" => "雑務".to_string(), + "revert" => "取り消し".to_string(), + _ => commit_type.to_string(), + } + } + + fn translate_commit_type_ko(&self, commit_type: &str) -> String { + match commit_type { + "feat" => "기능".to_string(), + "fix" => "버그 수정".to_string(), + "docs" => "문서".to_string(), + "style" => "스타일".to_string(), + "refactor" => "리팩토링".to_string(), + "perf" => "성능".to_string(), + "test" => "테스트".to_string(), + "build" => "빌드".to_string(), + "ci" => "CI".to_string(), + "chore" => "기타".to_string(), + "revert" => "되돌리기".to_string(), + _ => commit_type.to_string(), + } + } + + fn translate_commit_type_es(&self, commit_type: &str) -> String { + match commit_type { + "feat" => "nueva función".to_string(), + "fix" => "corrección".to_string(), + "docs" => "documentación".to_string(), + "style" => "estilo".to_string(), + "refactor" => "refactorización".to_string(), + "perf" => "rendimiento".to_string(), + "test" => "pruebas".to_string(), + "build" => "construcción".to_string(), + "ci" => "CI".to_string(), + "chore" => "tareas".to_string(), + "revert" => "revertir".to_string(), + _ => commit_type.to_string(), + } + } + + fn translate_commit_type_fr(&self, commit_type: &str) -> String { + match commit_type { + "feat" => "nouvelle fonctionnalité".to_string(), + "fix" => "correction".to_string(), + "docs" => "documentation".to_string(), + "style" => "style".to_string(), + "refactor" => "refactorisation".to_string(), + "perf" => "performance".to_string(), + "test" => "tests".to_string(), + "build" => "construction".to_string(), + "ci" => "CI".to_string(), + "chore" => "tâches".to_string(), + "revert" => "rétablir".to_string(), + _ => commit_type.to_string(), + } + } + + fn translate_commit_type_de(&self, commit_type: &str) -> String { + match commit_type { + "feat" => "Neue Funktion".to_string(), + "fix" => "Korrektur".to_string(), + "docs" => "Dokumentation".to_string(), + "style" => "Stil".to_string(), + "refactor" => "Refactoring".to_string(), + "perf" => "Leistung".to_string(), + "test" => "Tests".to_string(), + "build" => "Build".to_string(), + "ci" => "CI".to_string(), + "chore" => "Wartung".to_string(), + "revert" => "Zurücksetzen".to_string(), + _ => commit_type.to_string(), + } + } + + fn translate_changelog_category_zh(&self, category: &str) -> String { + match category.to_lowercase().as_str() { + "added" => "新增".to_string(), + "changed" => "更改".to_string(), + "deprecated" => "弃用".to_string(), + "removed" => "移除".to_string(), + "fixed" => "修复".to_string(), + "security" => "安全".to_string(), + _ => category.to_string(), + } + } + + fn translate_changelog_category_ja(&self, category: &str) -> String { + match category.to_lowercase().as_str() { + "added" => "追加".to_string(), + "changed" => "変更".to_string(), + "deprecated" => "非推奨".to_string(), + "removed" => "削除".to_string(), + "fixed" => "修正".to_string(), + "security" => "セキュリティ".to_string(), + _ => category.to_string(), + } + } + + fn translate_changelog_category_ko(&self, category: &str) -> String { + match category.to_lowercase().as_str() { + "added" => "추가됨".to_string(), + "changed" => "변경됨".to_string(), + "deprecated" => "사용 중단".to_string(), + "removed" => "제거됨".to_string(), + "fixed" => "수정됨".to_string(), + "security" => "보안".to_string(), + _ => category.to_string(), + } + } + + fn translate_changelog_category_es(&self, category: &str) -> String { + match category.to_lowercase().as_str() { + "added" => "Agregado".to_string(), + "changed" => "Cambiado".to_string(), + "deprecated" => "Obsoleto".to_string(), + "removed" => "Eliminado".to_string(), + "fixed" => "Corregido".to_string(), + "security" => "Seguridad".to_string(), + _ => category.to_string(), + } + } + + fn translate_changelog_category_fr(&self, category: &str) -> String { + match category.to_lowercase().as_str() { + "added" => "Ajouté".to_string(), + "changed" => "Modifié".to_string(), + "deprecated" => "Obsolète".to_string(), + "removed" => "Supprimé".to_string(), + "fixed" => "Corrigé".to_string(), + "security" => "Sécurité".to_string(), + _ => category.to_string(), + } + } + + fn translate_changelog_category_de(&self, category: &str) -> String { + match category.to_lowercase().as_str() { + "added" => "Hinzugefügt".to_string(), + "changed" => "Geändert".to_string(), + "deprecated" => "Veraltet".to_string(), + "removed" => "Entfernt".to_string(), + "fixed" => "Behoben".to_string(), + "security" => "Sicherheit".to_string(), + _ => category.to_string(), + } + } +} + +pub fn translate_commit_type(commit_type: &str, language: Language, keep_english: bool) -> String { + let translator = Translator::new(language, keep_english, true); + translator.translate_commit_type(commit_type) +} + +pub fn translate_changelog_category(category: &str, language: Language, keep_english: bool) -> String { + let translator = Translator::new(language, true, keep_english); + translator.translate_changelog_category(category) +} diff --git a/src/main.rs b/src/main.rs index a66a453..bf6881c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod commands; mod config; mod generator; mod git; +mod i18n; mod llm; mod utils;