From 53e3e665ca1dc435a13507fce61aa907f8d2cf0a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 16 Jun 2025 20:53:28 +0200 Subject: [PATCH] l10n: port cp for translation + add french --- src/uu/cp/locales/en-US.ftl | 101 ++++++- src/uu/cp/locales/fr-FR.ftl | 116 +++++++ src/uu/cp/src/copydir.rs | 48 ++- src/uu/cp/src/cp.rs | 437 +++++++++++++++++---------- src/uu/cp/src/platform/linux.rs | 4 +- src/uu/cp/src/platform/macos.rs | 17 +- src/uu/cp/src/platform/other.rs | 7 +- src/uu/cp/src/platform/other_unix.rs | 7 +- 8 files changed, 553 insertions(+), 184 deletions(-) create mode 100644 src/uu/cp/locales/fr-FR.ftl diff --git a/src/uu/cp/locales/en-US.ftl b/src/uu/cp/locales/en-US.ftl index 7150d1b88..bc39d6132 100644 --- a/src/uu/cp/locales/en-US.ftl +++ b/src/uu/cp/locales/en-US.ftl @@ -14,4 +14,103 @@ cp-after-help = Do not copy a non-directory that has an existing destination wit - all This is the default operation when an --update option is not specified, and results in all existing files in the destination being replaced. - none This is similar to the --no-clobber option, in that no files in the destination are replaced, but also skipping a file does not induce a failure. - - older This is the default operation when --update is specified, and results in files being replaced if they’re older than the corresponding source file. + - older This is the default operation when --update is specified, and results in files being replaced if they're older than the corresponding source file. + +# Help messages +cp-help-target-directory = copy all SOURCE arguments into target-directory +cp-help-no-target-directory = Treat DEST as a regular file and not a directory +cp-help-interactive = ask before overwriting files +cp-help-link = hard-link files instead of copying +cp-help-no-clobber = don't overwrite a file that already exists +cp-help-recursive = copy directories recursively +cp-help-strip-trailing-slashes = remove any trailing slashes from each SOURCE argument +cp-help-debug = explain how a file is copied. Implies -v +cp-help-verbose = explicitly state what is being done +cp-help-symbolic-link = make symbolic links instead of copying +cp-help-force = if an existing destination file cannot be opened, remove it and try again (this option is ignored when the -n option is also used). Currently not implemented for Windows. +cp-help-remove-destination = remove each existing destination file before attempting to open it (contrast with --force). On Windows, currently only works for writeable files. +cp-help-reflink = control clone/CoW copies. See below +cp-help-attributes-only = Don't copy the file data, just the attributes +cp-help-preserve = Preserve the specified attributes (default: mode, ownership (unix only), timestamps), if possible additional attributes: context, links, xattr, all +cp-help-preserve-default = same as --preserve=mode,ownership(unix only),timestamps +cp-help-no-preserve = don't preserve the specified attributes +cp-help-parents = use full source file name under DIRECTORY +cp-help-no-dereference = never follow symbolic links in SOURCE +cp-help-dereference = always follow symbolic links in SOURCE +cp-help-cli-symbolic-links = follow command-line symbolic links in SOURCE +cp-help-archive = Same as -dR --preserve=all +cp-help-no-dereference-preserve-links = same as --no-dereference --preserve=links +cp-help-one-file-system = stay on this file system +cp-help-sparse = control creation of sparse files. See below +cp-help-selinux = set SELinux security context of destination file to default type +cp-help-context = like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX +cp-help-progress = Display a progress bar. Note: this feature is not supported by GNU coreutils. +cp-help-copy-contents = NotImplemented: copy contents of special files when recursive + +# Error messages +cp-error-missing-file-operand = missing file operand +cp-error-missing-destination-operand = missing destination file operand after { $source } +cp-error-extra-operand = extra operand { $operand } +cp-error-same-file = { $source } and { $dest } are the same file +cp-error-backing-up-destroy-source = backing up { $dest } might destroy source; { $source } not copied +cp-error-cannot-open-for-reading = cannot open { $source } for reading +cp-error-not-writing-dangling-symlink = not writing through dangling symlink { $dest } +cp-error-failed-to-clone = failed to clone { $source } from { $dest }: { $error } +cp-error-cannot-change-attribute = cannot change attribute { $dest }: Source file is a non regular file +cp-error-cannot-stat = cannot stat { $source }: No such file or directory +cp-error-cannot-create-symlink = cannot create symlink { $dest } to { $source } +cp-error-cannot-create-hard-link = cannot create hard link { $dest } to { $source } +cp-error-omitting-directory = -r not specified; omitting directory { $dir } +cp-error-cannot-copy-directory-into-itself = cannot copy a directory, { $source }, into itself, { $dest } +cp-error-will-not-copy-through-symlink = will not copy { $source } through just-created symlink { $dest } +cp-error-will-not-overwrite-just-created = will not overwrite just-created { $dest } with { $source } +cp-error-target-not-directory = target: { $target } is not a directory +cp-error-cannot-overwrite-directory-with-non-directory = cannot overwrite directory { $dir } with non-directory +cp-error-cannot-overwrite-non-directory-with-directory = cannot overwrite non-directory with directory +cp-error-with-parents-dest-must-be-dir = with --parents, the destination must be a directory +cp-error-not-replacing = not replacing { $file } +cp-error-failed-get-current-dir = failed to get current directory { $error } +cp-error-failed-set-permissions = cannot set permissions { $path } +cp-error-backup-mutually-exclusive = options --backup and --no-clobber are mutually exclusive +cp-error-invalid-argument = invalid argument { $arg } for '{ $option }' +cp-error-option-not-implemented = Option '{ $option }' not yet implemented. +cp-error-not-all-files-copied = Not all files were copied +cp-error-reflink-always-sparse-auto = `--reflink=always` can be used only with --sparse=auto +cp-error-file-exists = { $path }: File exists +cp-error-invalid-backup-argument = --backup is mutually exclusive with -n or --update=none-fail +cp-error-reflink-not-supported = --reflink is only supported on linux and macOS +cp-error-sparse-not-supported = --sparse is only supported on linux +cp-error-not-a-directory = { $path } is not a directory +cp-error-selinux-not-enabled = SELinux was not enabled during the compile time! +cp-error-selinux-set-context = failed to set the security context of { $path }: { $error } +cp-error-selinux-get-context = failed to get security context of { $path } +cp-error-selinux-error = SELinux error: { $error } +cp-error-cannot-create-fifo = cannot create fifo { $path }: File exists +cp-error-invalid-attribute = invalid attribute { $value } +cp-error-failed-to-create-whole-tree = failed to create whole tree +cp-error-failed-to-create-directory = Failed to create directory: { $error } +cp-error-backup-format = cp: { $error } + Try '{ $exec } --help' for more information. + +# Debug enum strings +cp-debug-enum-no = no +cp-debug-enum-yes = yes +cp-debug-enum-avoided = avoided +cp-debug-enum-unsupported = unsupported +cp-debug-enum-unknown = unknown +cp-debug-enum-zeros = zeros +cp-debug-enum-seek-hole = SEEK_HOLE +cp-debug-enum-seek-hole-zeros = SEEK_HOLE + zeros + +# Warning messages +cp-warning-source-specified-more-than-once = source { $file_type } { $source } specified more than once + +# Verbose and debug messages +cp-verbose-copied = { $source } -> { $dest } +cp-debug-skipped = skipped { $path } +cp-verbose-created-directory = { $source } -> { $dest } +cp-debug-copy-offload = copy offload: { $offload }, reflink: { $reflink }, sparse detection: { $sparse } + +# Prompts +cp-prompt-overwrite = overwrite { $path }? +cp-prompt-overwrite-with-mode = replace { $path }, overriding mode diff --git a/src/uu/cp/locales/fr-FR.ftl b/src/uu/cp/locales/fr-FR.ftl new file mode 100644 index 000000000..2fea7cf4d --- /dev/null +++ b/src/uu/cp/locales/fr-FR.ftl @@ -0,0 +1,116 @@ +cp-about = Copier SOURCE vers DEST, ou plusieurs SOURCE(s) vers RÉPERTOIRE. +cp-usage = cp [OPTION]... [-T] SOURCE DEST + cp [OPTION]... SOURCE... RÉPERTOIRE + cp [OPTION]... -t RÉPERTOIRE SOURCE... +cp-after-help = Ne pas copier un non-répertoire qui a une destination existante avec le même horodatage de modification ou plus récent ; + à la place, ignorer silencieusement le fichier sans échec. Si les horodatages sont préservés, la comparaison est faite avec + l'horodatage source tronqué aux résolutions du système de fichiers de destination et des appels système utilisés pour + mettre à jour les horodatages ; cela évite le travail en double si plusieurs commandes cp -pu sont exécutées avec la même source + et destination. Cette option est ignorée si l'option -n ou --no-clobber est également spécifiée. De plus, si + --preserve=links est également spécifié (comme avec cp -au par exemple), cela aura la priorité ; par conséquent, + selon l'ordre dans lequel les fichiers sont traités depuis la source, les fichiers plus récents dans la destination peuvent être remplacés, + pour refléter les liens durs dans la source. ce qui donne plus de contrôle sur les fichiers existants dans la destination qui sont + remplacés, et sa valeur peut être l'une des suivantes : + + - all C'est l'opération par défaut lorsqu'une option --update n'est pas spécifiée, et entraîne le remplacement de tous les fichiers existants dans la destination. + - none Cela est similaire à l'option --no-clobber, en ce sens qu'aucun fichier dans la destination n'est remplacé, mais ignorer un fichier n'induit pas d'échec. + - older C'est l'opération par défaut lorsque --update est spécifié, et entraîne le remplacement des fichiers s'ils sont plus anciens que le fichier source correspondant. + +# Messages d'aide +cp-help-target-directory = copier tous les arguments SOURCE dans le répertoire cible +cp-help-no-target-directory = Traiter DEST comme un fichier régulier et non comme un répertoire +cp-help-interactive = demander avant d'écraser les fichiers +cp-help-link = créer des liens durs au lieu de copier +cp-help-no-clobber = ne pas écraser un fichier qui existe déjà +cp-help-recursive = copier les répertoires récursivement +cp-help-strip-trailing-slashes = supprimer les barres obliques finales de chaque argument SOURCE +cp-help-debug = expliquer comment un fichier est copié. Implique -v +cp-help-verbose = indiquer explicitement ce qui est fait +cp-help-symbolic-link = créer des liens symboliques au lieu de copier +cp-help-force = si un fichier de destination existant ne peut pas être ouvert, le supprimer et réessayer (cette option est ignorée lorsque l'option -n est également utilisée). Actuellement non implémenté pour Windows. +cp-help-remove-destination = supprimer chaque fichier de destination existant avant de tenter de l'ouvrir (contraste avec --force). Sur Windows, ne fonctionne actuellement que pour les fichiers inscriptibles. +cp-help-reflink = contrôler les copies clone/CoW. Voir ci-dessous +cp-help-attributes-only = Ne pas copier les données du fichier, juste les attributs +cp-help-preserve = Préserver les attributs spécifiés (par défaut : mode, propriété (unix uniquement), horodatages), si possible attributs supplémentaires : contexte, liens, xattr, all +cp-help-preserve-default = identique à --preserve=mode,ownership(unix uniquement),timestamps +cp-help-no-preserve = ne pas préserver les attributs spécifiés +cp-help-parents = utiliser le nom complet du fichier source sous RÉPERTOIRE +cp-help-no-dereference = ne jamais suivre les liens symboliques dans SOURCE +cp-help-dereference = toujours suivre les liens symboliques dans SOURCE +cp-help-cli-symbolic-links = suivre les liens symboliques de la ligne de commande dans SOURCE +cp-help-archive = Identique à -dR --preserve=all +cp-help-no-dereference-preserve-links = identique à --no-dereference --preserve=links +cp-help-one-file-system = rester sur ce système de fichiers +cp-help-sparse = contrôler la création de fichiers épars. Voir ci-dessous +cp-help-selinux = définir le contexte de sécurité SELinux du fichier de destination au type par défaut +cp-help-context = comme -Z, ou si CTX est spécifié, définir le contexte de sécurité SELinux ou SMACK à CTX +cp-help-progress = Afficher une barre de progression. Note : cette fonctionnalité n'est pas supportée par GNU coreutils. +cp-help-copy-contents = Non implémenté : copier le contenu des fichiers spéciaux lors de la récursion + +# Messages d'erreur +cp-error-missing-file-operand = opérande fichier manquant +cp-error-missing-destination-operand = opérande fichier de destination manquant après { $source } +cp-error-extra-operand = opérande supplémentaire { $operand } +cp-error-same-file = { $source } et { $dest } sont le même fichier +cp-error-backing-up-destroy-source = sauvegarder { $dest } pourrait détruire la source ; { $source } non copié +cp-error-cannot-open-for-reading = impossible d'ouvrir { $source } en lecture +cp-error-not-writing-dangling-symlink = ne pas écrire à travers le lien symbolique pendant { $dest } +cp-error-failed-to-clone = échec du clonage de { $source } depuis { $dest } : { $error } +cp-error-cannot-change-attribute = impossible de changer l'attribut { $dest } : Le fichier source n'est pas un fichier régulier +cp-error-cannot-stat = impossible de faire stat sur { $source } : Aucun fichier ou répertoire de ce type +cp-error-cannot-create-symlink = impossible de créer le lien symbolique { $dest } vers { $source } +cp-error-cannot-create-hard-link = impossible de créer le lien dur { $dest } vers { $source } +cp-error-omitting-directory = -r non spécifié ; répertoire { $dir } omis +cp-error-cannot-copy-directory-into-itself = impossible de copier un répertoire, { $source }, dans lui-même, { $dest } +cp-error-will-not-copy-through-symlink = ne copiera pas { $source } à travers le lien symbolique tout juste créé { $dest } +cp-error-will-not-overwrite-just-created = n'écrasera pas le fichier tout juste créé { $dest } avec { $source } +cp-error-target-not-directory = cible : { $target } n'est pas un répertoire +cp-error-cannot-overwrite-directory-with-non-directory = impossible d'écraser le répertoire { $dir } avec un non-répertoire +cp-error-cannot-overwrite-non-directory-with-directory = impossible d'écraser un non-répertoire avec un répertoire +cp-error-with-parents-dest-must-be-dir = avec --parents, la destination doit être un répertoire +cp-error-not-replacing = ne remplace pas { $file } +cp-error-failed-get-current-dir = échec de l'obtention du répertoire actuel { $error } +cp-error-failed-set-permissions = impossible de définir les permissions { $path } +cp-error-backup-mutually-exclusive = les options --backup et --no-clobber sont mutuellement exclusives +cp-error-invalid-argument = argument invalide { $arg } pour '{ $option }' +cp-error-option-not-implemented = Option '{ $option }' pas encore implémentée. +cp-error-not-all-files-copied = Tous les fichiers n'ont pas été copiés +cp-error-reflink-always-sparse-auto = `--reflink=always` ne peut être utilisé qu'avec --sparse=auto +cp-error-file-exists = { $path } : Le fichier existe +cp-error-invalid-backup-argument = --backup est mutuellement exclusif avec -n ou --update=none-fail +cp-error-reflink-not-supported = --reflink n'est supporté que sur linux et macOS +cp-error-sparse-not-supported = --sparse n'est supporté que sur linux +cp-error-not-a-directory = { $path } n'est pas un répertoire +cp-error-selinux-not-enabled = SELinux n'était pas activé lors de la compilation ! +cp-error-selinux-set-context = échec de la définition du contexte de sécurité de { $path } : { $error } +cp-error-selinux-get-context = échec de l'obtention du contexte de sécurité de { $path } +cp-error-selinux-error = Erreur SELinux : { $error } +cp-error-cannot-create-fifo = impossible de créer le fifo { $path } : Le fichier existe +cp-error-invalid-attribute = attribut invalide { $value } +cp-error-failed-to-create-whole-tree = échec de la création de l'arborescence complète +cp-error-failed-to-create-directory = Échec de la création du répertoire : { $error } +cp-error-backup-format = cp : { $error } + Tentez '{ $exec } --help' pour plus d'informations. + +# Debug enum strings +cp-debug-enum-no = non +cp-debug-enum-yes = oui +cp-debug-enum-avoided = évité +cp-debug-enum-unsupported = non supporté +cp-debug-enum-unknown = inconnu +cp-debug-enum-zeros = zéros +cp-debug-enum-seek-hole = SEEK_HOLE +cp-debug-enum-seek-hole-zeros = SEEK_HOLE + zéros + +# Messages d'avertissement +cp-warning-source-specified-more-than-once = { $file_type } source { $source } spécifié plus d'une fois + +# Messages verbeux et de débogage +cp-verbose-copied = { $source } -> { $dest } +cp-debug-skipped = { $path } ignoré +cp-verbose-created-directory = { $source } -> { $dest } +cp-debug-copy-offload = copy offload : { $offload }, reflink : { $reflink }, sparse detection : { $sparse } + +# Invites +cp-prompt-overwrite = écraser { $path } ? +cp-prompt-overwrite-with-mode = remplacer { $path }, en écrasant le mode diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index be81b260f..251c8c5af 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -20,6 +20,7 @@ use uucore::error::UIoError; use uucore::fs::{ FileInformation, MissingHandling, ResolveMode, canonicalize, path_ends_with_terminator, }; +use uucore::locale::{get_message, get_message_with_args}; use uucore::show; use uucore::show_error; use uucore::uio_error; @@ -183,7 +184,13 @@ impl Entry { let source_is_dir = source.is_dir(); if path_ends_with_terminator(context.target) && source_is_dir { if let Err(e) = fs::create_dir_all(context.target) { - eprintln!("Failed to create directory: {e}"); + eprintln!( + "{}", + get_message_with_args( + "cp-error-failed-to-create-directory", + HashMap::from([("error".to_string(), e.to_string())]) + ) + ); } } else { descendant = descendant.strip_prefix(context.root)?.to_path_buf(); @@ -229,7 +236,7 @@ fn copy_direntry( // exist, ... if source_absolute.is_dir() && !local_to_target.exists() { return if target_is_file { - Err("cannot overwrite non-directory with directory".into()) + Err(get_message("cp-error-cannot-overwrite-non-directory-with-directory").into()) } else { build_dir(&local_to_target, false, options, Some(&source_absolute))?; if options.verbose { @@ -269,8 +276,14 @@ fn copy_direntry( CpError::IoErrContext(e, _) if e.kind() == io::ErrorKind::PermissionDenied => { show!(uio_error!( e, - "cannot open {} for reading", - source_relative.quote(), + "{}", + get_message_with_args( + "cp-error-cannot-open-for-reading", + HashMap::from([( + "source".to_string(), + source_relative.quote().to_string() + )]) + ), )); } e => return Err(e), @@ -315,15 +328,24 @@ pub(crate) fn copy_directory( } if !options.recursive { - return Err(format!("-r not specified; omitting directory {}", root.quote()).into()); + return Err(get_message_with_args( + "cp-error-omitting-directory", + HashMap::from([("dir".to_string(), root.quote().to_string())]), + ) + .into()); } // check if root is a prefix of target if path_has_prefix(target, root)? { - return Err(format!( - "cannot copy a directory, {}, into itself, {}", - root.quote(), - target.join(root.file_name().unwrap()).quote() + return Err(get_message_with_args( + "cp-error-cannot-copy-directory-into-itself", + HashMap::from([ + ("source".to_string(), root.quote().to_string()), + ( + "dest".to_string(), + target.join(root.file_name().unwrap()).quote().to_string(), + ), + ]), ) .into()); } @@ -368,7 +390,13 @@ pub(crate) fn copy_directory( // the target directory. let context = match Context::new(root, target) { Ok(c) => c, - Err(e) => return Err(format!("failed to get current directory {e}").into()), + Err(e) => { + return Err(get_message_with_args( + "cp-error-failed-get-current-dir", + HashMap::from([("error".to_string(), e.to_string())]), + ) + .into()); + } }; // The directory we were in during the previous iteration diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 9436a66fe..e3f03a40d 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -41,7 +41,7 @@ use uucore::{ }; use crate::copydir::copy_directory; -use uucore::locale::get_message; +use uucore::locale::{get_message, get_message_with_args}; mod copydir; mod platform; @@ -62,7 +62,7 @@ pub enum CpError { /// Represents the state when a non-fatal error has occurred /// and not all files were copied. - #[error("Not all files were copied")] + #[error("{}", get_message("cp-error-not-all-files-copied"))] NotAllFilesCopied, /// Simple walkdir::Error wrapper @@ -80,21 +80,21 @@ pub enum CpError { #[error("Skipped copying file (exit with error = {0})")] Skipped(bool), - /// Result of a skipped file + /// Invalid argument error #[error("{0}")] InvalidArgument(String), /// All standard options are included as an an implementation /// path, but those that are not implemented yet should return /// a NotImplemented error. - #[error("Option '{0}' not yet implemented.")] + #[error("{}", get_message_with_args("cp-error-option-not-implemented", HashMap::from([("option".to_string(), 0.to_string())])))] NotImplemented(String), /// Invalid arguments to backup #[error(transparent)] Backup(#[from] BackupError), - #[error("'{}' is not a directory", .0.display())] + #[error("{}", get_message_with_args("cp-error-not-a-directory", HashMap::from([("path".to_string(), .0.quote().to_string())])))] NotADirectory(PathBuf), } @@ -118,9 +118,14 @@ impl Display for BackupError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "cp: {}\nTry '{} --help' for more information.", - self.0, - uucore::execution_phrase() + "{}", + get_message_with_args( + "cp-error-backup-format", + HashMap::from([ + ("error".to_string(), self.0.clone()), + ("exec".to_string(), uucore::execution_phrase().to_string()) + ]) + ) ) } } @@ -415,28 +420,30 @@ struct CopyDebug { sparse_detection: SparseDebug, } -impl OffloadReflinkDebug { - fn to_string(&self) -> &'static str { - match self { - Self::No => "no", - Self::Yes => "yes", - Self::Avoided => "avoided", - Self::Unsupported => "unsupported", - Self::Unknown => "unknown", - } +impl Display for OffloadReflinkDebug { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let msg = match self { + Self::No => get_message("cp-debug-enum-no"), + Self::Yes => get_message("cp-debug-enum-yes"), + Self::Avoided => get_message("cp-debug-enum-avoided"), + Self::Unsupported => get_message("cp-debug-enum-unsupported"), + Self::Unknown => get_message("cp-debug-enum-unknown"), + }; + write!(f, "{}", msg) } } -impl SparseDebug { - fn to_string(&self) -> &'static str { - match self { - Self::No => "no", - Self::Zeros => "zeros", - Self::SeekHole => "SEEK_HOLE", - Self::SeekHoleZeros => "SEEK_HOLE + zeros", - Self::Unsupported => "unsupported", - Self::Unknown => "unknown", - } +impl Display for SparseDebug { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let msg = match self { + Self::No => get_message("cp-debug-enum-no"), + Self::Zeros => get_message("cp-debug-enum-zeros"), + Self::SeekHole => get_message("cp-debug-enum-seek-hole"), + Self::SeekHoleZeros => get_message("cp-debug-enum-seek-hole-zeros"), + Self::Unsupported => get_message("cp-debug-enum-unsupported"), + Self::Unknown => get_message("cp-debug-enum-unknown"), + }; + write!(f, "{}", msg) } } @@ -445,10 +452,18 @@ impl SparseDebug { /// It prints the debug information of the offload, reflink, and sparse detection actions. fn show_debug(copy_debug: &CopyDebug) { println!( - "copy offload: {}, reflink: {}, sparse detection: {}", - copy_debug.offload.to_string(), - copy_debug.reflink.to_string(), - copy_debug.sparse_detection.to_string(), + "{}", + get_message_with_args( + "cp-debug-copy-offload", + HashMap::from([ + ("offload".to_string(), copy_debug.offload.to_string()), + ("reflink".to_string(), copy_debug.reflink.to_string()), + ( + "sparse".to_string(), + copy_debug.sparse_detection.to_string() + ), + ]) + ) ); } @@ -537,14 +552,14 @@ pub fn uu_app() -> Command { .value_name(options::TARGET_DIRECTORY) .value_hint(clap::ValueHint::DirPath) .value_parser(ValueParser::path_buf()) - .help("copy all SOURCE arguments into target-directory"), + .help(get_message("cp-help-target-directory")), ) .arg( Arg::new(options::NO_TARGET_DIRECTORY) .short('T') .long(options::NO_TARGET_DIRECTORY) .conflicts_with(options::TARGET_DIRECTORY) - .help("Treat DEST as a regular file and not a directory") + .help(get_message("cp-help-no-target-directory")) .action(ArgAction::SetTrue), ) .arg( @@ -552,7 +567,7 @@ pub fn uu_app() -> Command { .short('i') .long(options::INTERACTIVE) .overrides_with(options::NO_CLOBBER) - .help("ask before overwriting files") + .help(get_message("cp-help-interactive")) .action(ArgAction::SetTrue), ) .arg( @@ -560,7 +575,7 @@ pub fn uu_app() -> Command { .short('l') .long(options::LINK) .overrides_with_all(MODE_ARGS) - .help("hard-link files instead of copying") + .help(get_message("cp-help-link")) .action(ArgAction::SetTrue), ) .arg( @@ -568,7 +583,7 @@ pub fn uu_app() -> Command { .short('n') .long(options::NO_CLOBBER) .overrides_with(options::INTERACTIVE) - .help("don't overwrite a file that already exists") + .help(get_message("cp-help-no-clobber")) .action(ArgAction::SetTrue), ) .arg( @@ -577,26 +592,26 @@ pub fn uu_app() -> Command { .visible_short_alias('r') .long(options::RECURSIVE) // --archive sets this option - .help("copy directories recursively") + .help(get_message("cp-help-recursive")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::STRIP_TRAILING_SLASHES) .long(options::STRIP_TRAILING_SLASHES) - .help("remove any trailing slashes from each SOURCE argument") + .help(get_message("cp-help-strip-trailing-slashes")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::DEBUG) .long(options::DEBUG) - .help("explain how a file is copied. Implies -v") + .help(get_message("cp-help-debug")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::VERBOSE) .short('v') .long(options::VERBOSE) - .help("explicitly state what is being done") + .help(get_message("cp-help-verbose")) .action(ArgAction::SetTrue), ) .arg( @@ -604,29 +619,21 @@ pub fn uu_app() -> Command { .short('s') .long(options::SYMBOLIC_LINK) .overrides_with_all(MODE_ARGS) - .help("make symbolic links instead of copying") + .help(get_message("cp-help-symbolic-link")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::FORCE) .short('f') .long(options::FORCE) - .help( - "if an existing destination file cannot be opened, remove it and \ - try again (this option is ignored when the -n option is also used). \ - Currently not implemented for Windows.", - ) + .help(get_message("cp-help-force")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::REMOVE_DESTINATION) .long(options::REMOVE_DESTINATION) .overrides_with(options::FORCE) - .help( - "remove each existing destination file before attempting to open it \ - (contrast with --force). On Windows, currently only works for \ - writeable files.", - ) + .help(get_message("cp-help-remove-destination")) .action(ArgAction::SetTrue), ) .arg(backup_control::arguments::backup()) @@ -643,13 +650,13 @@ pub fn uu_app() -> Command { .default_missing_value("always") .value_parser(ShortcutValueParser::new(["auto", "always", "never"])) .num_args(0..=1) - .help("control clone/CoW copies. See below"), + .help(get_message("cp-help-reflink")), ) .arg( Arg::new(options::ATTRIBUTES_ONLY) .long(options::ATTRIBUTES_ONLY) .overrides_with_all(MODE_ARGS) - .help("Don't copy the file data, just the attributes") + .help(get_message("cp-help-attributes-only")) .action(ArgAction::SetTrue), ) .arg( @@ -664,16 +671,13 @@ pub fn uu_app() -> Command { .default_missing_value(PRESERVE_DEFAULT_VALUES) // -d sets this option // --archive sets this option - .help( - "Preserve the specified attributes (default: mode, ownership (unix only), \ - timestamps), if possible additional attributes: context, links, xattr, all", - ), + .help(get_message("cp-help-preserve")), ) .arg( Arg::new(options::PRESERVE_DEFAULT_ATTRIBUTES) .short('p') .long(options::PRESERVE_DEFAULT_ATTRIBUTES) - .help("same as --preserve=mode,ownership(unix only),timestamps") + .help(get_message("cp-help-preserve-default")) .action(ArgAction::SetTrue), ) .arg( @@ -685,13 +689,13 @@ pub fn uu_app() -> Command { .num_args(0..) .require_equals(true) .value_name("ATTR_LIST") - .help("don't preserve the specified attributes"), + .help(get_message("cp-help-no-preserve")), ) .arg( Arg::new(options::PARENTS) .long(options::PARENTS) .alias(options::PARENT) - .help("use full source file name under DIRECTORY") + .help(get_message("cp-help-parents")) .action(ArgAction::SetTrue), ) .arg( @@ -700,7 +704,7 @@ pub fn uu_app() -> Command { .long(options::NO_DEREFERENCE) .overrides_with(options::DEREFERENCE) // -d sets this option - .help("never follow symbolic links in SOURCE") + .help(get_message("cp-help-no-dereference")) .action(ArgAction::SetTrue), ) .arg( @@ -708,33 +712,33 @@ pub fn uu_app() -> Command { .short('L') .long(options::DEREFERENCE) .overrides_with(options::NO_DEREFERENCE) - .help("always follow symbolic links in SOURCE") + .help(get_message("cp-help-dereference")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::CLI_SYMBOLIC_LINKS) .short('H') - .help("follow command-line symbolic links in SOURCE") + .help(get_message("cp-help-cli-symbolic-links")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::ARCHIVE) .short('a') .long(options::ARCHIVE) - .help("Same as -dR --preserve=all") + .help(get_message("cp-help-archive")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::NO_DEREFERENCE_PRESERVE_LINKS) .short('d') - .help("same as --no-dereference --preserve=links") + .help(get_message("cp-help-no-dereference-preserve-links")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::ONE_FILE_SYSTEM) .short('x') .long(options::ONE_FILE_SYSTEM) - .help("stay on this file system") + .help(get_message("cp-help-one-file-system")) .action(ArgAction::SetTrue), ) .arg( @@ -742,12 +746,12 @@ pub fn uu_app() -> Command { .long(options::SPARSE) .value_name("WHEN") .value_parser(ShortcutValueParser::new(["never", "auto", "always"])) - .help("control creation of sparse files. See below"), + .help(get_message("cp-help-sparse")), ) .arg( Arg::new(options::SELINUX) .short('Z') - .help("set SELinux security context of destination file to default type") + .help(get_message("cp-help-selinux")) .action(ArgAction::SetTrue), ) .arg( @@ -755,10 +759,7 @@ pub fn uu_app() -> Command { .long(options::CONTEXT) .value_name("CTX") .value_parser(value_parser!(String)) - .help( - "like -Z, or if CTX is specified then set the SELinux or SMACK security \ - context to CTX", - ) + .help(get_message("cp-help-context")) .num_args(0..=1) .require_equals(true) .default_missing_value(""), @@ -770,17 +771,14 @@ pub fn uu_app() -> Command { .long(options::PROGRESS_BAR) .short('g') .action(ArgAction::SetTrue) - .help( - "Display a progress bar. \n\ - Note: this feature is not supported by GNU coreutils.", - ), + .help(get_message("cp-help-progress")), ) // TODO: implement the following args .arg( Arg::new(options::COPY_CONTENTS) .long(options::COPY_CONTENTS) .overrides_with(options::ATTRIBUTES_ONLY) - .help("NotImplemented: copy contents of special files when recursive") + .help(get_message("cp-help-copy-contents")) .action(ArgAction::SetTrue), ) // END TODO @@ -803,7 +801,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::None { return Err(UUsageError::new( EXIT_ERR, - "options --backup and --no-clobber are mutually exclusive", + get_message("cp-error-backup-mutually-exclusive"), )); } @@ -984,9 +982,9 @@ impl Attributes { "link" | "links" => &mut new.links, "xattr" => &mut new.xattr, _ => { - return Err(CpError::InvalidArgument(format!( - "invalid attribute {}", - value.quote() + return Err(CpError::InvalidArgument(get_message_with_args( + "cp-error-invalid-attribute", + HashMap::from([("value".to_string(), value.quote().to_string())]), ))); } }; @@ -1030,7 +1028,7 @@ impl Options { .is_some_and(|v| v == "none" || v == "none-fail") { return Err(CpError::InvalidArgument( - "--backup is mutually exclusive with -n or --update=none-fail".to_string(), + get_message("cp-error-invalid-backup-argument").to_string(), )); } @@ -1134,7 +1132,7 @@ impl Options { #[cfg(not(feature = "selinux"))] if let Preserve::Yes { required } = attributes.context { let selinux_disabled_error = - CpError::Error("SELinux was not enabled during the compile time!".to_owned()); + CpError::Error(get_message("cp-error-selinux-not-enabled")); if required { return Err(selinux_disabled_error); } @@ -1176,9 +1174,12 @@ impl Options { "auto" => ReflinkMode::Auto, "never" => ReflinkMode::Never, value => { - return Err(CpError::InvalidArgument(format!( - "invalid argument {} for \'reflink\'", - value.quote() + return Err(CpError::InvalidArgument(get_message_with_args( + "cp-error-invalid-argument", + HashMap::from([ + ("arg".to_string(), value.quote().to_string()), + ("option".to_string(), "reflink".to_string()), + ]), ))); } } @@ -1193,8 +1194,12 @@ impl Options { "auto" => SparseMode::Auto, "never" => SparseMode::Never, _ => { - return Err(CpError::InvalidArgument(format!( - "invalid argument {val} for \'sparse\'" + return Err(CpError::InvalidArgument(get_message_with_args( + "cp-error-invalid-argument", + HashMap::from([ + ("arg".to_string(), val.to_string()), + ("option".to_string(), "sparse".to_string()), + ]), ))); } } @@ -1269,12 +1274,15 @@ fn parse_path_args( ) -> CopyResult<(Vec, PathBuf)> { if paths.is_empty() { // No files specified - return Err("missing file operand".into()); + return Err(get_message("cp-error-missing-file-operand").into()); } else if paths.len() == 1 && options.target_dir.is_none() { // Only one file specified - return Err(format!( - "missing destination file operand after {}", - paths[0].display().to_string().quote() + return Err(get_message_with_args( + "cp-error-missing-destination-operand", + HashMap::from([( + "source".to_string(), + paths[0].display().to_string().quote().to_string(), + )]), ) .into()); } @@ -1282,7 +1290,14 @@ fn parse_path_args( // Return an error if the user requested to copy more than one // file source to a file target if options.no_target_dir && options.target_dir.is_none() && paths.len() > 2 { - return Err(format!("extra operand {:}", paths[2].display().to_string().quote()).into()); + return Err(get_message_with_args( + "cp-error-extra-operand", + HashMap::from([( + "operand".to_string(), + paths[2].display().to_string().quote().to_string(), + )]), + ) + .into()); } let target = match options.target_dir { @@ -1377,10 +1392,14 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult } else { "file" }; - show_warning!( - "source {file_type} {} specified more than once", - source.quote() + let msg = get_message_with_args( + "cp-warning-source-specified-more-than-once", + HashMap::from([ + ("file_type".to_string(), file_type.to_string()), + ("source".to_string(), source.quote().to_string()), + ]), ); + show_warning!("{}", msg); } else { let dest = construct_dest_path(source, target, target_type, options) .unwrap_or_else(|_| target.to_path_buf()); @@ -1395,10 +1414,12 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult // There is already a file and it isn't a symlink (managed in a different place) if copied_destinations.contains(&dest) && options.backup != BackupMode::Numbered { // If the target file was already created in this cp call, do not overwrite - return Err(CpError::Error(format!( - "will not overwrite just-created '{}' with '{}'", - dest.display(), - source.display() + return Err(CpError::Error(get_message_with_args( + "cp-error-will-not-overwrite-just-created", + HashMap::from([ + ("dest".to_string(), dest.quote().to_string()), + ("source".to_string(), source.quote().to_string()), + ]), ))); } } @@ -1442,15 +1463,15 @@ fn construct_dest_path( options: &Options, ) -> CopyResult { if options.no_target_dir && target.is_dir() { - return Err(format!( - "cannot overwrite directory {} with non-directory", - target.quote() + return Err(get_message_with_args( + "cp-error-cannot-overwrite-directory-with-non-directory", + HashMap::from([("dir".to_string(), target.quote().to_string())]), ) .into()); } if options.parents && !target.is_dir() { - return Err("with --parents, the destination must be a directory".into()); + return Err(get_message("cp-error-with-parents-dest-must-be-dir").into()); } Ok(match target_type { @@ -1575,7 +1596,13 @@ impl OverwriteMode { match *self { Self::NoClobber => { if debug { - println!("skipped {}", path.quote()); + println!( + "{}", + get_message_with_args( + "cp-debug-skipped", + HashMap::from([("path".to_string(), path.quote().to_string())]) + ) + ); } Err(CpError::Skipped(false)) } @@ -1583,12 +1610,17 @@ impl OverwriteMode { let prompt_yes_result = if let Some((octal, human_readable)) = file_mode_for_interactive_overwrite(path) { - prompt_yes!( - "replace {}, overriding mode {octal} ({human_readable})?", - path.quote() - ) + let prompt_msg = get_message_with_args( + "cp-prompt-overwrite-with-mode", + HashMap::from([("path".to_string(), path.quote().to_string())]), + ); + prompt_yes!("{} {octal} ({human_readable})?", prompt_msg) } else { - prompt_yes!("overwrite {}?", path.quote()) + let prompt_msg = get_message_with_args( + "cp-prompt-overwrite", + HashMap::from([("path".to_string(), path.quote().to_string())]), + ); + prompt_yes!("{}", prompt_msg) }; if prompt_yes_result { @@ -1621,8 +1653,8 @@ fn handle_preserve CopyResult<()>>(p: &Preserve, f: F) -> CopyResult< } /// Copies extended attributes (xattrs) from `source` to `dest`, ensuring that `dest` is temporarily -/// user-writable if needed and restoring its original permissions afterward. This avoids “Operation -/// not permitted” errors on read-only files. Returns an error if permission or metadata operations fail, +/// user-writable if needed and restoring its original permissions afterward. This avoids "Operation +/// not permitted" errors on read-only files. Returns an error if permission or metadata operations fail, /// or if xattr copying fails. #[cfg(all(unix, not(target_os = "android")))] fn copy_extended_attrs(source: &Path, dest: &Path) -> CopyResult<()> { @@ -1730,16 +1762,19 @@ pub(crate) fn copy_attributes( if let Ok(context) = selinux::SecurityContext::of_path(source, false, false) { if let Some(context) = context { if let Err(e) = context.set_for_path(dest, false, false) { - return Err(CpError::Error(format!( - "failed to set the security context of {}: {e}", - dest.display() + return Err(CpError::Error(get_message_with_args( + "cp-error-selinux-set-context", + HashMap::from([ + ("path".to_string(), dest.display().to_string()), + ("error".to_string(), e.to_string()), + ]), ))); } } } else { - return Err(CpError::Error(format!( - "failed to get security context of {}", - source.display() + return Err(CpError::Error(get_message_with_args( + "cp-error-selinux-get-context", + HashMap::from([("path".to_string(), source.display().to_string())]), ))); } Ok(()) @@ -1780,10 +1815,24 @@ fn symlink_file( std::os::unix::fs::symlink(source, dest).map_err(|e| { CpError::IoErrContext( e, - format!( - "cannot create symlink {} to {}", - get_filename(dest).unwrap_or("invalid file name").quote(), - get_filename(source).unwrap_or("invalid file name").quote() + get_message_with_args( + "cp-error-cannot-create-symlink", + HashMap::from([ + ( + "dest".to_string(), + get_filename(dest) + .unwrap_or("invalid file name") + .quote() + .to_string(), + ), + ( + "source".to_string(), + get_filename(source) + .unwrap_or("invalid file name") + .quote() + .to_string(), + ), + ]), ), ) })?; @@ -1793,10 +1842,24 @@ fn symlink_file( std::os::windows::fs::symlink_file(source, dest).map_err(|e| { CpError::IoErrContext( e, - format!( - "cannot create symlink {} to {}", - get_filename(dest).unwrap_or("invalid file name").quote(), - get_filename(source).unwrap_or("invalid file name").quote() + get_message_with_args( + "cp-error-cannot-create-symlink", + HashMap::from([ + ( + "dest".to_string(), + get_filename(dest) + .unwrap_or("invalid file name") + .quote() + .to_string(), + ), + ( + "source".to_string(), + get_filename(source) + .unwrap_or("invalid file name") + .quote() + .to_string(), + ), + ]), ), ) })?; @@ -1887,7 +1950,14 @@ fn handle_existing_dest( // Disallow copying a file to itself, unless `--force` and // `--backup` are both specified. if is_forbidden_to_copy_to_same_file(source, dest, options, source_in_command_line) { - return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into()); + return Err(get_message_with_args( + "cp-error-same-file", + HashMap::from([ + ("source".to_string(), source.quote().to_string()), + ("dest".to_string(), dest.quote().to_string()), + ]), + ) + .into()); } if options.update == UpdateMode::None { @@ -1905,10 +1975,12 @@ fn handle_existing_dest( let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix); if let Some(backup_path) = backup_path { if paths_refer_to_same_file(source, &backup_path, true) { - return Err(format!( - "backing up {} might destroy source; {} not copied", - dest.quote(), - source.quote() + return Err(get_message_with_args( + "cp-error-backing-up-destroy-source", + HashMap::from([ + ("dest".to_string(), dest.quote().to_string()), + ("source".to_string(), source.quote().to_string()), + ]), ) .into()); } @@ -2063,7 +2135,16 @@ fn print_paths(parents: bool, source: &Path, dest: &Path) { // a/b -> d/a/b // for (x, y) in aligned_ancestors(source, dest) { - println!("{} -> {}", x.display(), y.display()); + println!( + "{}", + get_message_with_args( + "cp-verbose-created-directory", + HashMap::from([ + ("source".to_string(), x.display().to_string()), + ("dest".to_string(), y.display().to_string()) + ]) + ) + ); } } @@ -2117,10 +2198,24 @@ fn handle_copy_mode( .map_err(|e| { CpError::IoErrContext( e, - format!( - "cannot create hard link {} to {}", - get_filename(dest).unwrap_or("invalid file name").quote(), - get_filename(source).unwrap_or("invalid file name").quote() + get_message_with_args( + "cp-error-cannot-create-hard-link", + HashMap::from([ + ( + "dest".to_string(), + get_filename(dest) + .unwrap_or("invalid file name") + .quote() + .to_string(), + ), + ( + "source".to_string(), + get_filename(source) + .unwrap_or("invalid file name") + .quote() + .to_string(), + ), + ]), ), ) })?; @@ -2168,9 +2263,9 @@ fn handle_copy_mode( return Ok(PerformedAction::Skipped); } UpdateMode::NoneFail => { - return Err(CpError::Error(format!( - "not replacing '{}'", - dest.display() + return Err(CpError::Error(get_message_with_args( + "cp-error-not-replacing", + HashMap::from([("file".to_string(), dest.quote().to_string())]), ))); } UpdateMode::IfOlder => { @@ -2296,20 +2391,24 @@ fn copy_file( .map(|info| symlinked_files.contains(&info)) .unwrap_or(false) { - return Err(CpError::Error(format!( - "will not copy '{}' through just-created symlink '{}'", - source.display(), - dest.display() + return Err(CpError::Error(get_message_with_args( + "cp-error-will-not-copy-through-symlink", + HashMap::from([ + ("source".to_string(), source.quote().to_string()), + ("dest".to_string(), dest.quote().to_string()), + ]), ))); } // Fail if cp tries to copy two sources of the same name into a single symlink // Example: "cp file1 dir1/file1 tmp" where "tmp" is a directory containing a symlink "file1" pointing to a file named "foo". // foo will contain the contents of "file1" and "dir1/file1" will not be copied over to "tmp/file1" if copied_destinations.contains(dest) { - return Err(CpError::Error(format!( - "will not copy '{}' through just-created symlink '{}'", - source.display(), - dest.display() + return Err(CpError::Error(get_message_with_args( + "cp-error-will-not-copy-through-symlink", + HashMap::from([ + ("source".to_string(), source.quote().to_string()), + ("dest".to_string(), dest.quote().to_string()), + ]), ))); } @@ -2323,9 +2422,9 @@ fn copy_file( && !is_symlink_loop(dest) && std::env::var_os("POSIXLY_CORRECT").is_none() { - return Err(CpError::Error(format!( - "not writing through dangling symlink '{}'", - dest.display() + return Err(CpError::Error(get_message_with_args( + "cp-error-not-writing-dangling-symlink", + HashMap::from([("dest".to_string(), dest.quote().to_string())]), ))); } if paths_refer_to_same_file(source, dest, true) @@ -2392,9 +2491,9 @@ fn copy_file( OverwriteMode::Clobber(ClobberMode::RemoveDestination) ) { - return Err(format!( - "cannot change attribute {}: Source file is a non regular file", - dest.quote() + return Err(get_message_with_args( + "cp-error-cannot-change-attribute", + HashMap::from([("dest".to_string(), dest.quote().to_string())]), ) .into()); } @@ -2430,7 +2529,10 @@ fn copy_file( // this is just for gnu tests compatibility result.map_err(|err| { if err.to_string().contains("No such file or directory") { - return format!("cannot stat {}: No such file or directory", source.quote()); + return get_message_with_args( + "cp-error-cannot-stat", + HashMap::from([("source".to_string(), source.quote().to_string())]), + ); } err.to_string() })? @@ -2499,7 +2601,10 @@ fn copy_file( if let Err(e) = uucore::selinux::set_selinux_security_context(dest, options.context.as_ref()) { - return Err(CpError::Error(format!("SELinux error: {}", e))); + return Err(CpError::Error(get_message_with_args( + "cp-error-selinux-error", + HashMap::from([("error".to_string(), e.to_string())]), + ))); } } @@ -2617,7 +2722,13 @@ fn copy_fifo(dest: &Path, overwrite: OverwriteMode, debug: bool) -> CopyResult<( fs::remove_file(dest)?; } - make_fifo(dest).map_err(|_| format!("cannot create fifo {}: File exists", dest.quote()).into()) + make_fifo(dest).map_err(|_| { + get_message_with_args( + "cp-error-cannot-create-fifo", + HashMap::from([("path".to_string(), dest.quote().to_string())]), + ) + .into() + }) } fn copy_link( @@ -2638,12 +2749,14 @@ fn copy_link( /// Generate an error message if `target` is not the correct `target_type` pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> { match (target_type, target.is_dir()) { - (&TargetType::Directory, false) => { - Err(format!("target: {} is not a directory", target.quote()).into()) - } - (&TargetType::File, true) => Err(format!( - "cannot overwrite directory {} with non-directory", - target.quote() + (&TargetType::Directory, false) => Err(get_message_with_args( + "cp-error-target-not-directory", + HashMap::from([("target".to_string(), target.quote().to_string())]), + ) + .into()), + (&TargetType::File, true) => Err(get_message_with_args( + "cp-error-cannot-overwrite-directory-with-non-directory", + HashMap::from([("dir".to_string(), target.quote().to_string())]), ) .into()), _ => Ok(()), diff --git a/src/uu/cp/src/platform/linux.rs b/src/uu/cp/src/platform/linux.rs index c4606d436..56b8b2fe4 100644 --- a/src/uu/cp/src/platform/linux.rs +++ b/src/uu/cp/src/platform/linux.rs @@ -13,7 +13,7 @@ use std::os::unix::fs::{FileTypeExt, OpenOptionsExt}; use std::os::unix::io::AsRawFd; use std::path::Path; use uucore::buf_copy; - +use uucore::locale::get_message; use uucore::mode::get_umask; use crate::{ @@ -401,7 +401,7 @@ pub(crate) fn copy_on_write( clone(source, dest, CloneFallback::Error) } (ReflinkMode::Always, _) => { - return Err("`--reflink=always` can be used only with --sparse=auto".into()); + return Err(get_message("cp-error-reflink-always-sparse-auto").into()); } }; result.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?; diff --git a/src/uu/cp/src/platform/macos.rs b/src/uu/cp/src/platform/macos.rs index be2e43a95..8c915d2df 100644 --- a/src/uu/cp/src/platform/macos.rs +++ b/src/uu/cp/src/platform/macos.rs @@ -3,6 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore reflink +use std::collections::HashMap; use std::ffi::CString; use std::fs::{self, File, OpenOptions}; use std::os::unix::ffi::OsStrExt; @@ -10,6 +11,7 @@ use std::os::unix::fs::OpenOptionsExt; use std::path::Path; use uucore::buf_copy; +use uucore::locale::{get_message, get_message_with_args}; use uucore::mode::get_umask; use crate::{ @@ -30,7 +32,9 @@ pub(crate) fn copy_on_write( source_is_stream: bool, ) -> CopyResult { if sparse_mode != SparseMode::Auto { - return Err("--sparse is only supported on linux".to_string().into()); + return Err(get_message("cp-error-sparse-not-supported") + .to_string() + .into()); } let mut copy_debug = CopyDebug { offload: OffloadReflinkDebug::Unknown, @@ -85,10 +89,13 @@ pub(crate) fn copy_on_write( // support COW). match reflink_mode { ReflinkMode::Always => { - return Err(format!( - "failed to clone {} from {}: {error}", - source.display(), - dest.display() + return Err(get_message_with_args( + "cp-error-failed-to-clone", + HashMap::from([ + ("source".to_string(), source.display().to_string()), + ("dest".to_string(), dest.display().to_string()), + ("error".to_string(), error.to_string()), + ]), ) .into()); } diff --git a/src/uu/cp/src/platform/other.rs b/src/uu/cp/src/platform/other.rs index 9df2f9993..4ac4e9c37 100644 --- a/src/uu/cp/src/platform/other.rs +++ b/src/uu/cp/src/platform/other.rs @@ -5,6 +5,7 @@ // spell-checker:ignore reflink use std::fs; use std::path::Path; +use uucore::locale::get_message; use crate::{ CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode, @@ -19,12 +20,14 @@ pub(crate) fn copy_on_write( context: &str, ) -> CopyResult { if reflink_mode != ReflinkMode::Never { - return Err("--reflink is only supported on linux and macOS" + return Err(get_message("cp-error-reflink-not-supported") .to_string() .into()); } if sparse_mode != SparseMode::Auto { - return Err("--sparse is only supported on linux".to_string().into()); + return Err(get_message("cp-error-sparse-not-supported") + .to_string() + .into()); } let copy_debug = CopyDebug { offload: OffloadReflinkDebug::Unsupported, diff --git a/src/uu/cp/src/platform/other_unix.rs b/src/uu/cp/src/platform/other_unix.rs index 94799139a..00f3cbf6e 100644 --- a/src/uu/cp/src/platform/other_unix.rs +++ b/src/uu/cp/src/platform/other_unix.rs @@ -8,6 +8,7 @@ use std::os::unix::fs::OpenOptionsExt; use std::path::Path; use uucore::buf_copy; +use uucore::locale::get_message; use uucore::mode::get_umask; use crate::{ @@ -25,12 +26,14 @@ pub(crate) fn copy_on_write( source_is_stream: bool, ) -> CopyResult { if reflink_mode != ReflinkMode::Never { - return Err("--reflink is only supported on linux and macOS" + return Err(get_message("cp-error-reflink-not-supported") .to_string() .into()); } if sparse_mode != SparseMode::Auto { - return Err("--sparse is only supported on linux".to_string().into()); + return Err(get_message("cp-error-sparse-not-supported") + .to_string() + .into()); } let copy_debug = CopyDebug { offload: OffloadReflinkDebug::Unsupported,