feat: phase 2 — i18n messages EN/FR (--lang, AM_LANG/LANG, catalogue tr/tr_fmt, REPL lang) (closes #74)
This commit is contained in:
+79
-15
@@ -120,6 +120,7 @@ const COMMAND_DESCRIPTIONS: &[(&str, &str)] = &[
|
||||
("plugins", "list the event plugins and test one (JSON contract on stdin/stdout)"),
|
||||
("shell", "show or switch the system shell"),
|
||||
("theme", "show or switch the color theme"),
|
||||
("lang", "show or switch the output language (fr/en)"),
|
||||
("tip", "cheat sheet of the most useful commands"),
|
||||
("ls", "list files and directories (Nushell-style)"),
|
||||
("dir", "list files and directories (same as ls)"),
|
||||
@@ -229,7 +230,7 @@ impl AmCompleter {
|
||||
commands: vec![
|
||||
"list", "status", "sessions", "stats", "top", "report", "projects", "timeline", "log", "logs", "history", "init", "alias", "secret", "open", "watch", "search", "info", "install", "uninstall", "update",
|
||||
"start", "stop", "restart", "run", "doctor", "config", "completion",
|
||||
"self-update", "self-uninstall", "export", "import", "shell", "theme",
|
||||
"self-update", "self-uninstall", "export", "import", "shell", "theme", "lang",
|
||||
"tip", "dashboard", "favorite", "unfavorite", "note", "tag", "untag", "tags",
|
||||
"profile", "man", "models", "catalog", "suggest", "audit",
|
||||
"service", "schedule", "monitor", "sync", "migrate", "playbook", "lab", "plugins",
|
||||
@@ -296,6 +297,9 @@ impl AmCompleter {
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
}
|
||||
"lang" => {
|
||||
pool = vec!["fr".to_string(), "en".to_string()];
|
||||
}
|
||||
"cd" | "chdir" => {
|
||||
// Complete directories only — dive one level per Tab.
|
||||
let cwd = current_dir();
|
||||
@@ -637,6 +641,7 @@ pub fn banner_box(
|
||||
session: &ShellSession,
|
||||
session_id: &str,
|
||||
cwd: &str,
|
||||
lang: &str,
|
||||
theme: &Theme,
|
||||
color: bool,
|
||||
) -> String {
|
||||
@@ -791,7 +796,7 @@ pub fn banner_box(
|
||||
" system self-update · self-uninstall · export · import".to_string(),
|
||||
));
|
||||
rows.push(inner(
|
||||
" shell ls · dir · cd · ps · where · get · shell · theme · help · version · exit"
|
||||
" shell ls · dir · cd · ps · where · get · shell · theme · lang · help · version · exit"
|
||||
.to_string(),
|
||||
));
|
||||
|
||||
@@ -799,9 +804,10 @@ pub fn banner_box(
|
||||
rows.push(blank());
|
||||
rows.push(inner(dim(
|
||||
&format!(
|
||||
"am v{version} · {agents} agents · {external} external · {installed} managed · shell {} · theme {} · /help for commands",
|
||||
"am v{version} · {agents} agents · {external} external · {installed} managed · shell {} · theme {} · lang {} · /help for commands",
|
||||
session.current.name,
|
||||
theme.name
|
||||
theme.name,
|
||||
lang,
|
||||
),
|
||||
theme,
|
||||
color,
|
||||
@@ -831,6 +837,7 @@ pub fn banner(app: &App, session: &ShellSession, session_id: &str) -> String {
|
||||
session,
|
||||
session_id,
|
||||
¤t_dir_string(),
|
||||
app.lang().code(),
|
||||
theme,
|
||||
color,
|
||||
);
|
||||
@@ -1255,6 +1262,10 @@ fn handle_input(
|
||||
theme_command(app, None);
|
||||
return Ok(false);
|
||||
}
|
||||
"lang" | "/lang" => {
|
||||
lang_command(app, None);
|
||||
return Ok(false);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// History expansion: !! !N !prefix ^old^new (re-execution, issue #27).
|
||||
@@ -1274,6 +1285,7 @@ fn handle_input(
|
||||
match cmd {
|
||||
"shell" => shell_command(session, arg),
|
||||
"theme" => theme_command(app, arg),
|
||||
"lang" => lang_command(app, arg),
|
||||
"help" => match arg {
|
||||
Some(topic) => {
|
||||
if let Err(e) = crate::help::print_command(app, topic) {
|
||||
@@ -1284,7 +1296,7 @@ fn handle_input(
|
||||
},
|
||||
_ => app
|
||||
.log
|
||||
.error(&format!("unknown command '/{cmd}' — type '/help'")),
|
||||
.error(crate::tr_fmt!("unknown command '/{}' — type '/help'", cmd)),
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -1354,6 +1366,11 @@ fn handle_line(
|
||||
theme_command(app, rest.first().map(|s| s.as_str()));
|
||||
return Ok(false);
|
||||
}
|
||||
// lang | lang <fr|en>: show the output language or switch it (issue #74).
|
||||
if cmd == "lang" {
|
||||
lang_command(app, rest.first().map(|s| s.as_str()));
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Session directory change: kept in the am process, not a child shell.
|
||||
if matches!(cmd, "cd" | "chdir") {
|
||||
@@ -1450,7 +1467,7 @@ fn handle_line(
|
||||
}),
|
||||
Some("list") | None => Command::Secret(crate::cli::SecretCmd::List),
|
||||
_ => {
|
||||
app.log.error("usage: secret set NAME --agent A --value V | secret unset NAME --agent A | secret list");
|
||||
app.log.error(crate::i18n::tr("usage: secret set NAME --agent A --value V | secret unset NAME --agent A | secret list"));
|
||||
return Ok(false);
|
||||
}
|
||||
},
|
||||
@@ -1467,13 +1484,13 @@ fn handle_line(
|
||||
}),
|
||||
Some("list") => Command::Catalog(crate::cli::CatalogCmd::List),
|
||||
_ => {
|
||||
app.log.error("usage: catalog update | catalog add <url> | catalog list");
|
||||
app.log.error(crate::i18n::tr("usage: catalog update | catalog add <url> | catalog list"));
|
||||
return Ok(false);
|
||||
}
|
||||
},
|
||||
"suggest" => {
|
||||
if rest.is_empty() {
|
||||
app.log.error("usage: suggest <requête> — ex: suggest un agent pour du Python");
|
||||
app.log.error(crate::i18n::tr("usage: suggest <requête> — ex: suggest un agent pour du Python"));
|
||||
return Ok(false);
|
||||
}
|
||||
Command::Suggest {
|
||||
@@ -1490,7 +1507,7 @@ fn handle_line(
|
||||
}),
|
||||
Some("list") | None => Command::Alias(crate::cli::AliasCmd::List),
|
||||
_ => {
|
||||
app.log.error("usage: alias add <name> <target> | alias remove <name> | alias list");
|
||||
app.log.error(crate::i18n::tr("usage: alias add <name> <target> | alias remove <name> | alias list"));
|
||||
return Ok(false);
|
||||
}
|
||||
},
|
||||
@@ -1525,7 +1542,8 @@ fn handle_line(
|
||||
Some("list") | None => Command::Profile(crate::cli::ProfileCmd::List),
|
||||
other => {
|
||||
app.log.error(&format!(
|
||||
"usage: profile list | profile show <name>{}",
|
||||
"{}{}",
|
||||
crate::i18n::tr("usage: profile list | profile show <name>"),
|
||||
other.map(|o| format!(" (unknown 'profile {o}')")).unwrap_or_default()
|
||||
));
|
||||
return Ok(false);
|
||||
@@ -1611,7 +1629,7 @@ fn handle_line(
|
||||
},
|
||||
"playbook" => {
|
||||
if rest.is_empty() {
|
||||
app.log.error("usage: playbook <file> — ex: playbook deploy.yaml");
|
||||
app.log.error(crate::i18n::tr("usage: playbook <file> — ex: playbook deploy.yaml"));
|
||||
return Ok(false);
|
||||
}
|
||||
Command::Playbook {
|
||||
@@ -1747,7 +1765,7 @@ fn handle_line(
|
||||
id: rest[1].clone(),
|
||||
}),
|
||||
_ => {
|
||||
app.log.error("usage: schedule add <cmd...> | schedule list | schedule remove <id>");
|
||||
app.log.error(crate::i18n::tr("usage: schedule add <cmd...> | schedule list | schedule remove <id>"));
|
||||
return Ok(false);
|
||||
}
|
||||
},
|
||||
@@ -1793,7 +1811,7 @@ fn handle_line(
|
||||
},
|
||||
other => {
|
||||
app.log
|
||||
.error(&format!("unknown command '{other}' — type 'help'"));
|
||||
.error(crate::tr_fmt!("unknown command '{}' — type 'help'", other));
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
@@ -1808,7 +1826,7 @@ fn handle_line(
|
||||
fn is_shell_command(word: &str) -> bool {
|
||||
matches!(
|
||||
word,
|
||||
"ls" | "dir" | "ps" | "where" | "get" | "cd" | "shell" | "theme" | "help" | "version" | "exit"
|
||||
"ls" | "dir" | "ps" | "where" | "get" | "cd" | "shell" | "theme" | "lang" | "help" | "version" | "exit"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2032,7 +2050,7 @@ fn run_system(app: &App, session: &ShellSession, line: &str) -> Result<()> {
|
||||
let code = session.run(line)?;
|
||||
if code != 0 {
|
||||
app.log
|
||||
.error(&format!("command exited with code {code}"));
|
||||
.error(crate::tr_fmt!("command exited with code {}", code));
|
||||
}
|
||||
app.emit(
|
||||
&Event::now(EventKind::Shell)
|
||||
@@ -2130,6 +2148,48 @@ fn print_theme_status(app: &App) {
|
||||
);
|
||||
}
|
||||
|
||||
/// Show or switch the output language (issue #74). With no argument the
|
||||
/// current locale is shown; with 'fr' or 'en' it is switched for the rest
|
||||
/// of the session (the i18n global follows immediately).
|
||||
fn lang_command(app: &App, arg: Option<&str>) {
|
||||
match arg.map(str::trim).filter(|s| !s.is_empty()) {
|
||||
None => print_lang_status(app),
|
||||
Some(code) => match code.to_ascii_lowercase().as_str() {
|
||||
"fr" | "french" | "français" => {
|
||||
app.set_lang(crate::i18n::Lang::Fr);
|
||||
println!("language: {}", crate::i18n::Lang::Fr.label());
|
||||
}
|
||||
"en" | "english" | "anglais" => {
|
||||
app.set_lang(crate::i18n::Lang::En);
|
||||
println!("language: {}", crate::i18n::Lang::En.label());
|
||||
}
|
||||
other => app.log.error(format!(
|
||||
"unknown language '{other}' — use 'lang fr' or 'lang en'"
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Show the active language and the switch hint.
|
||||
fn print_lang_status(app: &App) {
|
||||
let lang = app.lang();
|
||||
let color = app.color();
|
||||
let theme = app.theme();
|
||||
let hdr = |s: &str| -> String {
|
||||
if color {
|
||||
theme.hdr(s)
|
||||
} else {
|
||||
s.to_string()
|
||||
}
|
||||
};
|
||||
println!("{}", hdr("Language"));
|
||||
println!(" ◉ {} ({})", theme.acc(lang.label()), lang.code());
|
||||
println!(
|
||||
" {}",
|
||||
theme.dimmed("'lang <fr|en>' to switch (this session) · 'am --lang <fr|en>' at startup")
|
||||
);
|
||||
}
|
||||
|
||||
fn print_shell_status(session: &ShellSession) {
|
||||
let others: Vec<&str> = session
|
||||
.available
|
||||
@@ -2296,6 +2356,7 @@ mod tests {
|
||||
&session,
|
||||
"20260815_123456_abcdef",
|
||||
"/home/alice/dev/agent-manager",
|
||||
"fr",
|
||||
crate::theme::default_theme(),
|
||||
false,
|
||||
);
|
||||
@@ -2333,6 +2394,7 @@ mod tests {
|
||||
&session,
|
||||
"20260815_123456_abcdef",
|
||||
"/home/alice/dev/agent-manager",
|
||||
"fr",
|
||||
crate::theme::default_theme(),
|
||||
false,
|
||||
);
|
||||
@@ -2360,6 +2422,7 @@ mod tests {
|
||||
&session,
|
||||
"20260815_123456_abcdef",
|
||||
"/home/alice/dev/agent-manager",
|
||||
"fr",
|
||||
ocean,
|
||||
true,
|
||||
);
|
||||
@@ -2463,6 +2526,7 @@ mod tests {
|
||||
&session,
|
||||
"20260815_123456_abcdef",
|
||||
"/home/alice/dev/agent-manager",
|
||||
"fr",
|
||||
ocean,
|
||||
true,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user