diff --git a/src/uu/tr/locales/en-US.ftl b/src/uu/tr/locales/en-US.ftl index 931c6265b..0f1fc38a9 100644 --- a/src/uu/tr/locales/en-US.ftl +++ b/src/uu/tr/locales/en-US.ftl @@ -1,3 +1,41 @@ tr-about = Translate or delete characters tr-usage = tr [OPTION]... SET1 [SET2] tr-after-help = Translate, squeeze, and/or delete characters from standard input, writing to standard output. + +# Help messages +tr-help-complement = use the complement of SET1 +tr-help-delete = delete characters in SET1, do not translate +tr-help-squeeze = replace each sequence of a repeated character that is listed in the last specified SET, with a single occurrence of that character +tr-help-truncate-set1 = first truncate SET1 to length of SET2 + +# Error messages +tr-error-missing-operand = missing operand +tr-error-missing-operand-translating = missing operand after { $set } + Two strings must be given when translating. +tr-error-missing-operand-deleting-squeezing = missing operand after { $set } + Two strings must be given when deleting and squeezing. +tr-error-extra-operand-deleting-without-squeezing = extra operand { $operand } + Only one string may be given when deleting without squeezing repeats. +tr-error-extra-operand-simple = extra operand { $operand } +tr-error-read-directory = read error: Is a directory +tr-error-write-error = write error + +# Warning messages +tr-warning-unescaped-backslash = warning: an unescaped backslash at end of string is not portable +tr-warning-ambiguous-octal-escape = the ambiguous octal escape \{ $origin_octal } is being interpreted as the 2-byte sequence \0{ $actual_octal_tail }, { $outstand_char } + +# Sequence parsing error messages +tr-error-missing-char-class-name = missing character class name '[::]' +tr-error-missing-equivalence-class-char = missing equivalence class character '[==]' +tr-error-multiple-char-repeat-in-set2 = only one [c*] repeat construct may appear in string2 +tr-error-char-repeat-in-set1 = the [c*] repeat construct may not appear in string1 +tr-error-invalid-repeat-count = invalid repeat count { $count } in [c*n] construct +tr-error-empty-set2-when-not-truncating = when not truncating set1, string2 must be non-empty +tr-error-class-except-lower-upper-in-set2 = when translating, the only character classes that may appear in set2 are 'upper' and 'lower' +tr-error-class-in-set2-not-matched = when translating, every 'upper'/'lower' in set2 must be matched by a 'upper'/'lower' in the same position in set1 +tr-error-set1-longer-set2-ends-in-class = when translating with string1 longer than string2, + the latter string must not end with a character class +tr-error-complement-more-than-one-unique = when translating with complemented character classes, + string2 must map all characters in the domain to one +tr-error-backwards-range = range-endpoints of '{ $start }-{ $end }' are in reverse collating sequence order +tr-error-multiple-char-in-equivalence = { $chars }: equivalence class operand must be a single character diff --git a/src/uu/tr/locales/fr-FR.ftl b/src/uu/tr/locales/fr-FR.ftl new file mode 100644 index 000000000..e8af47a0d --- /dev/null +++ b/src/uu/tr/locales/fr-FR.ftl @@ -0,0 +1,42 @@ +tr-about = Traduire ou supprimer des caractères +tr-usage = tr [OPTION]... ENSEMBLE1 [ENSEMBLE2] +tr-after-help = Traduire, compresser et/ou supprimer des caractères de l'entrée standard, en écrivant vers la sortie standard. + +# Messages d'aide +tr-help-complement = utiliser le complément d'ENSEMBLE1 +tr-help-delete = supprimer les caractères dans ENSEMBLE1, ne pas traduire +tr-help-squeeze = remplacer chaque séquence d'un caractère répété qui est listé dans le dernier ENSEMBLE spécifié, avec une seule occurrence de ce caractère +tr-help-truncate-set1 = d'abord tronquer ENSEMBLE1 à la longueur d'ENSEMBLE2 + +# Messages d'erreur +tr-error-missing-operand = opérande manquant +tr-error-missing-operand-translating = opérande manquant après { $set } + Deux chaînes doivent être données lors de la traduction. +tr-error-missing-operand-deleting-squeezing = opérande manquant après { $set } + Deux chaînes doivent être données lors de la suppression et compression. +tr-error-extra-operand-deleting-without-squeezing = opérande supplémentaire { $operand } + Une seule chaîne peut être donnée lors de la suppression sans compression des répétitions. +tr-error-extra-operand-simple = opérande supplémentaire { $operand } +tr-error-read-directory = erreur de lecture : Est un répertoire +tr-error-write-error = erreur d'écriture + +# Messages d'avertissement +tr-warning-unescaped-backslash = avertissement : une barre oblique inverse non échappée à la fin de la chaîne n'est pas portable +tr-warning-ambiguous-octal-escape = l'échappement octal ambigu \{ $origin_octal } est en cours + d'interprétation comme la séquence de 2 octets \0{ $actual_octal_tail }, { $outstand_char } + +# Messages d'erreur d'analyse de séquence +tr-error-missing-char-class-name = nom de classe de caractères manquant '[::]' +tr-error-missing-equivalence-class-char = caractère de classe d'équivalence manquant '[==]' +tr-error-multiple-char-repeat-in-set2 = seule une construction de répétition [c*] peut apparaître dans string2 +tr-error-char-repeat-in-set1 = la construction de répétition [c*] ne peut pas apparaître dans string1 +tr-error-invalid-repeat-count = nombre de répétitions invalide { $count } dans la construction [c*n] +tr-error-empty-set2-when-not-truncating = quand on ne tronque pas set1, string2 doit être non-vide +tr-error-class-except-lower-upper-in-set2 = lors de la traduction, les seules classes de caractères qui peuvent apparaître dans set2 sont 'upper' et 'lower' +tr-error-class-in-set2-not-matched = lors de la traduction, chaque 'upper'/'lower' dans set2 doit être associé à un 'upper'/'lower' à la même position dans set1 +tr-error-set1-longer-set2-ends-in-class = lors de la traduction avec string1 plus long que string2, + cette dernière chaîne ne doit pas se terminer par une classe de caractères +tr-error-complement-more-than-one-unique = lors de la traduction avec des classes de caractères complémentées, + string2 doit mapper tous les caractères du domaine vers un seul +tr-error-backwards-range = les points de fin de plage de '{ $start }-{ $end }' sont dans l'ordre inverse de la séquence de collation +tr-error-multiple-char-in-equivalence = { $chars } : l'opérande de classe d'équivalence doit être un seul caractère diff --git a/src/uu/tr/src/operation.rs b/src/uu/tr/src/operation.rs index 8e24f8ce3..fde766e49 100644 --- a/src/uu/tr/src/operation.rs +++ b/src/uu/tr/src/operation.rs @@ -24,6 +24,7 @@ use std::{ ops::Not, }; use uucore::error::{FromIo, UError, UResult}; +use uucore::locale::{get_message, get_message_with_args}; use uucore::show_warning; #[derive(Debug, Clone)] @@ -45,44 +46,65 @@ pub enum BadSequence { impl Display for BadSequence { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::MissingCharClassName => write!(f, "missing character class name '[::]'"), + Self::MissingCharClassName => { + write!(f, "{}", get_message("tr-error-missing-char-class-name")) + } Self::MissingEquivalentClassChar => { - write!(f, "missing equivalence class character '[==]'") + write!( + f, + "{}", + get_message("tr-error-missing-equivalence-class-char") + ) } Self::MultipleCharRepeatInSet2 => { - write!(f, "only one [c*] repeat construct may appear in string2") + write!( + f, + "{}", + get_message("tr-error-multiple-char-repeat-in-set2") + ) } Self::CharRepeatInSet1 => { - write!(f, "the [c*] repeat construct may not appear in string1") + write!(f, "{}", get_message("tr-error-char-repeat-in-set1")) } Self::InvalidRepeatCount(count) => { - write!(f, "invalid repeat count '{count}' in [c*n] construct") + write!( + f, + "{}", + get_message_with_args( + "tr-error-invalid-repeat-count", + HashMap::from([("count".to_string(), format!("'{}'", count))]) + ) + ) } Self::EmptySet2WhenNotTruncatingSet1 => { - write!(f, "when not truncating set1, string2 must be non-empty") + write!( + f, + "{}", + get_message("tr-error-empty-set2-when-not-truncating") + ) } Self::ClassExceptLowerUpperInSet2 => { write!( f, - "when translating, the only character classes that may appear in set2 are 'upper' and 'lower'" + "{}", + get_message("tr-error-class-except-lower-upper-in-set2") ) } Self::ClassInSet2NotMatchedBySet1 => { - write!( - f, - "when translating, every 'upper'/'lower' in set2 must be matched by a 'upper'/'lower' in the same position in set1" - ) + write!(f, "{}", get_message("tr-error-class-in-set2-not-matched")) } Self::Set1LongerSet2EndsInClass => { write!( f, - "when translating with string1 longer than string2,\nthe latter string must not end with a character class" + "{}", + get_message("tr-error-set1-longer-set2-ends-in-class") ) } Self::ComplementMoreThanOneUniqueInSet2 => { write!( f, - "when translating with complemented character classes,\nstring2 must map all characters in the domain to one" + "{}", + get_message("tr-error-complement-more-than-one-unique") ) } Self::BackwardsRange { end, start } => { @@ -94,17 +116,25 @@ impl Display for BadSequence { } } } - write!( f, - "range-endpoints of '{}-{}' are in reverse collating sequence order", - end_or_start_to_string(start), - end_or_start_to_string(end) + "{}", + get_message_with_args( + "tr-error-backwards-range", + HashMap::from([ + ("start".to_string(), end_or_start_to_string(start)), + ("end".to_string(), end_or_start_to_string(end)) + ]) + ) ) } Self::MultipleCharInEquivalence(s) => write!( f, - "{s}: equivalence class operand must be a single character" + "{}", + get_message_with_args( + "tr-error-multiple-char-in-equivalence", + HashMap::from([("chars".to_string(), s.clone())]) + ) ), } } @@ -364,11 +394,25 @@ impl Sequence { let origin_octal: &str = std::str::from_utf8(input).unwrap(); let actual_octal_tail: &str = std::str::from_utf8(&input[0..2]).unwrap(); let outstand_char: char = char::from_u32(input[2] as u32).unwrap(); - show_warning!("the ambiguous octal escape \\{origin_octal} is being\n interpreted as the 2-byte sequence \\0{actual_octal_tail}, {outstand_char}"); + show_warning!( + "{}", + get_message_with_args( + "tr-warning-ambiguous-octal-escape", + HashMap::from([ + ("origin_octal".to_string(), origin_octal.to_string()), + ( + "actual_octal_tail".to_string(), + actual_octal_tail.to_string() + ), + ("outstand_char".to_string(), outstand_char.to_string()) + ]) + ) + ); } result }, - ).parse(input) + ) + .parse(input) } fn parse_octal_two_digits(input: &[u8]) -> IResult<&[u8], u8> { @@ -666,7 +710,7 @@ where output .write_all(&output_buf) - .map_err_context(|| "write error".into())?; + .map_err_context(|| get_message("tr-error-write-error"))?; buf.clear(); output_buf.clear(); diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 5259990bf..6d0a2784d 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -13,6 +13,7 @@ use clap::{Arg, ArgAction, Command, value_parser}; use operation::{ Sequence, SqueezeOperation, SymbolTranslator, TranslateOperation, translate_input, }; +use std::collections::HashMap; use std::ffi::OsString; use std::io::{BufWriter, Write, stdin, stdout}; use uucore::display::Quotable; @@ -20,7 +21,7 @@ use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::is_stdin_directory; use uucore::{format_usage, os_str_as_bytes, show}; -use uucore::locale::get_message; +use uucore::locale::{get_message, get_message_with_args}; mod options { pub const COMPLEMENT: &str = "complement"; @@ -53,15 +54,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let sets_len = sets.len(); if sets.is_empty() { - return Err(UUsageError::new(1, "missing operand")); + return Err(UUsageError::new(1, get_message("tr-error-missing-operand"))); } if !(delete_flag || squeeze_flag) && sets_len < 2 { return Err(UUsageError::new( 1, - format!( - "missing operand after {}\nTwo strings must be given when translating.", - sets[0].quote() + get_message_with_args( + "tr-error-missing-operand-translating", + HashMap::from([("set".to_string(), sets[0].quote().to_string())]), ), )); } @@ -69,29 +70,35 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if delete_flag & squeeze_flag && sets_len < 2 { return Err(UUsageError::new( 1, - format!( - "missing operand after {}\nTwo strings must be given when deleting and squeezing.", - sets[0].quote() + get_message_with_args( + "tr-error-missing-operand-deleting-squeezing", + HashMap::from([("set".to_string(), sets[0].quote().to_string())]), ), )); } if sets_len > 1 { - let start = "extra operand"; if delete_flag && !squeeze_flag { let op = sets[1].quote(); let msg = if sets_len == 2 { - format!( - "{start} {op}\nOnly one string may be given when deleting without squeezing repeats.", + get_message_with_args( + "tr-error-extra-operand-deleting-without-squeezing", + HashMap::from([("operand".to_string(), op.to_string())]), ) } else { - format!("{start} {op}") + get_message_with_args( + "tr-error-extra-operand-simple", + HashMap::from([("operand".to_string(), op.to_string())]), + ) }; return Err(UUsageError::new(1, msg)); } if sets_len > 2 { let op = sets[2].quote(); - let msg = format!("{start} {op}"); + let msg = get_message_with_args( + "tr-error-extra-operand-simple", + HashMap::from([("operand".to_string(), op.to_string())]), + ); return Err(UUsageError::new(1, msg)); } } @@ -103,7 +110,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // The trailing backslash has a non-backslash character before it. show!(USimpleError::new( 0, - "warning: an unescaped backslash at end of string is not portable" + get_message("tr-warning-unescaped-backslash") )); } } @@ -125,7 +132,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { )?; if is_stdin_directory(&stdin) { - return Err(USimpleError::new(1, "read error: Is a directory")); + return Err(USimpleError::new(1, get_message("tr-error-read-directory"))); } // '*_op' are the operations that need to be applied, in order. @@ -156,7 +163,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { buffered_stdout .flush() - .map_err_context(|| "write error".into())?; + .map_err_context(|| get_message("tr-error-write-error"))?; Ok(()) } @@ -173,7 +180,7 @@ pub fn uu_app() -> Command { .visible_short_alias('C') .short('c') .long(options::COMPLEMENT) - .help("use the complement of SET1") + .help(get_message("tr-help-complement")) .action(ArgAction::SetTrue) .overrides_with(options::COMPLEMENT), ) @@ -181,7 +188,7 @@ pub fn uu_app() -> Command { Arg::new(options::DELETE) .short('d') .long(options::DELETE) - .help("delete characters in SET1, do not translate") + .help(get_message("tr-help-delete")) .action(ArgAction::SetTrue) .overrides_with(options::DELETE), ) @@ -189,11 +196,7 @@ pub fn uu_app() -> Command { Arg::new(options::SQUEEZE) .long(options::SQUEEZE) .short('s') - .help( - "replace each sequence of a repeated character that is \ - listed in the last specified SET, with a single occurrence \ - of that character", - ) + .help(get_message("tr-help-squeeze")) .action(ArgAction::SetTrue) .overrides_with(options::SQUEEZE), ) @@ -201,7 +204,7 @@ pub fn uu_app() -> Command { Arg::new(options::TRUNCATE_SET1) .long(options::TRUNCATE_SET1) .short('t') - .help("first truncate SET1 to length of SET2") + .help(get_message("tr-help-truncate-set1")) .action(ArgAction::SetTrue) .overrides_with(options::TRUNCATE_SET1), ) diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 847211192..8c16ba9ed 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -1521,7 +1521,7 @@ fn test_multibyte_octal_sequence() { .args(&["-d", r"\501"]) .pipe_in("(1Ł)") .succeeds() - .stderr_is("tr: warning: the ambiguous octal escape \\501 is being\n interpreted as the 2-byte sequence \\050, 1\n") + .stderr_is("tr: warning: the ambiguous octal escape \\501 is being interpreted as the 2-byte sequence \\050, 1\n") .stdout_is("Ł)"); }