feat: phase 2 — i18n messages EN/FR (--lang, AM_LANG/LANG, catalogue tr/tr_fmt, REPL lang) (closes #74)

This commit is contained in:
2026-08-18 15:46:51 -04:00
parent 4c7866a25a
commit 7f4d74f811
19 changed files with 852 additions and 47 deletions
+34 -10
View File
@@ -435,7 +435,11 @@ pub fn render(app: &App, random: bool) -> String {
};
if random {
let (title, body) = random_tip();
return format!("💡 {} — {}\n", p.acc(title), body);
return format!(
"💡 {} — {}\n",
p.acc(crate::i18n::tr_in(app.lang(), title)),
crate::i18n::tr_in(app.lang(), body)
);
}
let mut out = String::new();
out.push_str(&header(app, &p));
@@ -444,7 +448,7 @@ pub fn render(app: &App, random: bool) -> String {
out.push_str(&section_rule(app, &p, s.icon, s.title));
out.push('\n');
for e in s.entries {
out.push_str(&entry(&p, e));
out.push_str(&entry(&p, app.lang(), e));
}
out.push('\n');
}
@@ -453,11 +457,18 @@ pub fn render(app: &App, random: bool) -> String {
let (title, body) = daily_tip();
out.push_str(&section_rule(app, &p, "💡", "Le saviez-vous ?"));
out.push('\n');
out.push_str(&format!(" {} — {}\n", p.acc(title), body));
out.push_str(&format!(
" {} — {}\n",
p.acc(crate::i18n::tr_in(app.lang(), title)),
crate::i18n::tr_in(app.lang(), body)
));
out.push('\n');
out.push_str(&format!(
" {}\n",
p.dim("am help <commande> pour le détail · am tip --random pour une astuce isolée · dans le shell am, tapez simplement 'tip'")
p.dim(crate::i18n::tr_in(
app.lang(),
"am help <commande> pour le détail · am tip --random pour une astuce isolée · dans le shell am, tapez simplement 'tip'"
))
));
out
}
@@ -528,17 +539,26 @@ fn header(app: &App, p: &Painter) -> String {
/// Section separator: " 🚀 Lancer & piloter ────────".
fn section_rule(app: &App, p: &Painter, icon: &str, title: &str) -> String {
let w = page_width(app);
let label = format!(" {icon} {title} ");
let label = format!(" {icon} {} ", crate::i18n::tr_in(app.lang(), title));
let dashes = "─".repeat(w.saturating_sub(label.chars().count()).max(1));
format!("{}{}", p.hdr(&label), p.frame(&dashes))
}
/// One cheat-sheet entry: usage line, options, example.
fn entry(p: &Painter, e: &TipEntry) -> String {
fn entry(p: &Painter, lang: crate::i18n::Lang, e: &TipEntry) -> String {
let mut out = String::new();
out.push_str(&format!(" {} {} {}\n", p.inf("▸"), p.acc(e.usage), e.about));
out.push_str(&format!(
" {} {} {}\n",
p.inf("▸"),
p.acc(e.usage),
crate::i18n::tr_in(lang, e.about)
));
for (flag, desc) in e.options {
out.push_str(&format!(" {} {}\n", p.dim(flag), desc));
out.push_str(&format!(
" {} {}\n",
p.dim(flag),
crate::i18n::tr_in(lang, desc)
));
}
if !e.example.is_empty() {
out.push_str(&format!(" {} {}\n", p.dim("❯"), p.val(e.example)));
@@ -593,8 +613,12 @@ fn most_used_section(app: &App, p: &Painter) -> String {
out.push('\n');
if top.is_empty() {
out.push_str(&format!(
" {} — lancez le shell am et tapez quelques commandes : vos habitudes apparaîtront ici.\n",
p.dim("aucune commande enregistrée")
" {} — {}\n",
p.dim(crate::i18n::tr_in(app.lang(), "aucune commande enregistrée")),
crate::i18n::tr_in(
app.lang(),
"lancez le shell am et tapez quelques commandes : vos habitudes apparaîtront ici"
)
));
return out;
}