Forráskód Böngészése

fix(i18n): wire up unused Fluent keys in before_serve, host_keys, health, exec

- before_serve: use pre-serve-skip key (was raw string)
- host_keys: use host-key-no-match, host-key-header, host-key-line,
  host-key-error keys; collect_updates returns (name, ip, fp) triple
- docker/health: use cluster-section, docker-unavailable, docker-error-line,
  health-no-containers, container-count, no-sm121-containers, ssh-error,
  task-join-error keys (all existed in locales)
- exec: use exec-header, exec-section, exec-failed, exec-completed-errors
  keys; add new exec-* keys to all 6 locale files
- Fix fluent_i18n::t! 'static lifetime requirement by converting &str
  to owned String before macro invocation
Alexander Korolev 4 hónapja
szülő
commit
aa34a575e4

+ 9 - 0
locales/de/main.ftl

@@ -72,3 +72,12 @@ logs-header = 📜 Logs for cluster '{ $name }' ({ $count } nodes)
 engine-started = ✅ { $engine } started on { $name } (rank { $rank })
 engine-start-failed = Failed to start { $engine } on { $name } (rank { $rank }): { $error }
 cluster-started = 🎉 Cluster started! API → http://{ $ip }:{ $port }
+
+exec-both-specified = Specify either --cluster or --recipe, not both
+exec-missing = Must specify either --cluster or --recipe
+exec-node-not-found = Node '{ $name }' not found in cluster '{ $cluster }'
+exec-cluster-empty = Cluster '{ $name }' has no nodes
+exec-cannot-use-alias = Cannot use --alias together with a trailing command
+exec-must-specify = Must specify either --alias <name> or a command after `--`
+exec-unknown-alias-empty = Unknown alias @{ $name } (no aliases configured)
+exec-unknown-alias-list = Unknown alias @{ $name }; known aliases: { $aliases }

+ 9 - 0
locales/en/main.ftl

@@ -72,3 +72,12 @@ logs-header = 📜 Logs for cluster '{ $name }' ({ $count } nodes)
 engine-started = ✅ { $engine } started on { $name } (rank { $rank })
 engine-start-failed = Failed to start { $engine } on { $name } (rank { $rank }): { $error }
 cluster-started = 🎉 Cluster started! API → http://{ $ip }:{ $port }
+
+exec-both-specified = Specify either --cluster or --recipe, not both
+exec-missing = Must specify either --cluster or --recipe
+exec-node-not-found = Node '{ $name }' not found in cluster '{ $cluster }'
+exec-cluster-empty = Cluster '{ $name }' has no nodes
+exec-cannot-use-alias = Cannot use --alias together with a trailing command
+exec-must-specify = Must specify either --alias <name> or a command after `--`
+exec-unknown-alias-empty = Unknown alias @{ $name } (no aliases configured)
+exec-unknown-alias-list = Unknown alias @{ $name }; known aliases: { $aliases }

+ 9 - 0
locales/es/main.ftl

@@ -72,3 +72,12 @@ logs-header = 📜 Logs for cluster '{ $name }' ({ $count } nodes)
 engine-started = ✅ { $engine } started on { $name } (rank { $rank })
 engine-start-failed = Failed to start { $engine } on { $name } (rank { $rank }): { $error }
 cluster-started = 🎉 Cluster started! API → http://{ $ip }:{ $port }
+
+exec-both-specified = Specify either --cluster or --recipe, not both
+exec-missing = Must specify either --cluster or --recipe
+exec-node-not-found = Node '{ $name }' not found in cluster '{ $cluster }'
+exec-cluster-empty = Cluster '{ $name }' has no nodes
+exec-cannot-use-alias = Cannot use --alias together with a trailing command
+exec-must-specify = Must specify either --alias <name> or a command after `--`
+exec-unknown-alias-empty = Unknown alias @{ $name } (no aliases configured)
+exec-unknown-alias-list = Unknown alias @{ $name }; known aliases: { $aliases }

+ 9 - 0
locales/fr/main.ftl

@@ -72,3 +72,12 @@ logs-header = 📜 Logs for cluster '{ $name }' ({ $count } nodes)
 engine-started = ✅ { $engine } started on { $name } (rank { $rank })
 engine-start-failed = Failed to start { $engine } on { $name } (rank { $rank }): { $error }
 cluster-started = 🎉 Cluster started! API → http://{ $ip }:{ $port }
+
+exec-both-specified = Specify either --cluster or --recipe, not both
+exec-missing = Must specify either --cluster or --recipe
+exec-node-not-found = Node '{ $name }' not found in cluster '{ $cluster }'
+exec-cluster-empty = Cluster '{ $name }' has no nodes
+exec-cannot-use-alias = Cannot use --alias together with a trailing command
+exec-must-specify = Must specify either --alias <name> or a command after `--`
+exec-unknown-alias-empty = Unknown alias @{ $name } (no aliases configured)
+exec-unknown-alias-list = Unknown alias @{ $name }; known aliases: { $aliases }

+ 9 - 0
locales/it/main.ftl

@@ -72,3 +72,12 @@ logs-header = 📜 Logs for cluster '{ $name }' ({ $count } nodes)
 engine-started = ✅ { $engine } started on { $name } (rank { $rank })
 engine-start-failed = Failed to start { $engine } on { $name } (rank { $rank }): { $error }
 cluster-started = 🎉 Cluster started! API → http://{ $ip }:{ $port }
+
+exec-both-specified = Specify either --cluster or --recipe, not both
+exec-missing = Must specify either --cluster or --recipe
+exec-node-not-found = Node '{ $name }' not found in cluster '{ $cluster }'
+exec-cluster-empty = Cluster '{ $name }' has no nodes
+exec-cannot-use-alias = Cannot use --alias together with a trailing command
+exec-must-specify = Must specify either --alias <name> or a command after `--`
+exec-unknown-alias-empty = Unknown alias @{ $name } (no aliases configured)
+exec-unknown-alias-list = Unknown alias @{ $name }; known aliases: { $aliases }

+ 9 - 0
locales/ru/main.ftl

@@ -73,3 +73,12 @@ logs-header = 📜 Логи кластера '{ $name }' ({ $count } узлов)
 engine-started = ✅ { $engine } запущен на { $name } (rank { $rank })
 engine-start-failed = Не удалось запустить { $engine } на { $name } (rank { $rank }): { $error }
 cluster-started = 🎉 Кластер успешно запущен! API → http://{ $ip }:{ $port }
+
+exec-both-specified = Specify either --cluster or --recipe, not both
+exec-missing = Must specify either --cluster or --recipe
+exec-node-not-found = Node '{ $name }' not found in cluster '{ $cluster }'
+exec-cluster-empty = Cluster '{ $name }' has no nodes
+exec-cannot-use-alias = Cannot use --alias together with a trailing command
+exec-must-specify = Must specify either --alias <name> or a command after `--`
+exec-unknown-alias-empty = Unknown alias @{ $name } (no aliases configured)
+exec-unknown-alias-list = Unknown alias @{ $name }; known aliases: { $aliases }

+ 1 - 1
src/before_serve.rs

@@ -25,7 +25,7 @@ pub async fn run(
     dry_run: bool,
 ) -> Result<()> {
     if skip {
-        info!("⏭  Skipping pre-serve hooks (--skip-before-serve)");
+        info!("{}", fluent_i18n::t!("pre-serve-skip"));
         return Ok(());
     }
 

+ 24 - 8
src/docker/health.rs

@@ -4,7 +4,10 @@ use anyhow::Result;
 
 /// Checks Docker health on a cluster — shows all containers and marks ours.
 pub async fn health_check(config: &Config, cluster: &Cluster, dry_run: bool) -> Result<()> {
-    println!("\n=== Cluster: {} ===", cluster.name);
+    println!(
+        "{}",
+        fluent_i18n::t!("cluster-section", { "name" => &cluster.name })
+    );
     let mut handles = Vec::new();
     for node in &cluster.nodes {
         let config = config.clone();
@@ -40,14 +43,21 @@ pub async fn health_check(config: &Config, cluster: &Cluster, dry_run: bool) ->
                     continue;
                 }
                 if output.starts_with("docker:") || output.contains("permission denied") {
-                    println!("  ✗ Docker unavailable");
+                    println!("  {}", fluent_i18n::t!("docker-unavailable"));
                     if let Some(line) = output.lines().next() {
-                        println!("    └─ {}", line);
+                        let err = line.to_string();
+                        println!(
+                            "    └─ {}",
+                            fluent_i18n::t!("docker-error-line", { "err" => &err })
+                        );
                     }
                 } else if all_lines.is_empty() {
-                    println!("  (no containers)");
+                    println!("  {}", fluent_i18n::t!("health-no-containers"));
                 } else {
-                    println!("  Total containers: {}", all_lines.len());
+                    println!(
+                        "  {}",
+                        fluent_i18n::t!("container-count", { "count" => all_lines.len() })
+                    );
                     for line in &all_lines {
                         let name_col = line.split('\t').next().unwrap_or("");
                         let tag = if sm121_names.contains(&name_col) {
@@ -58,15 +68,21 @@ pub async fn health_check(config: &Config, cluster: &Cluster, dry_run: bool) ->
                         println!("  {tag:5} {line}");
                     }
                     if sm121_names.is_empty() {
-                        println!("  (no sm121 containers)");
+                        println!("  {}", fluent_i18n::t!("no-sm121-containers"));
                     }
                 }
             }
             Ok((name, ib_ip, Err(e))) => {
-                println!("{} ({}): ✗ SSH error: {}", name, ib_ip, e);
+                println!(
+                    "{}",
+                    fluent_i18n::t!("ssh-error", { "node" => &name, "ip" => &ib_ip, "error" => &e.to_string() })
+                );
             }
             Err(join_err) => {
-                println!("✗ Task join error: {}", join_err);
+                println!(
+                    "{}",
+                    fluent_i18n::t!("task-join-error", { "error" => &join_err.to_string() })
+                );
             }
         }
     }

+ 24 - 18
src/exec.rs

@@ -12,10 +12,10 @@ pub async fn resolve_target(
 ) -> Result<(Cluster, Option<Recipe>)> {
     match (cluster_name, recipe_name) {
         (Some(_), Some(_)) => {
-            anyhow::bail!("Specify either --cluster or --recipe, not both")
+            anyhow::bail!(fluent_i18n::t!("exec-both-specified"))
         }
         (None, None) => {
-            anyhow::bail!("Must specify either --cluster or --recipe")
+            anyhow::bail!(fluent_i18n::t!("exec-missing"))
         }
         (Some(name), None) => {
             let cluster = crate::config::select_cluster_by_name(config, name)?;
@@ -53,12 +53,19 @@ pub async fn run(
 
     if targets.is_empty() {
         if let Some(n) = node_name {
-            anyhow::bail!("Node '{}' not found in cluster '{}'", n, cluster.name);
+            let name = n.to_string();
+            anyhow::bail!(
+                fluent_i18n::t!("exec-node-not-found", { "name" => &name, "cluster" => &cluster.name })
+            );
         }
-        anyhow::bail!("Cluster '{}' has no nodes", cluster.name);
+        anyhow::bail!(fluent_i18n::t!("exec-cluster-empty", { "name" => &cluster.name }));
     }
 
-    info!("⚡ exec: {} ({} node(s))", command, targets.len());
+    let cmd = command.to_string();
+    info!(
+        "{}",
+        fluent_i18n::t!("exec-header", { "cmd" => &cmd, "nodes" => targets.len() })
+    );
 
     let mut handles = Vec::with_capacity(targets.len());
     for node in targets {
@@ -78,7 +85,7 @@ pub async fn run(
         match result {
             Ok(output) => {
                 if !output.trim().is_empty() {
-                    println!("\n--- {} ---", name);
+                    println!("{}", fluent_i18n::t!("exec-section", { "name" => &name }));
                     print!("{}", output);
                     if !output.ends_with('\n') {
                         println!();
@@ -87,13 +94,16 @@ pub async fn run(
             }
             Err(e) => {
                 any_failed = true;
-                warn!("✗ {} :: {} :: {}", name, command, e);
+                warn!(
+                    "{}",
+                    fluent_i18n::t!("exec-failed", { "name" => &name, "cmd" => &command, "error" => &e.to_string() })
+                );
             }
         }
     }
 
     if any_failed {
-        warn!("exec completed with errors (some nodes reported failures)");
+        warn!("{}", fluent_i18n::t!("exec-completed-errors"));
     }
     Ok(())
 }
@@ -115,26 +125,22 @@ pub fn resolve_command(
     let has_args = !command_args.is_empty();
     match (has_alias, has_args) {
         (true, false) => {
-            let name = alias_name.expect("checked by has_alias");
-            config.exec_aliases.get(name).cloned().ok_or_else(|| {
+            let name = alias_name.expect("checked by has_alias").to_string();
+            config.exec_aliases.get(&name).cloned().ok_or_else(|| {
                 let mut known: Vec<String> = config.exec_aliases.keys().cloned().collect();
                 known.sort();
                 if known.is_empty() {
-                    anyhow!("Unknown alias @{} (no aliases configured)", name)
+                    anyhow!(fluent_i18n::t!("exec-unknown-alias-empty", { "name" => &name }))
                 } else {
-                    anyhow!(
-                        "Unknown alias @{}; known aliases: {}",
-                        name,
-                        known.join(", ")
-                    )
+                    anyhow!(fluent_i18n::t!("exec-unknown-alias-list", { "name" => &name, "aliases" => &known.join(", ") }))
                 }
             })
         }
         (true, true) => {
-            anyhow::bail!("Cannot use --alias together with a trailing command")
+            anyhow::bail!(fluent_i18n::t!("exec-cannot-use-alias"))
         }
         (false, false) => {
-            anyhow::bail!("Must specify either --alias <name> or a command after `--`")
+            anyhow::bail!(fluent_i18n::t!("exec-must-specify"))
         }
         (false, true) => Ok(command_args.join(" ")),
     }

+ 20 - 10
src/host_keys.rs

@@ -17,13 +17,16 @@ pub async fn init_host_keys(
     let updates = collect_updates(config, cluster_filter).await?;
 
     if updates.is_empty() {
-        println!("(no nodes matched)");
+        println!("{}", fluent_i18n::t!("host-key-no-match"));
         return Ok(());
     }
 
-    println!("Discovered host keys:");
-    for (name, fp) in &updates {
-        println!("  {} -> {}", name, fp);
+    println!("{}", fluent_i18n::t!("host-key-header"));
+    for (name, ip, fp) in &updates {
+        println!(
+            "  {}",
+            fluent_i18n::t!("host-key-line", { "name" => name, "ip" => ip, "key" => fp })
+        );
     }
 
     let content = fs::read_to_string(config_path)
@@ -44,7 +47,7 @@ pub async fn init_host_keys(
 async fn collect_updates(
     config: &Config,
     cluster_filter: Option<&str>,
-) -> Result<Vec<(String, String)>> {
+) -> Result<Vec<(String, String, String)>> {
     let mut out = Vec::new();
     for cluster in &config.clusters {
         if let Some(name) = cluster_filter
@@ -54,8 +57,11 @@ async fn collect_updates(
         }
         for node in &cluster.nodes {
             match fetch_host_key(config, node).await {
-                Ok(fp) => out.push((node.name.clone(), fp)),
-                Err(e) => eprintln!("warning: failed to fetch key for {}: {}", node.name, e),
+                Ok(fp) => out.push((node.name.clone(), node.ssh_ip.clone(), fp)),
+                Err(e) => eprintln!(
+                    "{}",
+                    fluent_i18n::t!("host-key-error", { "name" => &node.name, "ip" => &node.ssh_ip, "error" => &e.to_string() })
+                ),
             }
         }
     }
@@ -65,9 +71,9 @@ async fn collect_updates(
 /// Walk `content` line by line and rewrite any node definition whose
 /// `name = "..."` matches an entry in `updates`, setting or updating the
 /// `host_key` field.
-fn update_host_keys_text(content: &str, updates: &[(String, String)]) -> String {
+fn update_host_keys_text(content: &str, updates: &[(String, String, String)]) -> String {
     let mut lines: Vec<String> = content.lines().map(String::from).collect();
-    for (name, fp) in updates {
+    for (name, _ip, fp) in updates {
         let marker = format!("name = \"{}\"", name);
         for line in &mut lines {
             if is_node_line(line) && line.contains(&marker) {
@@ -165,7 +171,11 @@ nodes = [
     { name = \"b\", rank = 1 },
 ]
 ";
-        let updates = vec![("a".to_string(), "SHA256:newA".to_string())];
+        let updates = vec![(
+            "a".to_string(),
+            "10.0.0.1".to_string(),
+            "SHA256:newA".to_string(),
+        )];
         let result = update_host_keys_text(content, &updates);
         // a was updated to include host_key
         assert!(result.contains("name = \"a\", rank = 0, host_key = \"SHA256:newA\""));