Merge pull request #8116 from sylvestre/l10n-touch

l10n: port touch for translation + add french
This commit is contained in:
Daniel Hofstetter 2025-06-29 14:50:48 +02:00 committed by GitHub
commit 1d76a00840
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 173 additions and 51 deletions

View file

@ -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 }

View file

@ -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 }

View file

@ -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

View file

@ -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<Local>, s: &str) -> Result<FileTime, TouchError
/// - 68 and before is interpreted as 20xx
/// - 69 and after is interpreted as 19xx
fn prepend_century(s: &str) -> UResult<String> {
let first_two_digits = s[..2]
.parse::<u32>()
.map_err(|_| USimpleError::new(1, format!("invalid date ts format {}", s.quote())))?;
let first_two_digits = s[..2].parse::<u32>().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<FileTime> {
_ => {
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<FileTime> {
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<PathBuf, TouchError> {
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!(