feat: feat: add multilingual output support for commit, tag, and changelog commands
This commit is contained in:
@@ -5,10 +5,11 @@ use colored::Colorize;
|
|||||||
use dialoguer::{Confirm, Input};
|
use dialoguer::{Confirm, Input};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::config::manager::ConfigManager;
|
use crate::config::{Language, manager::ConfigManager};
|
||||||
use crate::generator::ContentGenerator;
|
use crate::generator::ContentGenerator;
|
||||||
use crate::git::find_repo;
|
use crate::git::find_repo;
|
||||||
use crate::git::{changelog::*, CommitInfo, GitRepo};
|
use crate::git::{changelog::*, CommitInfo, GitRepo};
|
||||||
|
use crate::i18n::{Messages, translate_changelog_category};
|
||||||
|
|
||||||
/// Generate changelog
|
/// Generate changelog
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@@ -67,6 +68,8 @@ impl ChangelogCommand {
|
|||||||
let repo = find_repo(std::env::current_dir()?.as_path())?;
|
let repo = find_repo(std::env::current_dir()?.as_path())?;
|
||||||
let manager = ConfigManager::new()?;
|
let manager = ConfigManager::new()?;
|
||||||
let config = manager.config();
|
let config = manager.config();
|
||||||
|
let language = manager.get_language().unwrap_or(Language::English);
|
||||||
|
let messages = Messages::new(language);
|
||||||
|
|
||||||
// Initialize changelog if requested
|
// Initialize changelog if requested
|
||||||
if self.init {
|
if self.init {
|
||||||
@@ -75,7 +78,7 @@ impl ChangelogCommand {
|
|||||||
.unwrap_or_else(|| PathBuf::from(&config.changelog.path));
|
.unwrap_or_else(|| PathBuf::from(&config.changelog.path));
|
||||||
|
|
||||||
init_changelog(&path)?;
|
init_changelog(&path)?;
|
||||||
println!("{} Initialized changelog at {:?}", "✓".green(), path);
|
println!("{}", messages.initialized_changelog(&format!("{:?}", path)));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,28 +101,28 @@ impl ChangelogCommand {
|
|||||||
v.clone()
|
v.clone()
|
||||||
} else if !self.yes {
|
} else if !self.yes {
|
||||||
Input::new()
|
Input::new()
|
||||||
.with_prompt("Version")
|
.with_prompt(messages.version())
|
||||||
.default("Unreleased".to_string())
|
.default(messages.unreleased().to_string())
|
||||||
.interact_text()?
|
.interact_text()?
|
||||||
} else {
|
} else {
|
||||||
"Unreleased".to_string()
|
messages.unreleased().to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get commits
|
// Get commits
|
||||||
println!("{} Fetching commits...", "→".blue());
|
println!("{}", messages.fetching_commits());
|
||||||
let commits = generate_from_history(&repo, self.from.as_deref(), Some(&self.to))?;
|
let commits = generate_from_history(&repo, self.from.as_deref(), Some(&self.to))?;
|
||||||
|
|
||||||
if commits.is_empty() {
|
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
|
// Generate changelog
|
||||||
let changelog = if self.generate || (config.changelog.auto_generate && !self.yes) {
|
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 {
|
} else {
|
||||||
self.generate_with_template(format, &version, &commits)?
|
self.generate_with_template(format, &version, &commits, language)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Output or write
|
// Output or write
|
||||||
@@ -133,7 +136,7 @@ impl ChangelogCommand {
|
|||||||
// Preview
|
// Preview
|
||||||
if !self.yes {
|
if !self.yes {
|
||||||
println!("\n{}", "─".repeat(60));
|
println!("\n{}", "─".repeat(60));
|
||||||
println!("{}", "Changelog preview:".bold());
|
println!("{}", messages.changelog_preview().bold());
|
||||||
println!("{}", "─".repeat(60));
|
println!("{}", "─".repeat(60));
|
||||||
// Show first 20 lines
|
// Show first 20 lines
|
||||||
let preview: String = changelog.lines().take(20).collect::<Vec<_>>().join("\n");
|
let preview: String = changelog.lines().take(20).collect::<Vec<_>>().join("\n");
|
||||||
@@ -144,12 +147,12 @@ impl ChangelogCommand {
|
|||||||
println!("{}", "─".repeat(60));
|
println!("{}", "─".repeat(60));
|
||||||
|
|
||||||
let confirm = Confirm::new()
|
let confirm = Confirm::new()
|
||||||
.with_prompt(&format!("Write to {:?}?", output_path))
|
.with_prompt(&messages.write_to_file(&format!("{:?}", output_path)))
|
||||||
.default(true)
|
.default(true)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
if !confirm {
|
if !confirm {
|
||||||
println!("{}", "Cancelled.".yellow());
|
println!("{}", messages.cancelled().yellow());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,7 +166,7 @@ impl ChangelogCommand {
|
|||||||
std::fs::write(&output_path, changelog)?;
|
std::fs::write(&output_path, changelog)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("{} Changelog written to {:?}", "✓".green(), output_path);
|
println!("{} {:?}", messages.changelog_written(), output_path);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -173,11 +176,12 @@ impl ChangelogCommand {
|
|||||||
repo: &GitRepo,
|
repo: &GitRepo,
|
||||||
version: &str,
|
version: &str,
|
||||||
commits: &[CommitInfo],
|
commits: &[CommitInfo],
|
||||||
|
messages: &Messages,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let manager = ConfigManager::new()?;
|
let manager = ConfigManager::new()?;
|
||||||
let config = manager.config();
|
let config = manager.config();
|
||||||
|
|
||||||
println!("{} AI is generating changelog...", "🤖");
|
println!("{}", messages.ai_generating_changelog());
|
||||||
|
|
||||||
let generator = ContentGenerator::new(&config.llm).await?;
|
let generator = ContentGenerator::new(&config.llm).await?;
|
||||||
generator.generate_changelog_entry(version, commits).await
|
generator.generate_changelog_entry(version, commits).await
|
||||||
@@ -188,12 +192,43 @@ impl ChangelogCommand {
|
|||||||
format: ChangelogFormat,
|
format: ChangelogFormat,
|
||||||
version: &str,
|
version: &str,
|
||||||
commits: &[CommitInfo],
|
commits: &[CommitInfo],
|
||||||
|
language: Language,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
|
let manager = ConfigManager::new()?;
|
||||||
|
|
||||||
let generator = ChangelogGenerator::new()
|
let generator = ChangelogGenerator::new()
|
||||||
.format(format)
|
.format(format)
|
||||||
.include_hashes(self.include_hashes)
|
.include_hashes(self.include_hashes)
|
||||||
.include_authors(self.include_authors);
|
.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::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
translated
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ use clap::Parser;
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use dialoguer::{Confirm, Input, Select};
|
use dialoguer::{Confirm, Input, Select};
|
||||||
|
|
||||||
use crate::config::manager::ConfigManager;
|
use crate::config::{Language, manager::ConfigManager};
|
||||||
use crate::config::CommitFormat;
|
use crate::config::CommitFormat;
|
||||||
use crate::generator::ContentGenerator;
|
use crate::generator::ContentGenerator;
|
||||||
use crate::git::{find_repo, GitRepo};
|
use crate::git::{find_repo, GitRepo};
|
||||||
use crate::git::commit::{CommitBuilder, create_date_commit_message};
|
use crate::git::commit::{CommitBuilder, create_date_commit_message};
|
||||||
|
use crate::i18n::{Messages, translate_commit_type};
|
||||||
use crate::utils::validators::get_commit_types;
|
use crate::utils::validators::get_commit_types;
|
||||||
|
|
||||||
/// Generate and execute conventional commits
|
/// Generate and execute conventional commits
|
||||||
@@ -79,15 +80,17 @@ impl CommitCommand {
|
|||||||
// Find git repository
|
// Find git repository
|
||||||
let repo = find_repo(std::env::current_dir()?.as_path())?;
|
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
|
// Load configuration
|
||||||
let manager = ConfigManager::new()?;
|
let manager = ConfigManager::new()?;
|
||||||
let config = manager.config();
|
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
|
// Determine commit format
|
||||||
let format = if self.conventional {
|
let format = if self.conventional {
|
||||||
@@ -101,7 +104,7 @@ impl CommitCommand {
|
|||||||
// Stage all if requested
|
// Stage all if requested
|
||||||
if self.all {
|
if self.all {
|
||||||
repo.stage_all()?;
|
repo.stage_all()?;
|
||||||
println!("{}", "✓ Staged all changes".green());
|
println!("{}", messages.staged_all().green());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate or build commit message
|
// Generate or build commit message
|
||||||
@@ -113,10 +116,10 @@ impl CommitCommand {
|
|||||||
self.create_manual_commit(format)?
|
self.create_manual_commit(format)?
|
||||||
} else if config.commit.auto_generate && !self.yes {
|
} else if config.commit.auto_generate && !self.yes {
|
||||||
// AI-generated commit
|
// AI-generated commit
|
||||||
self.generate_commit(&repo, format).await?
|
self.generate_commit(&repo, format, &messages).await?
|
||||||
} else {
|
} else {
|
||||||
// Interactive commit creation
|
// Interactive commit creation
|
||||||
self.create_interactive_commit(format).await?
|
self.create_interactive_commit(format, &messages).await?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Validate message
|
// Validate message
|
||||||
@@ -132,32 +135,32 @@ impl CommitCommand {
|
|||||||
// Show commit preview
|
// Show commit preview
|
||||||
if !self.yes {
|
if !self.yes {
|
||||||
println!("\n{}", "─".repeat(60));
|
println!("\n{}", "─".repeat(60));
|
||||||
println!("{}", "Commit preview:".bold());
|
println!("{}", messages.commit_preview().bold());
|
||||||
println!("{}", "─".repeat(60));
|
println!("{}", "─".repeat(60));
|
||||||
println!("{}", commit_message);
|
println!("{}", commit_message);
|
||||||
println!("{}", "─".repeat(60));
|
println!("{}", "─".repeat(60));
|
||||||
|
|
||||||
let confirm = Confirm::new()
|
let confirm = Confirm::new()
|
||||||
.with_prompt("Do you want to proceed with this commit?")
|
.with_prompt(messages.proceed_commit())
|
||||||
.default(true)
|
.default(true)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
if !confirm {
|
if !confirm {
|
||||||
println!("{}", "Commit cancelled.".yellow());
|
println!("{}", messages.commit_cancelled().yellow());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = if self.amend {
|
let result = if self.amend {
|
||||||
if self.dry_run {
|
if self.dry_run {
|
||||||
println!("\n{}", "Dry run - commit not amended.".yellow());
|
println!("\n{} {}", messages.dry_run(), "- commit not amended.".yellow());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
self.amend_commit(&repo, &commit_message)?;
|
self.amend_commit(&repo, &commit_message)?;
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
if self.dry_run {
|
if self.dry_run {
|
||||||
println!("\n{}", "Dry run - commit not created.".yellow());
|
println!("\n{} {}", messages.dry_run(), "- commit not created.".yellow());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
CommitBuilder::new()
|
CommitBuilder::new()
|
||||||
@@ -167,9 +170,9 @@ impl CommitCommand {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(commit_oid) = result {
|
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 {
|
} else {
|
||||||
println!("{} {}", "✓ Amended commit".green().bold(), "successfully");
|
println!("{} {}", messages.commit_amended().green().bold(), "successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -198,7 +201,7 @@ impl CommitCommand {
|
|||||||
builder.build_message()
|
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 manager = ConfigManager::new()?;
|
||||||
let config = manager.config();
|
let config = manager.config();
|
||||||
|
|
||||||
@@ -206,7 +209,7 @@ impl CommitCommand {
|
|||||||
let generator = ContentGenerator::new(&config.llm).await
|
let generator = ContentGenerator::new(&config.llm).await
|
||||||
.context("Failed to initialize LLM. Use --manual for manual commit.")?;
|
.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 {
|
let generated = if self.yes {
|
||||||
generator.generate_commit_from_repo(repo, format).await?
|
generator.generate_commit_from_repo(repo, format).await?
|
||||||
@@ -217,42 +220,42 @@ impl CommitCommand {
|
|||||||
Ok(generated.to_conventional())
|
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);
|
let types = get_commit_types(format == CommitFormat::Commitlint);
|
||||||
|
|
||||||
// Select type
|
// Select type
|
||||||
let type_idx = Select::new()
|
let type_idx = Select::new()
|
||||||
.with_prompt("Select commit type")
|
.with_prompt(messages.select_commit_type())
|
||||||
.items(types)
|
.items(types)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
let commit_type = types[type_idx].to_string();
|
let commit_type = types[type_idx].to_string();
|
||||||
|
|
||||||
// Enter scope (optional)
|
// Enter scope (optional)
|
||||||
let scope: String = Input::new()
|
let scope: String = Input::new()
|
||||||
.with_prompt("Scope (optional, press Enter to skip)")
|
.with_prompt(messages.scope_optional())
|
||||||
.allow_empty(true)
|
.allow_empty(true)
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
let scope = if scope.is_empty() { None } else { Some(scope) };
|
let scope = if scope.is_empty() { None } else { Some(scope) };
|
||||||
|
|
||||||
// Enter description
|
// Enter description
|
||||||
let description: String = Input::new()
|
let description: String = Input::new()
|
||||||
.with_prompt("Description")
|
.with_prompt(messages.description())
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
|
|
||||||
// Breaking change
|
// Breaking change
|
||||||
let breaking = Confirm::new()
|
let breaking = Confirm::new()
|
||||||
.with_prompt("Is this a breaking change?")
|
.with_prompt(messages.breaking_change())
|
||||||
.default(false)
|
.default(false)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
// Add body
|
// Add body
|
||||||
let add_body = Confirm::new()
|
let add_body = Confirm::new()
|
||||||
.with_prompt("Add body to commit?")
|
.with_prompt(messages.add_body())
|
||||||
.default(false)
|
.default(false)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
let body = if add_body {
|
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() {
|
if body_text.trim().is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use clap::{Parser, Subcommand};
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use dialoguer::{Confirm, Input, Select};
|
use dialoguer::{Confirm, Input, Select};
|
||||||
|
|
||||||
use crate::config::manager::ConfigManager;
|
use crate::config::{Language, manager::ConfigManager};
|
||||||
use crate::config::CommitFormat;
|
use crate::config::CommitFormat;
|
||||||
|
|
||||||
/// Mask API key with asterisks for security
|
/// Mask API key with asterisks for security
|
||||||
@@ -146,7 +146,25 @@ enum ConfigSubcommand {
|
|||||||
/// Path
|
/// Path
|
||||||
path: String,
|
path: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Set output language
|
||||||
|
SetLanguage {
|
||||||
|
/// Language code (en, zh, ja, ko, es, fr, de)
|
||||||
|
language: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// 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 configuration to defaults
|
||||||
Reset {
|
Reset {
|
||||||
/// Skip confirmation
|
/// Skip confirmation
|
||||||
@@ -196,6 +214,9 @@ impl ConfigCommand {
|
|||||||
Some(ConfigSubcommand::SetCommitFormat { format }) => self.set_commit_format(format).await,
|
Some(ConfigSubcommand::SetCommitFormat { format }) => self.set_commit_format(format).await,
|
||||||
Some(ConfigSubcommand::SetVersionPrefix { prefix }) => self.set_version_prefix(prefix).await,
|
Some(ConfigSubcommand::SetVersionPrefix { prefix }) => self.set_version_prefix(prefix).await,
|
||||||
Some(ConfigSubcommand::SetChangelogPath { path }) => self.set_changelog_path(path).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::Reset { force }) => self.reset(*force).await,
|
||||||
Some(ConfigSubcommand::Export { output }) => self.export_config(output.as_deref()).await,
|
Some(ConfigSubcommand::Export { output }) => self.export_config(output.as_deref()).await,
|
||||||
Some(ConfigSubcommand::Import { file }) => self.import_config(file).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!(" 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!(" 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!("\n{}", "Changelog Configuration:".bold());
|
||||||
println!(" Path: {}", config.changelog.path);
|
println!(" Path: {}", config.changelog.path);
|
||||||
println!(" Auto-generate: {}", if config.changelog.auto_generate { "yes".green() } else { "no".red() });
|
println!(" Auto-generate: {}", if config.changelog.auto_generate { "yes".green() } else { "no".red() });
|
||||||
@@ -706,6 +733,54 @@ impl ConfigCommand {
|
|||||||
Ok(())
|
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<String> = 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<()> {
|
async fn reset(&self, force: bool) -> Result<()> {
|
||||||
if !force {
|
if !force {
|
||||||
let confirm = Confirm::new()
|
let confirm = Confirm::new()
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ use clap::Parser;
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use dialoguer::{Confirm, Input, Select};
|
use dialoguer::{Confirm, Input, Select};
|
||||||
|
|
||||||
use crate::config::{GitProfile};
|
use crate::config::{GitProfile, Language};
|
||||||
use crate::config::manager::ConfigManager;
|
use crate::config::manager::ConfigManager;
|
||||||
use crate::config::profile::{GpgConfig, SshConfig};
|
use crate::config::profile::{GpgConfig, SshConfig};
|
||||||
|
use crate::i18n::Messages;
|
||||||
use crate::utils::validators::validate_email;
|
use crate::utils::validators::validate_email;
|
||||||
|
|
||||||
/// Initialize quicommit configuration
|
/// Initialize quicommit configuration
|
||||||
@@ -22,7 +23,9 @@ pub struct InitCommand {
|
|||||||
|
|
||||||
impl InitCommand {
|
impl InitCommand {
|
||||||
pub async fn execute(&self) -> Result<()> {
|
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()?;
|
let config_path = crate::config::AppConfig::default_path()?;
|
||||||
|
|
||||||
@@ -57,9 +60,13 @@ impl InitCommand {
|
|||||||
|
|
||||||
manager.save()?;
|
manager.save()?;
|
||||||
|
|
||||||
println!("{}", "✅ QuiCommit initialized successfully!".bold().green());
|
// Get configured language for final messages
|
||||||
println!("\nConfig file: {}", config_path.display());
|
let language = manager.get_language().unwrap_or(Language::English);
|
||||||
println!("\nNext steps:");
|
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!(" 1. Create a profile: {}", "quicommit profile add".cyan());
|
||||||
println!(" 2. Configure LLM: {}", "quicommit config set-llm".cyan());
|
println!(" 2. Configure LLM: {}", "quicommit config set-llm".cyan());
|
||||||
println!(" 3. Start committing: {}", "quicommit commit".cyan());
|
println!(" 3. Start committing: {}", "quicommit commit".cyan());
|
||||||
@@ -90,11 +97,35 @@ impl InitCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn interactive_setup(&self, manager: &mut ConfigManager) -> Result<()> {
|
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<String> = 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
|
// Profile name
|
||||||
let profile_name: String = Input::new()
|
let profile_name: String = Input::new()
|
||||||
.with_prompt("Profile name")
|
.with_prompt(messages.profile_name())
|
||||||
.default("personal".to_string())
|
.default("personal".to_string())
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
|
|
||||||
@@ -110,12 +141,12 @@ impl InitCommand {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let user_name: String = Input::new()
|
let user_name: String = Input::new()
|
||||||
.with_prompt("Git user name")
|
.with_prompt(messages.git_user_name())
|
||||||
.default(default_name)
|
.default(default_name)
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
|
|
||||||
let user_email: String = Input::new()
|
let user_email: String = Input::new()
|
||||||
.with_prompt("Git user email")
|
.with_prompt(messages.git_user_email())
|
||||||
.default(default_email)
|
.default(default_email)
|
||||||
.validate_with(|input: &String| {
|
.validate_with(|input: &String| {
|
||||||
validate_email(input).map_err(|e| e.to_string())
|
validate_email(input).map_err(|e| e.to_string())
|
||||||
@@ -123,18 +154,18 @@ impl InitCommand {
|
|||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
|
|
||||||
let description: String = Input::new()
|
let description: String = Input::new()
|
||||||
.with_prompt("Profile description (optional)")
|
.with_prompt(messages.profile_description())
|
||||||
.allow_empty(true)
|
.allow_empty(true)
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
|
|
||||||
let is_work = Confirm::new()
|
let is_work = Confirm::new()
|
||||||
.with_prompt("Is this a work profile?")
|
.with_prompt(messages.is_work_profile())
|
||||||
.default(false)
|
.default(false)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
let organization = if is_work {
|
let organization = if is_work {
|
||||||
Some(Input::new()
|
Some(Input::new()
|
||||||
.with_prompt("Organization/Company name")
|
.with_prompt(messages.organization_name())
|
||||||
.interact_text()?)
|
.interact_text()?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -142,24 +173,24 @@ impl InitCommand {
|
|||||||
|
|
||||||
// SSH configuration
|
// SSH configuration
|
||||||
let setup_ssh = Confirm::new()
|
let setup_ssh = Confirm::new()
|
||||||
.with_prompt("Configure SSH key?")
|
.with_prompt(messages.configure_ssh())
|
||||||
.default(false)
|
.default(false)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
let ssh_config = if setup_ssh {
|
let ssh_config = if setup_ssh {
|
||||||
Some(self.setup_ssh_interactive().await?)
|
Some(self.setup_ssh_interactive(&messages).await?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// GPG configuration
|
// GPG configuration
|
||||||
let setup_gpg = Confirm::new()
|
let setup_gpg = Confirm::new()
|
||||||
.with_prompt("Configure GPG signing?")
|
.with_prompt(messages.configure_gpg())
|
||||||
.default(false)
|
.default(false)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
let gpg_config = if setup_gpg {
|
let gpg_config = if setup_gpg {
|
||||||
Some(self.setup_gpg_interactive().await?)
|
Some(self.setup_gpg_interactive(&messages).await?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -184,7 +215,7 @@ impl InitCommand {
|
|||||||
manager.set_default_profile(Some(profile_name))?;
|
manager.set_default_profile(Some(profile_name))?;
|
||||||
|
|
||||||
// LLM provider selection
|
// LLM provider selection
|
||||||
println!("\n{}", "Select your preferred LLM provider:".bold());
|
println!("\n{}", messages.select_llm_provider().bold());
|
||||||
let providers = vec![
|
let providers = vec![
|
||||||
"Ollama (local)",
|
"Ollama (local)",
|
||||||
"OpenAI",
|
"OpenAI",
|
||||||
@@ -213,27 +244,27 @@ impl InitCommand {
|
|||||||
// Configure API key if needed
|
// Configure API key if needed
|
||||||
if provider == "openai" {
|
if provider == "openai" {
|
||||||
let api_key: String = Input::new()
|
let api_key: String = Input::new()
|
||||||
.with_prompt("OpenAI API key")
|
.with_prompt(messages.openai_api_key())
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
manager.set_openai_api_key(api_key);
|
manager.set_openai_api_key(api_key);
|
||||||
} else if provider == "anthropic" {
|
} else if provider == "anthropic" {
|
||||||
let api_key: String = Input::new()
|
let api_key: String = Input::new()
|
||||||
.with_prompt("Anthropic API key")
|
.with_prompt(messages.anthropic_api_key())
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
manager.set_anthropic_api_key(api_key);
|
manager.set_anthropic_api_key(api_key);
|
||||||
} else if provider == "kimi" {
|
} else if provider == "kimi" {
|
||||||
let api_key: String = Input::new()
|
let api_key: String = Input::new()
|
||||||
.with_prompt("Kimi API key")
|
.with_prompt(messages.kimi_api_key())
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
manager.set_kimi_api_key(api_key);
|
manager.set_kimi_api_key(api_key);
|
||||||
} else if provider == "deepseek" {
|
} else if provider == "deepseek" {
|
||||||
let api_key: String = Input::new()
|
let api_key: String = Input::new()
|
||||||
.with_prompt("DeepSeek API key")
|
.with_prompt(messages.deepseek_api_key())
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
manager.set_deepseek_api_key(api_key);
|
manager.set_deepseek_api_key(api_key);
|
||||||
} else if provider == "openrouter" {
|
} else if provider == "openrouter" {
|
||||||
let api_key: String = Input::new()
|
let api_key: String = Input::new()
|
||||||
.with_prompt("OpenRouter API key")
|
.with_prompt(messages.openrouter_api_key())
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
manager.set_openrouter_api_key(api_key);
|
manager.set_openrouter_api_key(api_key);
|
||||||
}
|
}
|
||||||
@@ -241,7 +272,7 @@ impl InitCommand {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup_ssh_interactive(&self) -> Result<SshConfig> {
|
async fn setup_ssh_interactive(&self, messages: &Messages) -> Result<SshConfig> {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
let ssh_dir = dirs::home_dir()
|
let ssh_dir = dirs::home_dir()
|
||||||
@@ -249,17 +280,17 @@ impl InitCommand {
|
|||||||
.unwrap_or_else(|| PathBuf::from("~/.ssh"));
|
.unwrap_or_else(|| PathBuf::from("~/.ssh"));
|
||||||
|
|
||||||
let key_path: String = Input::new()
|
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())
|
.default(ssh_dir.join("id_rsa").display().to_string())
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
|
|
||||||
let has_passphrase = Confirm::new()
|
let has_passphrase = Confirm::new()
|
||||||
.with_prompt("Does this key have a passphrase?")
|
.with_prompt(messages.has_passphrase())
|
||||||
.default(false)
|
.default(false)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
let passphrase = if has_passphrase {
|
let passphrase = if has_passphrase {
|
||||||
Some(crate::utils::password_input("SSH key passphrase")?)
|
Some(crate::utils::password_input(messages.ssh_key_passphrase())?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -274,13 +305,13 @@ impl InitCommand {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup_gpg_interactive(&self) -> Result<GpgConfig> {
|
async fn setup_gpg_interactive(&self, messages: &Messages) -> Result<GpgConfig> {
|
||||||
let key_id: String = Input::new()
|
let key_id: String = Input::new()
|
||||||
.with_prompt("GPG key ID")
|
.with_prompt(messages.gpg_key_id())
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
|
|
||||||
let use_agent = Confirm::new()
|
let use_agent = Confirm::new()
|
||||||
.with_prompt("Use GPG agent?")
|
.with_prompt(messages.use_gpg_agent())
|
||||||
.default(true)
|
.default(true)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ use colored::Colorize;
|
|||||||
use dialoguer::{Confirm, Input, Select};
|
use dialoguer::{Confirm, Input, Select};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
use crate::config::manager::ConfigManager;
|
use crate::config::{Language, manager::ConfigManager};
|
||||||
use crate::git::{find_repo, GitRepo};
|
use crate::git::{find_repo, GitRepo};
|
||||||
use crate::generator::ContentGenerator;
|
use crate::generator::ContentGenerator;
|
||||||
use crate::git::tag::{
|
use crate::git::tag::{
|
||||||
bump_version, get_latest_version, suggest_version_bump, TagBuilder, VersionBump,
|
bump_version, get_latest_version, suggest_version_bump, TagBuilder, VersionBump,
|
||||||
};
|
};
|
||||||
|
use crate::i18n::Messages;
|
||||||
|
|
||||||
/// Generate and create Git tags
|
/// Generate and create Git tags
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@@ -64,6 +65,8 @@ impl TagCommand {
|
|||||||
let repo = find_repo(std::env::current_dir()?.as_path())?;
|
let repo = find_repo(std::env::current_dir()?.as_path())?;
|
||||||
let manager = ConfigManager::new()?;
|
let manager = ConfigManager::new()?;
|
||||||
let config = manager.config();
|
let config = manager.config();
|
||||||
|
let language = manager.get_language().unwrap_or(Language::English);
|
||||||
|
let messages = Messages::new(language);
|
||||||
|
|
||||||
// Determine tag name
|
// Determine tag name
|
||||||
let tag_name = if let Some(name) = &self.name {
|
let tag_name = if let Some(name) = &self.name {
|
||||||
@@ -80,7 +83,7 @@ impl TagCommand {
|
|||||||
format!("{}{}", prefix, new_version)
|
format!("{}{}", prefix, new_version)
|
||||||
} else {
|
} else {
|
||||||
// Interactive mode
|
// 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)
|
// Validate tag name (if it looks like a version)
|
||||||
@@ -96,7 +99,7 @@ impl TagCommand {
|
|||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
if !proceed {
|
if !proceed {
|
||||||
bail!("Tag creation cancelled");
|
bail!("{}", messages.tag_cancelled());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,16 +111,16 @@ impl TagCommand {
|
|||||||
} else if let Some(msg) = &self.message {
|
} else if let Some(msg) = &self.message {
|
||||||
Some(msg.clone())
|
Some(msg.clone())
|
||||||
} else if self.generate || (config.tag.auto_generate && !self.yes) {
|
} 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 {
|
} else if !self.yes {
|
||||||
Some(self.input_message_interactive(&tag_name)?)
|
Some(self.input_message_interactive(&tag_name, &messages)?)
|
||||||
} else {
|
} else {
|
||||||
Some(format!("Release {}", tag_name))
|
Some(format!("Release {}", tag_name))
|
||||||
};
|
};
|
||||||
|
|
||||||
// Show preview
|
// Show preview
|
||||||
println!("\n{}", "─".repeat(60));
|
println!("\n{}", "─".repeat(60));
|
||||||
println!("{}", "Tag preview:".bold());
|
println!("{}", messages.tag_preview().bold());
|
||||||
println!("{}", "─".repeat(60));
|
println!("{}", "─".repeat(60));
|
||||||
println!("Name: {}", tag_name.cyan());
|
println!("Name: {}", tag_name.cyan());
|
||||||
if let Some(ref msg) = message {
|
if let Some(ref msg) = message {
|
||||||
@@ -129,18 +132,18 @@ impl TagCommand {
|
|||||||
|
|
||||||
if !self.yes {
|
if !self.yes {
|
||||||
let confirm = Confirm::new()
|
let confirm = Confirm::new()
|
||||||
.with_prompt("Create this tag?")
|
.with_prompt(messages.create_tag())
|
||||||
.default(true)
|
.default(true)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
if !confirm {
|
if !confirm {
|
||||||
println!("{}", "Tag creation cancelled.".yellow());
|
println!("{}", messages.tag_cancelled().yellow());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.dry_run {
|
if self.dry_run {
|
||||||
println!("\n{}", "Dry run - tag not created.".yellow());
|
println!("\n{} {}", messages.dry_run(), "- tag not created.".yellow());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,41 +156,41 @@ impl TagCommand {
|
|||||||
|
|
||||||
builder.execute(&repo)?;
|
builder.execute(&repo)?;
|
||||||
|
|
||||||
println!("{} Created tag {}", "✓".green(), tag_name.cyan());
|
println!("{} {}", messages.tag_created().green(), tag_name.cyan());
|
||||||
|
|
||||||
// Push if requested
|
// Push if requested
|
||||||
if self.push {
|
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))?;
|
repo.push(&self.remote, &format!("refs/tags/{}", tag_name))?;
|
||||||
println!("{} Pushed tag to {}", "✓".green(), &self.remote);
|
println!("{}", messages.pushed_tag(&self.remote));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
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 {
|
loop {
|
||||||
let latest = get_latest_version(repo, prefix)?;
|
let latest = get_latest_version(repo, prefix)?;
|
||||||
|
|
||||||
println!("\n{}", "Version selection:".bold());
|
println!("\n{}", messages.version_selection().bold());
|
||||||
|
|
||||||
if let Some(ref version) = latest {
|
if let Some(ref version) = latest {
|
||||||
println!("Latest version: {}{}", prefix, version);
|
println!("{} {}{}", messages.latest_version(), prefix, version);
|
||||||
} else {
|
} else {
|
||||||
println!("No existing version tags found");
|
println!("{}", messages.no_existing_version_tags());
|
||||||
}
|
}
|
||||||
|
|
||||||
let options = vec![
|
let options = vec![
|
||||||
"Auto-detect bump from commits",
|
messages.auto_detect_bump(),
|
||||||
"Bump major version",
|
messages.bump_major_version(),
|
||||||
"Bump minor version",
|
messages.bump_minor_version(),
|
||||||
"Bump patch version",
|
messages.bump_patch_version(),
|
||||||
"Enter custom version",
|
messages.enter_custom_version(),
|
||||||
"Enter custom tag name",
|
messages.enter_custom_tag_name(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let selection = Select::new()
|
let selection = Select::new()
|
||||||
.with_prompt("Select option")
|
.with_prompt(messages.select_option())
|
||||||
.items(&options)
|
.items(&options)
|
||||||
.default(0)
|
.default(0)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
@@ -201,10 +204,10 @@ impl TagCommand {
|
|||||||
.map(|v| bump_version(v, bump, None))
|
.map(|v| bump_version(v, bump, None))
|
||||||
.unwrap_or_else(|| Version::new(0, 1, 0));
|
.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()
|
let confirm = Confirm::new()
|
||||||
.with_prompt("Use this version?")
|
.with_prompt(messages.use_this_version())
|
||||||
.default(true)
|
.default(true)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
@@ -233,14 +236,14 @@ impl TagCommand {
|
|||||||
}
|
}
|
||||||
4 => {
|
4 => {
|
||||||
let input: String = Input::new()
|
let input: String = Input::new()
|
||||||
.with_prompt("Enter version (e.g., 1.2.3)")
|
.with_prompt(messages.enter_version())
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
let version = Version::parse(&input)?;
|
let version = Version::parse(&input)?;
|
||||||
return Ok(format!("{}{}", prefix, version));
|
return Ok(format!("{}{}", prefix, version));
|
||||||
}
|
}
|
||||||
5 => {
|
5 => {
|
||||||
let input: String = Input::new()
|
let input: String = Input::new()
|
||||||
.with_prompt("Enter tag name")
|
.with_prompt(messages.enter_tag_name())
|
||||||
.interact_text()?;
|
.interact_text()?;
|
||||||
return Ok(input);
|
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 manager = ConfigManager::new()?;
|
||||||
let config = manager.config();
|
let config = manager.config();
|
||||||
|
|
||||||
@@ -265,17 +268,17 @@ impl TagCommand {
|
|||||||
return Ok(format!("Release {}", version));
|
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?;
|
let generator = ContentGenerator::new(&config.llm).await?;
|
||||||
generator.generate_tag_message(version, &commits).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 default_msg = format!("Release {}", version);
|
||||||
|
|
||||||
let use_editor = Confirm::new()
|
let use_editor = Confirm::new()
|
||||||
.with_prompt("Open editor for tag message?")
|
.with_prompt(messages.open_editor())
|
||||||
.default(false)
|
.default(false)
|
||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
@@ -283,7 +286,7 @@ impl TagCommand {
|
|||||||
crate::utils::editor::edit_content(&default_msg)
|
crate::utils::editor::edit_content(&default_msg)
|
||||||
} else {
|
} else {
|
||||||
Ok(Input::new()
|
Ok(Input::new()
|
||||||
.with_prompt("Tag message")
|
.with_prompt(messages.tag_message())
|
||||||
.default(default_msg)
|
.default(default_msg)
|
||||||
.interact_text()?)
|
.interact_text()?)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -391,6 +391,46 @@ impl ConfigManager {
|
|||||||
self.modified = true;
|
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> {
|
||||||
|
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
|
/// Export configuration to TOML string
|
||||||
pub fn export(&self) -> Result<String> {
|
pub fn export(&self) -> Result<String> {
|
||||||
toml::to_string_pretty(&self.config)
|
toml::to_string_pretty(&self.config)
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ pub struct AppConfig {
|
|||||||
/// Theme settings
|
/// Theme settings
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub theme: ThemeConfig,
|
pub theme: ThemeConfig,
|
||||||
|
|
||||||
|
/// Language settings
|
||||||
|
#[serde(default)]
|
||||||
|
pub language: LanguageConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppConfig {
|
impl Default for AppConfig {
|
||||||
@@ -68,6 +72,7 @@ impl Default for AppConfig {
|
|||||||
repo_profiles: HashMap::new(),
|
repo_profiles: HashMap::new(),
|
||||||
encrypt_sensitive: true,
|
encrypt_sensitive: true,
|
||||||
theme: ThemeConfig::default(),
|
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<Self> {
|
||||||
|
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
|
// Default value functions
|
||||||
fn default_version() -> String {
|
fn default_version() -> String {
|
||||||
"1".to_string()
|
"1".to_string()
|
||||||
@@ -594,6 +676,10 @@ fn default_date_format() -> String {
|
|||||||
"%Y-%m-%d".to_string()
|
"%Y-%m-%d".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_output_language() -> String {
|
||||||
|
"en".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
/// Load configuration from file
|
/// Load configuration from file
|
||||||
pub fn load(path: &Path) -> Result<Self> {
|
pub fn load(path: &Path) -> Result<Self> {
|
||||||
|
|||||||
1091
src/i18n/messages.rs
Normal file
1091
src/i18n/messages.rs
Normal file
File diff suppressed because it is too large
Load Diff
7
src/i18n/mod.rs
Normal file
7
src/i18n/mod.rs
Normal file
@@ -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;
|
||||||
233
src/i18n/translator.rs
Normal file
233
src/i18n/translator.rs
Normal file
@@ -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)
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ mod commands;
|
|||||||
mod config;
|
mod config;
|
||||||
mod generator;
|
mod generator;
|
||||||
mod git;
|
mod git;
|
||||||
|
mod i18n;
|
||||||
mod llm;
|
mod llm;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user