From 19f1d18d3c442ecaa1cb546fd7b1bcce00bbd935 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 15 Jun 2025 19:11:53 +0200 Subject: [PATCH] l10n: port install for translation + add french --- src/uu/install/locales/en-US.ftl | 53 ++++++++++ src/uu/install/locales/fr-FR.ftl | 56 ++++++++++ src/uu/install/src/install.rs | 175 ++++++++++++++++++++----------- src/uu/install/src/mode.rs | 13 ++- 4 files changed, 233 insertions(+), 64 deletions(-) create mode 100644 src/uu/install/locales/fr-FR.ftl diff --git a/src/uu/install/locales/en-US.ftl b/src/uu/install/locales/en-US.ftl index b5781abfb..0643cc557 100644 --- a/src/uu/install/locales/en-US.ftl +++ b/src/uu/install/locales/en-US.ftl @@ -1,3 +1,56 @@ install-about = Copy SOURCE to DEST or multiple SOURCE(s) to the existing DIRECTORY, while setting permission modes and owner/group install-usage = install [OPTION]... [FILE]... + +# Help messages +install-help-ignored = ignored +install-help-compare = compare each pair of source and destination files, and in some cases, do not modify the destination at all +install-help-directory = treat all arguments as directory names. create all components of the specified directories +install-help-create-leading = create all leading components of DEST except the last, then copy SOURCE to DEST +install-help-group = set group ownership, instead of process's current group +install-help-mode = set permission mode (as in chmod), instead of rwxr-xr-x +install-help-owner = set ownership (super-user only) +install-help-preserve-timestamps = apply access/modification times of SOURCE files to corresponding destination files +install-help-strip = strip symbol tables (no action Windows) +install-help-strip-program = program used to strip binaries (no action Windows) +install-help-target-directory = move all SOURCE arguments into DIRECTORY +install-help-no-target-directory = treat DEST as a normal file +install-help-verbose = explain what is being done +install-help-preserve-context = preserve security context +install-help-context = set security context of files and directories + +# Error messages +install-error-dir-needs-arg = { $util_name } with -d requires at least one argument. +install-error-create-dir-failed = failed to create { $path } +install-error-chmod-failed = failed to chmod { $path } +install-error-chmod-failed-detailed = { $path }: chmod failed with error { $error } +install-error-chown-failed = failed to chown { $path }: { $error } +install-error-invalid-target = invalid target { $path }: No such file or directory +install-error-target-not-dir = target { $path } is not a directory +install-error-backup-failed = cannot backup { $from } to { $to } +install-error-install-failed = cannot install { $from } to { $to } +install-error-strip-failed = strip program failed: { $error } +install-error-strip-abnormal = strip process terminated abnormally - exit code: { $code } +install-error-metadata-failed = metadata error +install-error-invalid-user = invalid user: { $user } +install-error-invalid-group = invalid group: { $group } +install-error-omitting-directory = omitting directory { $path } +install-error-not-a-directory = failed to access { $path }: Not a directory +install-error-override-directory-failed = cannot overwrite directory { $dir } with non-directory { $file } +install-error-same-file = '{ $file1 }' and '{ $file2 }' are the same file +install-error-extra-operand = extra operand { $operand } +{ $usage } +install-error-invalid-mode = Invalid mode string: { $error } +install-error-mutually-exclusive-target = Options --target-directory and --no-target-directory are mutually exclusive +install-error-mutually-exclusive-compare-preserve = Options --compare and --preserve-timestamps are mutually exclusive +install-error-mutually-exclusive-compare-strip = Options --compare and --strip are mutually exclusive +install-error-missing-file-operand = missing file operand +install-error-missing-destination-operand = missing destination file operand after '{ $path }' +install-error-failed-to-remove = Failed to remove existing file { $path }. Error: { $error } + +# Verbose output +install-verbose-creating-directory = creating directory { $path } +install-verbose-creating-directory-step = install: creating directory { $path } +install-verbose-removed = removed { $path } +install-verbose-copy = { $from } -> { $to } +install-verbose-backup = (backup: { $backup }) diff --git a/src/uu/install/locales/fr-FR.ftl b/src/uu/install/locales/fr-FR.ftl new file mode 100644 index 000000000..10cc217b1 --- /dev/null +++ b/src/uu/install/locales/fr-FR.ftl @@ -0,0 +1,56 @@ +install-about = Copier SOURCE vers DEST ou plusieurs SOURCE(s) vers le + RÉPERTOIRE existant, tout en définissant les modes de permission et propriétaire/groupe +install-usage = install [OPTION]... [FICHIER]... + +# Messages d'aide +install-help-ignored = ignoré +install-help-compare = comparer chaque paire de fichiers source et destination, et dans certains cas, ne pas modifier la destination du tout +install-help-directory = traiter tous les arguments comme des noms de répertoires. créer tous les composants des répertoires spécifiés +install-help-create-leading = créer tous les composants principaux de DEST sauf le dernier, puis copier SOURCE vers DEST +install-help-group = définir la propriété du groupe, au lieu du groupe actuel du processus +install-help-mode = définir le mode de permission (comme dans chmod), au lieu de rwxr-xr-x +install-help-owner = définir la propriété (super-utilisateur uniquement) +install-help-preserve-timestamps = appliquer les temps d'accès/modification des fichiers SOURCE aux fichiers de destination correspondants +install-help-strip = supprimer les tables de symboles (aucune action Windows) +install-help-strip-program = programme utilisé pour supprimer les binaires (aucune action Windows) +install-help-target-directory = déplacer tous les arguments SOURCE dans RÉPERTOIRE +install-help-no-target-directory = traiter DEST comme un fichier normal +install-help-verbose = expliquer ce qui est fait +install-help-preserve-context = préserver le contexte de sécurité +install-help-context = définir le contexte de sécurité des fichiers et répertoires + +# Messages d'erreur +install-error-dir-needs-arg = { $util_name } avec -d nécessite au moins un argument. +install-error-create-dir-failed = échec de la création de { $path } +install-error-chmod-failed = échec du chmod { $path } +install-error-chmod-failed-detailed = { $path } : échec du chmod avec l'erreur { $error } +install-error-chown-failed = échec du chown { $path } : { $error } +install-error-invalid-target = cible invalide { $path } : Aucun fichier ou répertoire de ce type +install-error-target-not-dir = la cible { $path } n'est pas un répertoire +install-error-backup-failed = impossible de sauvegarder { $from } vers { $to } +install-error-install-failed = impossible d'installer { $from } vers { $to } +install-error-strip-failed = échec du programme strip : { $error } +install-error-strip-abnormal = le processus strip s'est terminé anormalement - code de sortie : { $code } +install-error-metadata-failed = erreur de métadonnées +install-error-invalid-user = utilisateur invalide : { $user } +install-error-invalid-group = groupe invalide : { $group } +install-error-omitting-directory = omission du répertoire { $path } +install-error-not-a-directory = échec de l'accès à { $path } : N'est pas un répertoire +install-error-override-directory-failed = impossible d'écraser le répertoire { $dir } avec un non-répertoire { $file } +install-error-same-file = '{ $file1 }' et '{ $file2 }' sont le même fichier +install-error-extra-operand = opérande supplémentaire { $operand } + { $usage } +install-error-invalid-mode = Chaîne de mode invalide : { $error } +install-error-mutually-exclusive-target = Les options --target-directory et --no-target-directory sont mutuellement exclusives +install-error-mutually-exclusive-compare-preserve = Les options --compare et --preserve-timestamps sont mutuellement exclusives +install-error-mutually-exclusive-compare-strip = Les options --compare et --strip sont mutuellement exclusives +install-error-missing-file-operand = opérande de fichier manquant +install-error-missing-destination-operand = opérande de fichier de destination manquant après '{ $path }' +install-error-failed-to-remove = Échec de la suppression du fichier existant { $path }. Erreur : { $error } + +# Sortie détaillée +install-verbose-creating-directory = création du répertoire { $path } +install-verbose-creating-directory-step = install : création du répertoire { $path } +install-verbose-removed = supprimé { $path } +install-verbose-copy = { $from } -> { $to } +install-verbose-backup = (sauvegarde : { $backup }) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 4d93a3fc4..db7cfb4ce 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -10,6 +10,7 @@ mod mode; use clap::{Arg, ArgAction, ArgMatches, Command}; use file_diff::diff; use filetime::{FileTime, set_file_times}; +use std::collections::HashMap; use std::fmt::Debug; use std::fs::File; use std::fs::{self, metadata}; @@ -33,7 +34,7 @@ use uucore::{format_usage, show, show_error, show_if_err}; use std::os::unix::fs::{FileTypeExt, MetadataExt}; #[cfg(unix)] use std::os::unix::prelude::OsStrExt; -use uucore::locale::get_message; +use uucore::locale::{get_message, get_message_with_args}; const DEFAULT_MODE: u32 = 0o755; const DEFAULT_STRIP_PROGRAM: &str = "strip"; @@ -60,55 +61,55 @@ pub struct Behavior { #[derive(Error, Debug)] enum InstallError { - #[error("{} with -d requires at least one argument.", uucore::util_name())] + #[error("{}", get_message_with_args("install-error-dir-needs-arg", HashMap::from([("util_name".to_string(), uucore::util_name().to_string())])))] DirNeedsArg, - #[error("failed to create {0}")] + #[error("{}", get_message_with_args("install-error-create-dir-failed", HashMap::from([("path".to_string(), .0.quote().to_string())])))] CreateDirFailed(PathBuf, #[source] std::io::Error), - #[error("failed to chmod {}", .0.quote())] + #[error("{}", get_message_with_args("install-error-chmod-failed", HashMap::from([("path".to_string(), .0.quote().to_string())])))] ChmodFailed(PathBuf), - #[error("failed to chown {}: {}", .0.quote(), .1)] + #[error("{}", get_message_with_args("install-error-chown-failed", HashMap::from([("path".to_string(), .0.quote().to_string()), ("error".to_string(), .1.clone())])))] ChownFailed(PathBuf, String), - #[error("invalid target {}: No such file or directory", .0.quote())] + #[error("{}", get_message_with_args("install-error-invalid-target", HashMap::from([("path".to_string(), .0.quote().to_string())])))] InvalidTarget(PathBuf), - #[error("target {} is not a directory", .0.quote())] + #[error("{}", get_message_with_args("install-error-target-not-dir", HashMap::from([("path".to_string(), .0.quote().to_string())])))] TargetDirIsntDir(PathBuf), - #[error("cannot backup {0} to {1}")] + #[error("{}", get_message_with_args("install-error-backup-failed", HashMap::from([("from".to_string(), .0.to_string_lossy().to_string()), ("to".to_string(), .1.to_string_lossy().to_string())])))] BackupFailed(PathBuf, PathBuf, #[source] std::io::Error), - #[error("cannot install {0} to {1}")] + #[error("{}", get_message_with_args("install-error-install-failed", HashMap::from([("from".to_string(), .0.to_string_lossy().to_string()), ("to".to_string(), .1.to_string_lossy().to_string())])))] InstallFailed(PathBuf, PathBuf, #[source] std::io::Error), - #[error("strip program failed: {0}")] + #[error("{}", get_message_with_args("install-error-strip-failed", HashMap::from([("error".to_string(), .0.clone())])))] StripProgramFailed(String), - #[error("metadata error")] + #[error("{}", get_message("install-error-metadata-failed"))] MetadataFailed(#[source] std::io::Error), - #[error("invalid user: {}", .0.quote())] + #[error("{}", get_message_with_args("install-error-invalid-user", HashMap::from([("user".to_string(), .0.quote().to_string())])))] InvalidUser(String), - #[error("invalid group: {}", .0.quote())] + #[error("{}", get_message_with_args("install-error-invalid-group", HashMap::from([("group".to_string(), .0.quote().to_string())])))] InvalidGroup(String), - #[error("omitting directory {}", .0.quote())] + #[error("{}", get_message_with_args("install-error-omitting-directory", HashMap::from([("path".to_string(), .0.quote().to_string())])))] OmittingDirectory(PathBuf), - #[error("failed to access {}: Not a directory", .0.quote())] + #[error("{}", get_message_with_args("install-error-not-a-directory", HashMap::from([("path".to_string(), .0.quote().to_string())])))] NotADirectory(PathBuf), - #[error("cannot overwrite directory {} with non-directory {}", .0.quote(), .1.quote())] + #[error("{}", get_message_with_args("install-error-override-directory-failed", HashMap::from([("dir".to_string(), .0.quote().to_string()), ("file".to_string(), .1.quote().to_string())])))] OverrideDirectoryFailed(PathBuf, PathBuf), - #[error("'{0}' and '{1}' are the same file")] + #[error("{}", get_message_with_args("install-error-same-file", HashMap::from([("file1".to_string(), .0.to_string_lossy().to_string()), ("file2".to_string(), .1.to_string_lossy().to_string())])))] SameFile(PathBuf, PathBuf), - #[error("extra operand {}\n{}", .0.quote(), .1.quote())] + #[error("{}", get_message_with_args("install-error-extra-operand", HashMap::from([("operand".to_string(), .0.quote().to_string()), ("usage".to_string(), .1.clone())])))] ExtraOperand(String, String), #[cfg(feature = "selinux")] @@ -191,57 +192,48 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_IGNORED) .short('c') - .help("ignored") + .help(get_message("install-help-ignored")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_COMPARE) .short('C') .long(OPT_COMPARE) - .help( - "compare each pair of source and destination files, and in some cases, \ - do not modify the destination at all", - ) + .help(get_message("install-help-compare")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_DIRECTORY) .short('d') .long(OPT_DIRECTORY) - .help( - "treat all arguments as directory names. create all components of \ - the specified directories", - ) + .help(get_message("install-help-directory")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_CREATE_LEADING) .short('D') - .help( - "create all leading components of DEST except the last, then copy \ - SOURCE to DEST", - ) + .help(get_message("install-help-create-leading")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_GROUP) .short('g') .long(OPT_GROUP) - .help("set group ownership, instead of process's current group") + .help(get_message("install-help-group")) .value_name("GROUP"), ) .arg( Arg::new(OPT_MODE) .short('m') .long(OPT_MODE) - .help("set permission mode (as in chmod), instead of rwxr-xr-x") + .help(get_message("install-help-mode")) .value_name("MODE"), ) .arg( Arg::new(OPT_OWNER) .short('o') .long(OPT_OWNER) - .help("set ownership (super-user only)") + .help(get_message("install-help-owner")) .value_name("OWNER") .value_hint(clap::ValueHint::Username), ) @@ -249,23 +241,20 @@ pub fn uu_app() -> Command { Arg::new(OPT_PRESERVE_TIMESTAMPS) .short('p') .long(OPT_PRESERVE_TIMESTAMPS) - .help( - "apply access/modification times of SOURCE files to \ - corresponding destination files", - ) + .help(get_message("install-help-preserve-timestamps")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_STRIP) .short('s') .long(OPT_STRIP) - .help("strip symbol tables (no action Windows)") + .help(get_message("install-help-strip")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_STRIP_PROGRAM) .long(OPT_STRIP_PROGRAM) - .help("program used to strip binaries (no action Windows)") + .help(get_message("install-help-strip-program")) .value_name("PROGRAM") .value_hint(clap::ValueHint::CommandName), ) @@ -274,7 +263,7 @@ pub fn uu_app() -> Command { Arg::new(OPT_TARGET_DIRECTORY) .short('t') .long(OPT_TARGET_DIRECTORY) - .help("move all SOURCE arguments into DIRECTORY") + .help(get_message("install-help-target-directory")) .value_name("DIRECTORY") .value_hint(clap::ValueHint::DirPath), ) @@ -282,28 +271,28 @@ pub fn uu_app() -> Command { Arg::new(OPT_NO_TARGET_DIRECTORY) .short('T') .long(OPT_NO_TARGET_DIRECTORY) - .help("treat DEST as a normal file") + .help(get_message("install-help-no-target-directory")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_VERBOSE) .short('v') .long(OPT_VERBOSE) - .help("explain what is being done") + .help(get_message("install-help-verbose")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_PRESERVE_CONTEXT) .short('P') .long(OPT_PRESERVE_CONTEXT) - .help("preserve security context") + .help(get_message("install-help-preserve-context")) .action(ArgAction::SetTrue), ) .arg( Arg::new(OPT_CONTEXT) .short('Z') .long(OPT_CONTEXT) - .help("set security context of files and directories") + .help(get_message("install-help-context")) .value_name("CONTEXT") .value_parser(clap::value_parser!(String)) .num_args(0..=1), @@ -336,7 +325,13 @@ fn behavior(matches: &ArgMatches) -> UResult { let specified_mode: Option = if matches.contains_id(OPT_MODE) { let x = matches.get_one::(OPT_MODE).ok_or(1)?; Some(mode::parse(x, considering_dir, get_umask()).map_err(|err| { - show_error!("Invalid mode string: {err}"); + show_error!( + "{}", + get_message_with_args( + "install-error-invalid-mode", + HashMap::from([("error".to_string(), err)]) + ) + ); 1 })?) } else { @@ -347,7 +342,7 @@ fn behavior(matches: &ArgMatches) -> UResult { let target_dir = matches.get_one::(OPT_TARGET_DIRECTORY).cloned(); let no_target_dir = matches.get_flag(OPT_NO_TARGET_DIRECTORY); if target_dir.is_some() && no_target_dir { - show_error!("Options --target-directory and --no-target-directory are mutually exclusive"); + show_error!("{}", get_message("install-error-mutually-exclusive-target")); return Err(1.into()); } @@ -355,11 +350,17 @@ fn behavior(matches: &ArgMatches) -> UResult { let compare = matches.get_flag(OPT_COMPARE); let strip = matches.get_flag(OPT_STRIP); if preserve_timestamps && compare { - show_error!("Options --compare and --preserve-timestamps are mutually exclusive"); + show_error!( + "{}", + get_message("install-error-mutually-exclusive-compare-preserve") + ); return Err(1.into()); } if compare && strip { - show_error!("Options --compare and --strip are mutually exclusive"); + show_error!( + "{}", + get_message("install-error-mutually-exclusive-compare-strip") + ); return Err(1.into()); } @@ -456,7 +457,16 @@ fn directory(paths: &[String], b: &Behavior) -> UResult<()> { } if b.verbose { - println!("creating directory {}", path_to_create.quote()); + println!( + "{}", + get_message_with_args( + "install-verbose-creating-directory", + HashMap::from([( + "path".to_string(), + path_to_create.quote().to_string() + )]) + ) + ); } } @@ -511,7 +521,10 @@ fn is_potential_directory_path(path: &Path) -> bool { fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { // first check that paths contains at least one element if paths.is_empty() { - return Err(UUsageError::new(1, "missing file operand")); + return Err(UUsageError::new( + 1, + get_message("install-error-missing-file-operand"), + )); } if b.no_target_dir && paths.len() > 2 { return Err(InstallError::ExtraOperand( @@ -531,9 +544,9 @@ fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { if paths.is_empty() { return Err(UUsageError::new( 1, - format!( - "missing destination file operand after '{}'", - last_path.to_str().unwrap() + get_message_with_args( + "install-error-missing-destination-operand", + HashMap::from([("path".to_string(), last_path.to_str().unwrap().to_string())]), ), )); } @@ -570,7 +583,16 @@ fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { result.push(part.as_os_str()); if !result.is_dir() { // Don't display when the directory already exists - println!("install: creating directory {}", result.quote()); + println!( + "{}", + get_message_with_args( + "install-verbose-creating-directory-step", + HashMap::from([( + "path".to_string(), + result.quote().to_string() + )]) + ) + ); } } } @@ -716,7 +738,13 @@ fn chown_optional_user_group(path: &Path, b: &Behavior) -> UResult<()> { fn perform_backup(to: &Path, b: &Behavior) -> UResult> { if to.exists() { if b.verbose { - println!("removed {}", to.quote()); + println!( + "{}", + get_message_with_args( + "install-verbose-removed", + HashMap::from([("path".to_string(), to.quote().to_string())]) + ) + ); } let backup_path = backup_control::get_backup_path(b.backup_mode, to, &b.suffix); if let Some(ref backup_path) = backup_path { @@ -777,8 +805,14 @@ fn copy_file(from: &Path, to: &Path) -> UResult<()> { if let Err(e) = fs::remove_file(to) { if e.kind() != std::io::ErrorKind::NotFound { show_error!( - "Failed to remove existing file {}. Error: {e:?}", - to.display(), + "{}", + get_message_with_args( + "install-error-failed-to-remove", + HashMap::from([ + ("path".to_string(), to.display().to_string()), + ("error".to_string(), format!("{e:?}")) + ]) + ) ); } } @@ -832,9 +866,9 @@ fn strip_file(to: &Path, b: &Behavior) -> UResult<()> { if !status.success() { // Follow GNU's behavior: if strip fails, removes the target let _ = fs::remove_file(to); - return Err(InstallError::StripProgramFailed(format!( - "strip process terminated abnormally - exit code: {}", - status.code().unwrap() + return Err(InstallError::StripProgramFailed(get_message_with_args( + "install-error-strip-abnormal", + HashMap::from([("code".to_string(), status.code().unwrap().to_string())]), )) .into()); } @@ -940,9 +974,24 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> { } if b.verbose { - print!("{} -> {}", from.quote(), to.quote()); + print!( + "{}", + get_message_with_args( + "install-verbose-copy", + HashMap::from([ + ("from".to_string(), from.quote().to_string()), + ("to".to_string(), to.quote().to_string()) + ]) + ) + ); match backup_path { - Some(path) => println!(" (backup: {})", path.quote()), + Some(path) => println!( + " {}", + get_message_with_args( + "install-verbose-backup", + HashMap::from([("backup".to_string(), path.quote().to_string())]) + ) + ), None => println!(), } } diff --git a/src/uu/install/src/mode.rs b/src/uu/install/src/mode.rs index 5fcb9f332..54bf25c31 100644 --- a/src/uu/install/src/mode.rs +++ b/src/uu/install/src/mode.rs @@ -2,8 +2,10 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use std::collections::HashMap; use std::fs; use std::path::Path; +use uucore::locale::get_message_with_args; #[cfg(not(windows))] use uucore::mode; @@ -25,7 +27,16 @@ pub fn chmod(path: &Path, mode: u32) -> Result<(), ()> { use std::os::unix::fs::PermissionsExt; use uucore::{display::Quotable, show_error}; fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|err| { - show_error!("{}: chmod failed with error {err}", path.maybe_quote()); + show_error!( + "{}", + get_message_with_args( + "install-error-chmod-failed-detailed", + HashMap::from([ + ("path".to_string(), path.maybe_quote().to_string()), + ("error".to_string(), err.to_string()) + ]) + ) + ); }) }