From c8066700a2544037d3cf47cbf9e88b5348bcf3fc Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 8 Jun 2025 19:24:51 +0200 Subject: [PATCH] l10n: port touch for translation + add french --- src/uu/touch/locales/en-US.ftl | 29 +++++- src/uu/touch/locales/fr-FR.ftl | 30 +++++++ src/uu/touch/src/error.rs | 10 ++- src/uu/touch/src/touch.rs | 155 +++++++++++++++++++++++---------- 4 files changed, 173 insertions(+), 51 deletions(-) create mode 100644 src/uu/touch/locales/fr-FR.ftl diff --git a/src/uu/touch/locales/en-US.ftl b/src/uu/touch/locales/en-US.ftl index a3e6d6e3e..985f7cca1 100644 --- a/src/uu/touch/locales/en-US.ftl +++ b/src/uu/touch/locales/en-US.ftl @@ -1,2 +1,29 @@ touch-about = Update the access and modification times of each FILE to the current time. -touch-usage = touch [OPTION]... [USER] +touch-usage = touch [OPTION]... [FILE]... + +# Help messages +touch-help-help = Print help information. +touch-help-access = change only the access time +touch-help-timestamp = use [[CC]YY]MMDDhhmm[.ss] instead of the current time +touch-help-date = parse argument and use it instead of current time +touch-help-modification = change only the modification time +touch-help-no-create = do not create any files +touch-help-no-deref = affect each symbolic link instead of any referenced file (only for systems that can change the timestamps of a symlink) +touch-help-reference = use this file's times instead of the current time +touch-help-time = change only the specified time: "access", "atime", or "use" are equivalent to -a; "modify" or "mtime" are equivalent to -m + +# Error messages +touch-error-missing-file-operand = missing file operand + Try '{ $help_command } --help' for more information. +touch-error-setting-times-of = setting times of { $filename } +touch-error-setting-times-no-such-file = setting times of { $filename }: No such file or directory +touch-error-cannot-touch = cannot touch { $filename } +touch-error-no-such-file-or-directory = No such file or directory +touch-error-failed-to-get-attributes = failed to get attributes of { $path } +touch-error-setting-times-of-path = setting times of { $path } +touch-error-invalid-date-ts-format = invalid date ts format { $date } +touch-error-invalid-date-format = invalid date format { $date } +touch-error-unable-to-parse-date = Unable to parse date: { $date } +touch-error-windows-stdout-path-failed = GetFinalPathNameByHandleW failed with code { $code } +touch-error-invalid-filetime = Source has invalid access or modification time: { $time } +touch-error-reference-file-inaccessible = failed to get attributes of { $path }: { $error } diff --git a/src/uu/touch/locales/fr-FR.ftl b/src/uu/touch/locales/fr-FR.ftl new file mode 100644 index 000000000..7ca2f995b --- /dev/null +++ b/src/uu/touch/locales/fr-FR.ftl @@ -0,0 +1,30 @@ + +touch-about = Mettre à jour les temps d'accès et de modification de chaque FICHIER avec l'heure actuelle. +touch-usage = touch [OPTION]... [FICHIER]... + +# Messages d'aide +touch-help-help = Afficher les informations d'aide. +touch-help-access = changer seulement le temps d'accès +touch-help-timestamp = utiliser [[CC]AA]MMJJhhmm[.ss] au lieu de l'heure actuelle +touch-help-date = analyser l'argument et l'utiliser au lieu de l'heure actuelle +touch-help-modification = changer seulement le temps de modification +touch-help-no-create = ne créer aucun fichier +touch-help-no-deref = affecter chaque lien symbolique au lieu de tout fichier référencé (seulement pour les systèmes qui peuvent changer les horodatages d'un lien symbolique) +touch-help-reference = utiliser les temps de ce fichier au lieu de l'heure actuelle +touch-help-time = changer seulement le temps spécifié : "access", "atime", ou "use" sont équivalents à -a ; "modify" ou "mtime" sont équivalents à -m + +# Messages d'erreur +touch-error-missing-file-operand = opérande de fichier manquant + Essayez '{ $help_command } --help' pour plus d'informations. +touch-error-setting-times-of = définition des temps de { $filename } +touch-error-setting-times-no-such-file = définition des temps de { $filename } : Aucun fichier ou répertoire de ce type +touch-error-cannot-touch = impossible de toucher { $filename } +touch-error-no-such-file-or-directory = Aucun fichier ou répertoire de ce type +touch-error-failed-to-get-attributes = échec d'obtention des attributs de { $path } +touch-error-setting-times-of-path = définition des temps de { $path } +touch-error-invalid-date-ts-format = format de date ts invalide { $date } +touch-error-invalid-date-format = format de date invalide { $date } +touch-error-unable-to-parse-date = Impossible d'analyser la date : { $date } +touch-error-windows-stdout-path-failed = GetFinalPathNameByHandleW a échoué avec le code { $code } +touch-error-invalid-filetime = La source a un temps d'accès ou de modification invalide : { $time } +touch-error-reference-file-inaccessible = échec d'obtention des attributs de { $path } : { $error } diff --git a/src/uu/touch/src/error.rs b/src/uu/touch/src/error.rs index 78cc8f330..84d5112f7 100644 --- a/src/uu/touch/src/error.rs +++ b/src/uu/touch/src/error.rs @@ -5,26 +5,28 @@ // spell-checker:ignore (misc) uioerror use filetime::FileTime; +use std::collections::HashMap; use std::path::PathBuf; use thiserror::Error; use uucore::display::Quotable; use uucore::error::{UError, UIoError}; +use uucore::locale::get_message_with_args; #[derive(Debug, Error)] pub enum TouchError { - #[error("Unable to parse date: {0}")] + #[error("{}", get_message_with_args("touch-error-unable-to-parse-date", HashMap::from([("date".to_string(), .0.clone())])))] InvalidDateFormat(String), /// The source time couldn't be converted to a [chrono::DateTime] - #[error("Source has invalid access or modification time: {0}")] + #[error("{}", get_message_with_args("touch-error-invalid-filetime", HashMap::from([("time".to_string(), .0.to_string())])))] InvalidFiletime(FileTime), /// The reference file's attributes could not be found or read - #[error("failed to get attributes of {}: {}", .0.quote(), to_uioerror(.1))] + #[error("{}", get_message_with_args("touch-error-reference-file-inaccessible", HashMap::from([("path".to_string(), .0.quote().to_string()), ("error".to_string(), to_uioerror(.1).to_string())])))] ReferenceFileInaccessible(PathBuf, std::io::Error), /// An error getting a path to stdout on Windows - #[error("GetFinalPathNameByHandleW failed with code {0}")] + #[error("{}", get_message_with_args("touch-error-windows-stdout-path-failed", HashMap::from([("code".to_string(), .0.clone())])))] WindowsStdoutPathError(String), /// An error encountered on a specific file diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index c12c50530..9bc00379c 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -16,6 +16,7 @@ use clap::builder::{PossibleValue, ValueParser}; use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command}; use filetime::{FileTime, set_file_times, set_symlink_file_times}; use std::borrow::Cow; +use std::collections::HashMap; use std::ffi::OsString; use std::fs::{self, File}; use std::io::{Error, ErrorKind}; @@ -26,7 +27,7 @@ use uucore::parser::shortcut_value_parser::ShortcutValueParser; use uucore::{format_usage, show}; use crate::error::TouchError; -use uucore::locale::get_message; +use uucore::locale::{get_message, get_message_with_args}; /// Options contains all the possible behaviors and flags for touch. /// @@ -192,9 +193,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .ok_or_else(|| { USimpleError::new( 1, - format!( - "missing file operand\nTry '{} --help' for more information.", - uucore::execution_phrase() + get_message_with_args( + "touch-error-missing-file-operand", + HashMap::from([( + "help_command".to_string(), + uucore::execution_phrase().to_string(), + )]), ), ) })? @@ -263,19 +267,19 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::HELP) .long(options::HELP) - .help("Print help information.") + .help(get_message("touch-help-help")) .action(ArgAction::Help), ) .arg( Arg::new(options::ACCESS) .short('a') - .help("change only the access time") + .help(get_message("touch-help-access")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::sources::TIMESTAMP) .short('t') - .help("use [[CC]YY]MMDDhhmm[.ss] instead of the current time") + .help(get_message("touch-help-timestamp")) .value_name("STAMP"), ) .arg( @@ -283,38 +287,35 @@ pub fn uu_app() -> Command { .short('d') .long(options::sources::DATE) .allow_hyphen_values(true) - .help("parse argument and use it instead of current time") + .help(get_message("touch-help-date")) .value_name("STRING") .conflicts_with(options::sources::TIMESTAMP), ) .arg( Arg::new(options::MODIFICATION) .short('m') - .help("change only the modification time") + .help(get_message("touch-help-modification")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::NO_CREATE) .short('c') .long(options::NO_CREATE) - .help("do not create any files") + .help(get_message("touch-help-no-create")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::NO_DEREF) .short('h') .long(options::NO_DEREF) - .help( - "affect each symbolic link instead of any referenced file \ - (only for systems that can change the timestamps of a symlink)", - ) + .help(get_message("touch-help-no-deref")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::sources::REFERENCE) .short('r') .long(options::sources::REFERENCE) - .help("use this file's times instead of the current time") + .help(get_message("touch-help-reference")) .value_name("FILE") .value_parser(ValueParser::os_string()) .value_hint(clap::ValueHint::AnyPath) @@ -323,11 +324,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::TIME) .long(options::TIME) - .help( - "change only the specified time: \"access\", \"atime\", or \ - \"use\" are equivalent to -a; \"modify\" or \"mtime\" are \ - equivalent to -m", - ) + .help(get_message("touch-help-time")) .value_name("WORD") .value_parser(ShortcutValueParser::new([ PossibleValue::new("atime").alias("access").alias("use"), @@ -442,7 +439,12 @@ fn touch_file( if let Err(e) = metadata_result { if e.kind() != ErrorKind::NotFound { - return Err(e.map_err_context(|| format!("setting times of {}", filename.quote()))); + return Err(e.map_err_context(|| { + get_message_with_args( + "touch-error-setting-times-of", + HashMap::from([("filename".to_string(), filename.quote().to_string())]), + ) + })); } if opts.no_create { @@ -452,9 +454,9 @@ fn touch_file( if opts.no_deref { let e = USimpleError::new( 1, - format!( - "setting times of {}: No such file or directory", - filename.quote() + get_message_with_args( + "touch-error-setting-times-no-such-file", + HashMap::from([("filename".to_string(), filename.quote().to_string())]), ), ); if opts.strict { @@ -475,12 +477,20 @@ fn touch_file( false }; if is_directory { - let custom_err = Error::other("No such file or directory"); - return Err( - custom_err.map_err_context(|| format!("cannot touch {}", filename.quote())) - ); + let custom_err = Error::other(get_message("touch-error-no-such-file-or-directory")); + return Err(custom_err.map_err_context(|| { + get_message_with_args( + "touch-error-cannot-touch", + HashMap::from([("filename".to_string(), filename.quote().to_string())]), + ) + })); } - let e = e.map_err_context(|| format!("cannot touch {}", path.quote())); + let e = e.map_err_context(|| { + get_message_with_args( + "touch-error-cannot-touch", + HashMap::from([("filename".to_string(), path.quote().to_string())]), + ) + }); if opts.strict { return Err(e); } @@ -543,12 +553,22 @@ fn update_times( ChangeTimes::AtimeOnly => ( atime, stat(path, !opts.no_deref) - .map_err_context(|| format!("failed to get attributes of {}", path.quote()))? + .map_err_context(|| { + get_message_with_args( + "touch-error-failed-to-get-attributes", + HashMap::from([("path".to_string(), path.quote().to_string())]), + ) + })? .1, ), ChangeTimes::MtimeOnly => ( stat(path, !opts.no_deref) - .map_err_context(|| format!("failed to get attributes of {}", path.quote()))? + .map_err_context(|| { + get_message_with_args( + "touch-error-failed-to-get-attributes", + HashMap::from([("path".to_string(), path.quote().to_string())]), + ) + })? .0, mtime, ), @@ -563,7 +583,12 @@ fn update_times( } else { set_file_times(path, atime, mtime) } - .map_err_context(|| format!("setting times of {}", path.quote())) + .map_err_context(|| { + get_message_with_args( + "touch-error-setting-times-of-path", + HashMap::from([("path".to_string(), path.quote().to_string())]), + ) + }) } /// Get metadata of the provided path @@ -645,9 +670,15 @@ fn parse_date(ref_time: DateTime, s: &str) -> Result UResult { - let first_two_digits = s[..2] - .parse::() - .map_err(|_| USimpleError::new(1, format!("invalid date ts format {}", s.quote())))?; + let first_two_digits = s[..2].parse::().map_err(|_| { + USimpleError::new( + 1, + get_message_with_args( + "touch-error-invalid-date-ts-format", + HashMap::from([("date".to_string(), s.quote().to_string())]), + ), + ) + })?; Ok(format!( "{}{s}", if first_two_digits > 68 { 19 } else { 20 } @@ -678,17 +709,30 @@ fn parse_timestamp(s: &str) -> UResult { _ => { return Err(USimpleError::new( 1, - format!("invalid date format {}", s.quote()), + get_message_with_args( + "touch-error-invalid-date-format", + HashMap::from([("date".to_string(), s.quote().to_string())]), + ), )); } }; - let local = NaiveDateTime::parse_from_str(&ts, format) - .map_err(|_| USimpleError::new(1, format!("invalid date ts format {}", ts.quote())))?; + let local = NaiveDateTime::parse_from_str(&ts, format).map_err(|_| { + USimpleError::new( + 1, + get_message_with_args( + "touch-error-invalid-date-ts-format", + HashMap::from([("date".to_string(), ts.quote().to_string())]), + ), + ) + })?; let LocalResult::Single(mut local) = Local.from_local_datetime(&local) else { return Err(USimpleError::new( 1, - format!("invalid date ts format {}", ts.quote()), + get_message_with_args( + "touch-error-invalid-date-ts-format", + HashMap::from([("date".to_string(), ts.quote().to_string())]), + ), )); }; @@ -709,7 +753,10 @@ fn parse_timestamp(s: &str) -> UResult { if local.hour() != local2.hour() { return Err(USimpleError::new( 1, - format!("invalid date format {}", s.quote()), + get_message_with_args( + "touch-error-invalid-date-format", + HashMap::from([("date".to_string(), s.quote().to_string())]), + ), )); } @@ -763,15 +810,22 @@ fn pathbuf_from_stdout() -> Result { let buffer_size = match ret { ERROR_PATH_NOT_FOUND | ERROR_NOT_ENOUGH_MEMORY | ERROR_INVALID_PARAMETER => { - return Err(TouchError::WindowsStdoutPathError(format!( - "GetFinalPathNameByHandleW failed with code {ret}" + return Err(TouchError::WindowsStdoutPathError(get_message_with_args( + "touch-error-windows-stdout-path-failed", + HashMap::from([("code".to_string(), ret.to_string())]), ))); } 0 => { - return Err(TouchError::WindowsStdoutPathError(format!( - "GetFinalPathNameByHandleW failed with code {}", - // SAFETY: GetLastError is thread-safe and has no documented memory unsafety. - unsafe { GetLastError() }, + return Err(TouchError::WindowsStdoutPathError(get_message_with_args( + "touch-error-windows-stdout-path-failed", + HashMap::from([( + "code".to_string(), + format!( + "{}", + // SAFETY: GetLastError is thread-safe and has no documented memory unsafety. + unsafe { GetLastError() } + ), + )]), ))); } e => e as usize, @@ -793,9 +847,18 @@ mod tests { uu_app, }; + #[cfg(windows)] + use std::env; + #[cfg(windows)] + use uucore::locale; + #[cfg(windows)] #[test] fn test_get_pathbuf_from_stdout_fails_if_stdout_is_not_a_file() { + unsafe { + env::set_var("LANG", "C"); + } + let _ = locale::setup_localization("touch"); // We can trigger an error by not setting stdout to anything (will // fail with code 1) assert!(