From e79bc8790b59c99ca70a6dae6996d656a02ca1ab Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 23 Jun 2025 19:05:15 +0200 Subject: [PATCH] l10n: port rm for translation + add french --- Cargo.lock | 1 + src/uu/rm/Cargo.toml | 1 + src/uu/rm/locales/en-US.ftl | 32 ++++++++ src/uu/rm/locales/fr-FR.ftl | 46 ++++++++++++ src/uu/rm/src/rm.rs | 142 +++++++++++++++++++++++++----------- 5 files changed, 181 insertions(+), 41 deletions(-) create mode 100644 src/uu/rm/locales/fr-FR.ftl diff --git a/Cargo.lock b/Cargo.lock index f8e977712..74423c091 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3388,6 +3388,7 @@ version = "0.1.0" dependencies = [ "clap", "libc", + "thiserror 2.0.12", "uucore", "windows-sys 0.60.2", ] diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index 28366060e..0ed6e93fa 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -18,6 +18,7 @@ workspace = true path = "src/rm.rs" [dependencies] +thiserror = { workspace = true } clap = { workspace = true } uucore = { workspace = true, features = ["fs"] } diff --git a/src/uu/rm/locales/en-US.ftl b/src/uu/rm/locales/en-US.ftl index 31f0d0083..5a1231806 100644 --- a/src/uu/rm/locales/en-US.ftl +++ b/src/uu/rm/locales/en-US.ftl @@ -12,3 +12,35 @@ rm-after-help = By default, rm does not remove directories. Use the --recursive Note that if you use rm to remove a file, it might be possible to recover some of its contents, given sufficient expertise and/or time. For greater assurance that the contents are truly unrecoverable, consider using shred. + +# Help text for options +rm-help-force = ignore nonexistent files and arguments, never prompt +rm-help-prompt-always = prompt before every removal +rm-help-prompt-once = prompt once before removing more than three files, or when removing recursively. + Less intrusive than -i, while still giving some protection against most mistakes +rm-help-interactive = prompt according to WHEN: never, once (-I), or always (-i). Without WHEN, + prompts always +rm-help-one-file-system = when removing a hierarchy recursively, skip any directory that is on a file + system different from that of the corresponding command line argument (NOT + IMPLEMENTED) +rm-help-no-preserve-root = do not treat '/' specially +rm-help-preserve-root = do not remove '/' (default) +rm-help-recursive = remove directories and their contents recursively +rm-help-dir = remove empty directories +rm-help-verbose = explain what is being done + +# Error messages +rm-error-missing-operand = missing operand + Try '{$util_name} --help' for more information. +rm-error-invalid-interactive-argument = Invalid argument to interactive ({$arg}) +rm-error-cannot-remove-no-such-file = cannot remove {$file}: No such file or directory +rm-error-cannot-remove-permission-denied = cannot remove {$file}: Permission denied +rm-error-cannot-remove-is-directory = cannot remove {$file}: Is a directory +rm-error-dangerous-recursive-operation = it is dangerous to operate recursively on '/' +rm-error-use-no-preserve-root = use --no-preserve-root to override this failsafe +rm-error-refusing-to-remove-directory = refusing to remove '.' or '..' directory: skipping '{$path}' +rm-error-cannot-remove = cannot remove {$file} + +# Verbose messages +rm-verbose-removed = removed {$file} +rm-verbose-removed-directory = removed directory {$file} diff --git a/src/uu/rm/locales/fr-FR.ftl b/src/uu/rm/locales/fr-FR.ftl new file mode 100644 index 000000000..80842e8bc --- /dev/null +++ b/src/uu/rm/locales/fr-FR.ftl @@ -0,0 +1,46 @@ +rm-about = Supprimer (délier) le(s) FICHIER(s) +rm-usage = rm [OPTION]... FICHIER... +rm-after-help = Par défaut, rm ne supprime pas les répertoires. Utilisez l'option --recursive (-r ou -R) + pour supprimer également chaque répertoire listé, ainsi que tout son contenu + + Pour supprimer un fichier dont le nom commence par un '-', par exemple '-foo', + utilisez une de ces commandes : + rm -- -foo + + rm ./-foo + + Notez que si vous utilisez rm pour supprimer un fichier, il pourrait être possible de récupérer + une partie de son contenu, avec suffisamment d'expertise et/ou de temps. Pour une meilleure + assurance que le contenu est vraiment irrécupérable, considérez utiliser shred. + +# Texte d'aide pour les options +rm-help-force = ignorer les fichiers inexistants et les arguments, ne jamais demander +rm-help-prompt-always = demander avant chaque suppression +rm-help-prompt-once = demander une fois avant de supprimer plus de trois fichiers, ou lors d'une suppression récursive. + Moins intrusif que -i, tout en offrant une protection contre la plupart des erreurs +rm-help-interactive = demander selon QUAND : never, once (-I), ou always (-i). Sans QUAND, + demande toujours +rm-help-one-file-system = lors de la suppression récursive d'une hiérarchie, ignorer tout répertoire situé sur un + système de fichiers différent de celui de l'argument de ligne de commande correspondant (NON + IMPLÉMENTÉ) +rm-help-no-preserve-root = ne pas traiter '/' spécialement +rm-help-preserve-root = ne pas supprimer '/' (par défaut) +rm-help-recursive = supprimer les répertoires et leur contenu récursivement +rm-help-dir = supprimer les répertoires vides +rm-help-verbose = expliquer ce qui est fait + +# Messages d'erreur +rm-error-missing-operand = opérande manquant + Essayez '{$util_name} --help' pour plus d'informations. +rm-error-invalid-interactive-argument = Argument invalide pour interactive ({$arg}) +rm-error-cannot-remove-no-such-file = impossible de supprimer {$file} : Aucun fichier ou répertoire de ce type +rm-error-cannot-remove-permission-denied = impossible de supprimer {$file} : Permission refusée +rm-error-cannot-remove-is-directory = impossible de supprimer {$file} : C'est un répertoire +rm-error-dangerous-recursive-operation = il est dangereux d'opérer récursivement sur '/' +rm-error-use-no-preserve-root = utilisez --no-preserve-root pour outrepasser cette protection +rm-error-refusing-to-remove-directory = refus de supprimer le répertoire '.' ou '..' : ignorer '{$path}' +rm-error-cannot-remove = impossible de supprimer {$file} + +# Messages verbeux +rm-verbose-removed = {$file} supprimé +rm-verbose-removed-directory = répertoire {$file} supprimé diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index d7340faa1..864458f66 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -6,6 +6,7 @@ // spell-checker:ignore (path) eacces inacc rm-r4 use clap::{Arg, ArgAction, Command, builder::ValueParser, parser::ValueSource}; +use std::collections::HashMap; use std::ffi::{OsStr, OsString}; use std::fs::{self, Metadata}; use std::io::{IsTerminal, stdin}; @@ -16,11 +17,38 @@ use std::os::unix::ffi::OsStrExt; use std::os::unix::fs::PermissionsExt; use std::path::MAIN_SEPARATOR; use std::path::{Path, PathBuf}; +use thiserror::Error; use uucore::display::Quotable; -use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::locale::get_message; +use uucore::error::{FromIo, UError, UResult}; +use uucore::locale::{get_message, get_message_with_args}; use uucore::{format_usage, os_str_as_bytes, prompt_yes, show_error}; +#[derive(Debug, Error)] +enum RmError { + #[error("{}", get_message_with_args("rm-error-missing-operand", + HashMap::from([ + ("util_name".to_string(), + uucore::execution_phrase().to_string()) + ])))] + MissingOperand, + #[error("{}", get_message_with_args("rm-error-invalid-interactive-argument", HashMap::from([("arg".to_string(), _0.clone())])))] + InvalidInteractiveArgument(String), + #[error("{}", get_message_with_args("rm-error-cannot-remove-no-such-file", HashMap::from([("file".to_string(), _0.quote().to_string())])))] + CannotRemoveNoSuchFile(String), + #[error("{}", get_message_with_args("rm-error-cannot-remove-permission-denied", HashMap::from([("file".to_string(), _0.quote().to_string())])))] + CannotRemovePermissionDenied(String), + #[error("{}", get_message_with_args("rm-error-cannot-remove-is-directory", HashMap::from([("file".to_string(), _0.quote().to_string())])))] + CannotRemoveIsDirectory(String), + #[error("{}", get_message("rm-error-dangerous-recursive-operation"))] + DangerousRecursiveOperation, + #[error("{}", get_message("rm-error-use-no-preserve-root"))] + UseNoPreserveRoot, + #[error("{}", get_message_with_args("rm-error-refusing-to-remove-directory", HashMap::from([("path".to_string(), _0.to_string())])))] + RefusingToRemoveDirectory(String), +} + +impl UError for RmError {} + #[derive(Eq, PartialEq, Clone, Copy)] /// Enum, determining when the `rm` will prompt the user about the file deletion pub enum InteractiveMode { @@ -117,7 +145,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if files.is_empty() && !force_flag { // Still check by hand and not use clap // Because "rm -f" is a thing - return Err(UUsageError::new(1, "missing operand")); + return Err(RmError::MissingOperand.into()); } // If -f(--force) is before any -i (or variants) we want prompts else no prompts @@ -146,10 +174,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { "once" => InteractiveMode::Once, "always" => InteractiveMode::Always, val => { - return Err(USimpleError::new( - 1, - format!("Invalid argument to interactive ({val})"), - )); + return Err(RmError::InvalidInteractiveArgument(val.to_string()).into()); } } } else { @@ -206,31 +231,27 @@ pub fn uu_app() -> Command { Arg::new(OPT_FORCE) .short('f') .long(OPT_FORCE) - .help("ignore nonexistent files and arguments, never prompt") + .help(get_message("rm-help-force")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_PROMPT_ALWAYS) .short('i') - .help("prompt before every removal") + .help(get_message("rm-help-prompt-always")) .overrides_with_all([OPT_PROMPT_ONCE, OPT_INTERACTIVE]) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_PROMPT_ONCE) .short('I') - .help("prompt once before removing more than three files, or when removing recursively. \ - Less intrusive than -i, while still giving some protection against most mistakes") + .help(get_message("rm-help-prompt-once")) .overrides_with_all([OPT_PROMPT_ALWAYS, OPT_INTERACTIVE]) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_INTERACTIVE) .long(OPT_INTERACTIVE) - .help( - "prompt according to WHEN: never, once (-I), or always (-i). Without WHEN, \ - prompts always", - ) + .help(get_message("rm-help-interactive")) .value_name("WHEN") .num_args(0..=1) .require_equals(true) @@ -240,22 +261,19 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_ONE_FILE_SYSTEM) .long(OPT_ONE_FILE_SYSTEM) - .help( - "when removing a hierarchy recursively, skip any directory that is on a file \ - system different from that of the corresponding command line argument (NOT \ - IMPLEMENTED)", - ).action(ArgAction::SetTrue), + .help(get_message("rm-help-one-file-system")) + .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_NO_PRESERVE_ROOT) .long(OPT_NO_PRESERVE_ROOT) - .help("do not treat '/' specially") + .help(get_message("rm-help-no-preserve-root")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_PRESERVE_ROOT) .long(OPT_PRESERVE_ROOT) - .help("do not remove '/' (default)") + .help(get_message("rm-help-preserve-root")) .action(ArgAction::SetTrue), ) .arg( @@ -263,21 +281,21 @@ pub fn uu_app() -> Command { .short('r') .visible_short_alias('R') .long(OPT_RECURSIVE) - .help("remove directories and their contents recursively") + .help(get_message("rm-help-recursive")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_DIR) .short('d') .long(OPT_DIR) - .help("remove empty directories") + .help(get_message("rm-help-dir")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_VERBOSE) .short('v') .long(OPT_VERBOSE) - .help("explain what is being done") + .help(get_message("rm-help-verbose")) .action(ArgAction::SetTrue), ) // From the GNU source code: @@ -337,8 +355,8 @@ pub fn remove(files: &[&OsStr], options: &Options) -> bool { false } else { show_error!( - "cannot remove {}: No such file or directory", - filename.quote() + "{}", + RmError::CannotRemoveNoSuchFile(filename.to_string_lossy().to_string()) ); true } @@ -425,7 +443,12 @@ fn remove_dir_recursive(path: &Path, options: &Options) -> bool { match fs::remove_dir_all(path) { Ok(_) => return false, Err(e) => { - let e = e.map_err_context(|| format!("cannot remove {}", path.quote())); + let e = e.map_err_context(|| { + get_message_with_args( + "rm-error-cannot-remove", + HashMap::from([("file".to_string(), path.quote().to_string())]), + ) + }); show_error!("{e}"); return true; } @@ -487,7 +510,12 @@ fn remove_dir_recursive(path: &Path, options: &Options) -> bool { error = true; } Err(e) if !error => { - let e = e.map_err_context(|| format!("cannot remove {}", path.quote())); + let e = e.map_err_context(|| { + get_message_with_args( + "rm-error-cannot-remove", + HashMap::from([("file".to_string(), path.quote().to_string())]), + ) + }); show_error!("{e}"); error = true; } @@ -497,7 +525,13 @@ fn remove_dir_recursive(path: &Path, options: &Options) -> bool { // show another error message as we return from each level // of the recursion. } - Ok(_) if options.verbose => println!("removed directory {}", normalize(path).quote()), + Ok(_) if options.verbose => println!( + "{}", + get_message_with_args( + "rm-verbose-removed-directory", + HashMap::from([("file".to_string(), normalize(path).quote().to_string())]) + ) + ), Ok(_) => {} } @@ -510,8 +544,8 @@ fn handle_dir(path: &Path, options: &Options) -> bool { let path = clean_trailing_slashes(path); if path_is_current_or_parent_directory(path) { show_error!( - "refusing to remove '.' or '..' directory: skipping '{}'", - path.display() + "{}", + RmError::RefusingToRemoveDirectory(path.display().to_string()) ); return true; } @@ -522,13 +556,13 @@ fn handle_dir(path: &Path, options: &Options) -> bool { } else if options.dir && (!is_root || !options.preserve_root) { had_err = remove_dir(path, options).bitor(had_err); } else if options.recursive { - show_error!("it is dangerous to operate recursively on '{MAIN_SEPARATOR}'"); - show_error!("use --no-preserve-root to override this failsafe"); + show_error!("{}", RmError::DangerousRecursiveOperation); + show_error!("{}", RmError::UseNoPreserveRoot); had_err = true; } else { show_error!( - "cannot remove {}: Is a directory", // GNU's rm error message does not include help - path.quote() + "{}", + RmError::CannotRemoveIsDirectory(path.to_string_lossy().to_string()) ); had_err = true; } @@ -547,7 +581,10 @@ fn remove_dir(path: &Path, options: &Options) -> bool { // Called to remove a symlink_dir (windows) without "-r"/"-R" or "-d". if !options.dir && !options.recursive { - show_error!("cannot remove {}: Is a directory", path.quote()); + show_error!( + "{}", + RmError::CannotRemoveIsDirectory(path.to_string_lossy().to_string()) + ); return true; } @@ -555,12 +592,23 @@ fn remove_dir(path: &Path, options: &Options) -> bool { match fs::remove_dir(path) { Ok(_) => { if options.verbose { - println!("removed directory {}", normalize(path).quote()); + println!( + "{}", + get_message_with_args( + "rm-verbose-removed-directory", + HashMap::from([("file".to_string(), normalize(path).quote().to_string())]) + ) + ); } false } Err(e) => { - let e = e.map_err_context(|| format!("cannot remove {}", path.quote())); + let e = e.map_err_context(|| { + get_message_with_args( + "rm-error-cannot-remove", + HashMap::from([("file".to_string(), path.quote().to_string())]), + ) + }); show_error!("{e}"); true } @@ -572,13 +620,25 @@ fn remove_file(path: &Path, options: &Options) -> bool { match fs::remove_file(path) { Ok(_) => { if options.verbose { - println!("removed {}", normalize(path).quote()); + println!( + "{}", + get_message_with_args( + "rm-verbose-removed", + HashMap::from([( + "file".to_string(), + normalize(path).quote().to_string() + )]) + ) + ); } } Err(e) => { if e.kind() == std::io::ErrorKind::PermissionDenied { // GNU compatibility (rm/fail-eacces.sh) - show_error!("cannot remove {}: {}", path.quote(), "Permission denied"); + show_error!( + "{}", + RmError::CannotRemovePermissionDenied(path.to_string_lossy().to_string()) + ); } else { show_error!("cannot remove {}: {e}", path.quote()); }