1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

l10n: port tr for translation + add french

This commit is contained in:
Sylvestre Ledru 2025-06-08 11:34:39 +02:00 committed by Sylvestre Ledru
parent a769fc7904
commit aba6128fc1
5 changed files with 173 additions and 46 deletions

View file

@ -1,3 +1,41 @@
tr-about = Translate or delete characters tr-about = Translate or delete characters
tr-usage = tr [OPTION]... SET1 [SET2] tr-usage = tr [OPTION]... SET1 [SET2]
tr-after-help = Translate, squeeze, and/or delete characters from standard input, writing to standard output. 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

View file

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

View file

@ -24,6 +24,7 @@ use std::{
ops::Not, ops::Not,
}; };
use uucore::error::{FromIo, UError, UResult}; use uucore::error::{FromIo, UError, UResult};
use uucore::locale::{get_message, get_message_with_args};
use uucore::show_warning; use uucore::show_warning;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -45,44 +46,65 @@ pub enum BadSequence {
impl Display for BadSequence { impl Display for BadSequence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::MissingCharClassName => write!(f, "missing character class name '[::]'"), Self::MissingCharClassName => {
write!(f, "{}", get_message("tr-error-missing-char-class-name"))
}
Self::MissingEquivalentClassChar => { Self::MissingEquivalentClassChar => {
write!(f, "missing equivalence class character '[==]'") write!(
f,
"{}",
get_message("tr-error-missing-equivalence-class-char")
)
} }
Self::MultipleCharRepeatInSet2 => { 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 => { 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) => { 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 => { 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 => { Self::ClassExceptLowerUpperInSet2 => {
write!( write!(
f, 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 => { Self::ClassInSet2NotMatchedBySet1 => {
write!( write!(f, "{}", get_message("tr-error-class-in-set2-not-matched"))
f,
"when translating, every 'upper'/'lower' in set2 must be matched by a 'upper'/'lower' in the same position in set1"
)
} }
Self::Set1LongerSet2EndsInClass => { Self::Set1LongerSet2EndsInClass => {
write!( write!(
f, 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 => { Self::ComplementMoreThanOneUniqueInSet2 => {
write!( write!(
f, 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 } => { Self::BackwardsRange { end, start } => {
@ -94,17 +116,25 @@ impl Display for BadSequence {
} }
} }
} }
write!( write!(
f, f,
"range-endpoints of '{}-{}' are in reverse collating sequence order", "{}",
end_or_start_to_string(start), get_message_with_args(
end_or_start_to_string(end) "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!( Self::MultipleCharInEquivalence(s) => write!(
f, 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 origin_octal: &str = std::str::from_utf8(input).unwrap();
let actual_octal_tail: &str = std::str::from_utf8(&input[0..2]).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(); 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 result
}, },
).parse(input) )
.parse(input)
} }
fn parse_octal_two_digits(input: &[u8]) -> IResult<&[u8], u8> { fn parse_octal_two_digits(input: &[u8]) -> IResult<&[u8], u8> {
@ -666,7 +710,7 @@ where
output output
.write_all(&output_buf) .write_all(&output_buf)
.map_err_context(|| "write error".into())?; .map_err_context(|| get_message("tr-error-write-error"))?;
buf.clear(); buf.clear();
output_buf.clear(); output_buf.clear();

View file

@ -13,6 +13,7 @@ use clap::{Arg, ArgAction, Command, value_parser};
use operation::{ use operation::{
Sequence, SqueezeOperation, SymbolTranslator, TranslateOperation, translate_input, Sequence, SqueezeOperation, SymbolTranslator, TranslateOperation, translate_input,
}; };
use std::collections::HashMap;
use std::ffi::OsString; use std::ffi::OsString;
use std::io::{BufWriter, Write, stdin, stdout}; use std::io::{BufWriter, Write, stdin, stdout};
use uucore::display::Quotable; use uucore::display::Quotable;
@ -20,7 +21,7 @@ use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
use uucore::fs::is_stdin_directory; use uucore::fs::is_stdin_directory;
use uucore::{format_usage, os_str_as_bytes, show}; 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 { mod options {
pub const COMPLEMENT: &str = "complement"; pub const COMPLEMENT: &str = "complement";
@ -53,15 +54,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let sets_len = sets.len(); let sets_len = sets.len();
if sets.is_empty() { 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 { if !(delete_flag || squeeze_flag) && sets_len < 2 {
return Err(UUsageError::new( return Err(UUsageError::new(
1, 1,
format!( get_message_with_args(
"missing operand after {}\nTwo strings must be given when translating.", "tr-error-missing-operand-translating",
sets[0].quote() 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 { if delete_flag & squeeze_flag && sets_len < 2 {
return Err(UUsageError::new( return Err(UUsageError::new(
1, 1,
format!( get_message_with_args(
"missing operand after {}\nTwo strings must be given when deleting and squeezing.", "tr-error-missing-operand-deleting-squeezing",
sets[0].quote() HashMap::from([("set".to_string(), sets[0].quote().to_string())]),
), ),
)); ));
} }
if sets_len > 1 { if sets_len > 1 {
let start = "extra operand";
if delete_flag && !squeeze_flag { if delete_flag && !squeeze_flag {
let op = sets[1].quote(); let op = sets[1].quote();
let msg = if sets_len == 2 { let msg = if sets_len == 2 {
format!( get_message_with_args(
"{start} {op}\nOnly one string may be given when deleting without squeezing repeats.", "tr-error-extra-operand-deleting-without-squeezing",
HashMap::from([("operand".to_string(), op.to_string())]),
) )
} else { } 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)); return Err(UUsageError::new(1, msg));
} }
if sets_len > 2 { if sets_len > 2 {
let op = sets[2].quote(); 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)); 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. // The trailing backslash has a non-backslash character before it.
show!(USimpleError::new( show!(USimpleError::new(
0, 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) { 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. // '*_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 buffered_stdout
.flush() .flush()
.map_err_context(|| "write error".into())?; .map_err_context(|| get_message("tr-error-write-error"))?;
Ok(()) Ok(())
} }
@ -173,7 +180,7 @@ pub fn uu_app() -> Command {
.visible_short_alias('C') .visible_short_alias('C')
.short('c') .short('c')
.long(options::COMPLEMENT) .long(options::COMPLEMENT)
.help("use the complement of SET1") .help(get_message("tr-help-complement"))
.action(ArgAction::SetTrue) .action(ArgAction::SetTrue)
.overrides_with(options::COMPLEMENT), .overrides_with(options::COMPLEMENT),
) )
@ -181,7 +188,7 @@ pub fn uu_app() -> Command {
Arg::new(options::DELETE) Arg::new(options::DELETE)
.short('d') .short('d')
.long(options::DELETE) .long(options::DELETE)
.help("delete characters in SET1, do not translate") .help(get_message("tr-help-delete"))
.action(ArgAction::SetTrue) .action(ArgAction::SetTrue)
.overrides_with(options::DELETE), .overrides_with(options::DELETE),
) )
@ -189,11 +196,7 @@ pub fn uu_app() -> Command {
Arg::new(options::SQUEEZE) Arg::new(options::SQUEEZE)
.long(options::SQUEEZE) .long(options::SQUEEZE)
.short('s') .short('s')
.help( .help(get_message("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",
)
.action(ArgAction::SetTrue) .action(ArgAction::SetTrue)
.overrides_with(options::SQUEEZE), .overrides_with(options::SQUEEZE),
) )
@ -201,7 +204,7 @@ pub fn uu_app() -> Command {
Arg::new(options::TRUNCATE_SET1) Arg::new(options::TRUNCATE_SET1)
.long(options::TRUNCATE_SET1) .long(options::TRUNCATE_SET1)
.short('t') .short('t')
.help("first truncate SET1 to length of SET2") .help(get_message("tr-help-truncate-set1"))
.action(ArgAction::SetTrue) .action(ArgAction::SetTrue)
.overrides_with(options::TRUNCATE_SET1), .overrides_with(options::TRUNCATE_SET1),
) )

View file

@ -1521,7 +1521,7 @@ fn test_multibyte_octal_sequence() {
.args(&["-d", r"\501"]) .args(&["-d", r"\501"])
.pipe_in("(1Ł)") .pipe_in("(1Ł)")
.succeeds() .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("Ł)"); .stdout_is("Ł)");
} }