clap: also support translations for invalid values

This commit is contained in:
Sylvestre Ledru
2025-08-11 22:03:59 +02:00
parent 3b9a506825
commit ce941947b7
4 changed files with 102 additions and 1 deletions
+2
View File
@@ -12,6 +12,8 @@ common-version = version
clap-error-unexpected-argument = { $error_word }: unexpected argument '{ $arg }' found
clap-error-similar-argument = { $tip_word }: a similar argument exists: '{ $suggestion }'
clap-error-pass-as-value = { $tip_word }: to pass '{ $arg }' as a value, use '{ $tip_command }'
clap-error-invalid-value = { $error_word }: invalid value '{ $value }' for '{ $option }'
clap-error-possible-values = possible values
clap-error-help-suggestion = For more information, try '{ $command } --help'.
common-help-suggestion = For more information, try '--help'.
+2
View File
@@ -12,6 +12,8 @@ common-version = version
clap-error-unexpected-argument = { $error_word } : argument inattendu '{ $arg }' trouvé
clap-error-similar-argument = { $tip_word } : un argument similaire existe : '{ $suggestion }'
clap-error-pass-as-value = { $tip_word } : pour passer '{ $arg }' comme valeur, utilisez '{ $tip_command }'
clap-error-invalid-value = { $error_word } : valeur invalide '{ $value }' pour '{ $option }'
clap-error-possible-values = valeurs possibles
clap-error-help-suggestion = Pour plus d'informations, essayez '{ $command } --help'.
common-help-suggestion = Pour plus d'informations, essayez '--help'.
+70 -1
View File
@@ -6,10 +6,15 @@
//! Helper clap functions to localize error handling and options
//!
//! This module provides utilities for handling clap errors with localization support.
//! It uses clap's error context API to extract structured information from errors
//! instead of parsing error strings, providing a more robust solution.
//!
use crate::locale::translate;
use clap::error::{ContextKind, ErrorKind};
use clap::{ArgMatches, Command, Error};
use std::error::Error as StdError;
use std::ffi::OsString;
/// Determines if a clap error should show simple help instead of full usage
@@ -193,7 +198,71 @@ pub fn handle_clap_error_with_exit_code(err: Error, util_name: &str, exit_code:
}
// Check if this is a simple validation error that should show simple help
kind if should_show_simple_help_for_clap_error(kind) => {
// For simple validation errors, use the same simple format as other errors
// Special handling for InvalidValue and ValueValidation to provide localized error
if matches!(kind, ErrorKind::InvalidValue | ErrorKind::ValueValidation) {
// Force localization initialization
crate::locale::setup_localization(util_name).ok();
// Extract value and option from error context using clap's context API
// This is much more robust than parsing the error string
let invalid_arg = err.get(ContextKind::InvalidArg);
let invalid_value = err.get(ContextKind::InvalidValue);
if let (Some(arg), Some(value)) = (invalid_arg, invalid_value) {
let option = arg.to_string();
let value = value.to_string();
// Get localized error word
let error_word = translate!("common-error");
let colored_error_word = maybe_colorize(&error_word, Color::Red);
// Apply color to value and option if colors are enabled
let colored_value = maybe_colorize(&value, Color::Yellow);
let colored_option = maybe_colorize(&option, Color::Green);
// Print localized error message
let error_msg = translate!(
"clap-error-invalid-value",
"error_word" => colored_error_word,
"value" => colored_value,
"option" => colored_option
);
eprintln!("{error_msg}");
// For ValueValidation errors, include the validation error details
if matches!(kind, ErrorKind::ValueValidation) {
if let Some(source) = err.source() {
eprintln!(" {}", source);
}
}
// Show possible values if available (for InvalidValue errors)
if matches!(kind, ErrorKind::InvalidValue) {
if let Some(valid_values) = err.get(ContextKind::ValidValue) {
eprintln!();
let possible_values_label = translate!("clap-error-possible-values");
eprintln!(" [{}: {}]", possible_values_label, valid_values);
}
}
eprintln!();
eprintln!("{}", translate!("common-help-suggestion"));
std::process::exit(1);
} else {
// Fallback if we can't extract context - use clap's default formatting
let lines: Vec<&str> = rendered_str.lines().collect();
if let Some(main_error_line) = lines.first() {
eprintln!("{}", main_error_line);
eprintln!();
eprintln!("{}", translate!("common-help-suggestion"));
} else {
eprint!("{}", err.render());
}
std::process::exit(1);
}
}
// For other simple validation errors, use the same simple format as other errors
let lines: Vec<&str> = rendered_str.lines().collect();
if let Some(main_error_line) = lines.first() {
// Keep the "error: " prefix for test compatibility
+28
View File
@@ -83,6 +83,34 @@ fn test_invalid_value_returns_1() {
}
}
/* spellchecker: disable */
#[test]
fn test_localized_possible_values_english() {
// Test that "possible values" is properly localized in English
new_ucmd!()
.env("LANG", "en_US.UTF-8")
.env("LC_ALL", "en_US.UTF-8")
.arg("--color=invalid_test_value")
.fails()
.code_is(1)
.stderr_contains("error: invalid value 'invalid_test_value' for '--color")
.stderr_contains("[possible values:");
}
#[test]
fn test_localized_possible_values_french() {
// Test that "possible values" is properly localized in French
new_ucmd!()
.env("LANG", "fr_FR.UTF-8")
.env("LC_ALL", "fr_FR.UTF-8")
.arg("--color=invalid_test_value")
.fails()
.code_is(1)
.stderr_contains("erreur : valeur invalide 'invalid_test_value' pour '--color")
.stderr_contains("[valeurs possibles:");
}
/* spellchecker: enable */
#[test]
fn test_invalid_value_returns_2() {
// Invalid values to flags *sometimes* result in error code 2: