feat: feat: add multilingual output support for commit, tag, and changelog commands

This commit is contained in:
2026-02-01 12:06:12 +00:00
parent c3cd01dbcd
commit 0cbd975748
11 changed files with 1710 additions and 105 deletions

View File

@@ -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<String> {
async fn generate_commit(&self, repo: &GitRepo, format: CommitFormat, messages: &Messages) -> Result<String> {
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<String> {
async fn create_interactive_commit(&self, format: CommitFormat, messages: &Messages) -> Result<String> {
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 {