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

@@ -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<String> {
async fn select_version_interactive(&self, repo: &GitRepo, prefix: &str, messages: &Messages) -> Result<String> {
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<String> {
async fn generate_tag_message(&self, repo: &GitRepo, version: &str, messages: &Messages) -> Result<String> {
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<String> {
fn input_message_interactive(&self, version: &str, messages: &Messages) -> Result<String> {
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()?)
}