Skip to content

Commit c635fdd

Browse files
committed
add localization
1 parent 4f9a089 commit c635fdd

File tree

10 files changed

+108
-50
lines changed

10 files changed

+108
-50
lines changed

sshdconfig/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sshdconfig/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ lto = true
1717
atty = { version = "0.2" }
1818
chrono = { version = "0.4" }
1919
clap = { version = "4.5", features = ["derive"] }
20+
rust-i18n = { version = "3.1" }
2021
schemars = "0.9"
2122
serde = { version = "1.0", features = ["derive"] }
2223
serde_json = { version = "1.0", features = ["preserve_order"] }

sshdconfig/locales/en-us.toml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
_version = 1
2+
3+
[args]
4+
setInput = "input to set in sshd_config"
5+
6+
[error]
7+
command = "Command"
8+
invalidInput = "Invalid Input"
9+
json = "JSON"
10+
language = "Language"
11+
notImplemented = "Not Implemented"
12+
parser = "Parser"
13+
parseInt = "Parse Integer"
14+
registry = "Registry"
15+
16+
[get]
17+
notImplemented = "get not yet implemented for Microsoft.OpenSSH.SSHD/sshd_config"
18+
defaultShellEmpty = "default_shell cannot be empty"
19+
defaultShellMustBeString = "default_shell must be a string"
20+
defaultShellCmdOptionMustBeString = "value must be a string"
21+
defaultShellEscapeArgsMustBe0Or1 = "'%{input}' must be a 0 or 1"
22+
defaultShellEscapeArgsMustBeDWord = "value must be a DWord"
23+
windowsOnly = "Microsoft.OpenSSH.SSHD/Windows is only applicable to Windows"
24+
25+
[set]
26+
failedToParseInput = "failed to parse input as DefaultShell with error: '%{error}'"
27+
shellPathMustNotBeRelative = "shell path must not be relative"
28+
shellPathDoesNotExist = "shell path does not exist: '%{shell}'"
29+
30+
[parser]
31+
failedToParse = "failed to parse: '%{input}'"
32+
failedToParseRoot = "failed to parse root: '%{input}'"
33+
invalidConfig = "invalid config: '%{input}'"
34+
unknownNodeType = "unknown node type: '%{node}'"
35+
unexpectedFalse = "unexpected 'false' for '%{input}'; expected 'yes' or 'no'"
36+
failedToParseChildNode = "failed to parse child node: '%{input}'"
37+
missingValueInChildNode = "missing value in child node: '%{input}'"
38+
missingKeyInChildNode = "missing key in child node: '%{input}'"
39+
failedToParseAsArray = "value is not an array"
40+
failedToParseNode = "failed to parse '%{input}'"
41+
keyNotFound = "key '%{key}' not found"
42+
keyNotRepeatable = "key '%{key}' is not repeatable"
43+
invalidValue = "operator is an invalid value for node"
44+
unknownNode = "unknown node: '%{kind}'"
45+
46+
[util]
47+
sshdNoHostkeys = "elevated security context required"

sshdconfig/src/args.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
use clap::{Parser, Subcommand, ValueEnum};
5+
use rust_i18n::t;
56
use schemars::JsonSchema;
67
use serde::{Deserialize, Serialize};
78

@@ -20,7 +21,7 @@ pub enum Command {
2021
},
2122
/// Set default shell, eventually to be used for `sshd_config` and repeatable keywords
2223
Set {
23-
#[clap(short = 'i', long, help = "input to set in sshd_config")]
24+
#[clap(short = 'i', long, help = t!("args.setInput").to_string())]
2425
input: String
2526
},
2627
/// Export `sshd_config`

sshdconfig/src/error.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
use rust_i18n::t;
45
use thiserror::Error;
56

67
#[derive(Debug, Error)]
78
pub enum SshdConfigError {
8-
#[error("Command: {0}")]
9+
#[error("{t}: {0}", t = t!("error.command"))]
910
CommandError(String),
10-
#[error("IO: {0}")]
11+
#[error("{t}: {0}", t = t!("error.invalidInput"))]
1112
InvalidInput(String),
12-
#[error("JSON: {0}")]
13+
#[error("{t}: {0}", t = t!("error.json"))]
1314
Json(#[from] serde_json::Error),
14-
#[error("Language: {0}")]
15+
#[error("{t}: {0}", t = t!("error.language"))]
1516
LanguageError(#[from] tree_sitter::LanguageError),
16-
#[error("Not Implemented: {0}")]
17+
#[error("{t}: {0}", t = t!("error.notImplemented"))]
1718
NotImplemented(String),
18-
#[error("Parser: {0}")]
19+
#[error("{t}: {0}", t = t!("error.parser"))]
1920
ParserError(String),
20-
#[error("Parser Int: {0}")]
21+
#[error("{t}: {0}", t = t!("error.parseInt"))]
2122
ParseIntError(#[from] std::num::ParseIntError),
2223
#[cfg(windows)]
23-
#[error("Registry: {0}")]
24+
#[error("{t}: {0}", t = t!("error.registry"))]
2425
RegistryError(#[from] registry_lib::error::RegistryError),
2526
}

sshdconfig/src/get.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use {
99
};
1010

1111
use crate::error::SshdConfigError;
12+
use rust_i18n::t;
1213

1314
/// Invoke the get command.
1415
///
@@ -18,7 +19,7 @@ use crate::error::SshdConfigError;
1819
pub fn invoke_get(resource: &Resource) -> Result<(), SshdConfigError> {
1920
match resource {
2021
&Resource::DefaultShell => get_default_shell(),
21-
&Resource::SshdConfig => Err(SshdConfigError::NotImplemented("get not yet implemented for Microsoft.OpenSSH.SSHD/sshd_config".to_string())),
22+
&Resource::SshdConfig => Err(SshdConfigError::NotImplemented(t!("get.notImplemented").to_string())),
2223
}
2324
}
2425

@@ -34,14 +35,14 @@ fn get_default_shell() -> Result<(), SshdConfigError> {
3435
RegistryValueData::String(s) => {
3536
let parts: Vec<&str> = s.split_whitespace().collect();
3637
if parts.is_empty() {
37-
return Err(SshdConfigError::InvalidInput(format!("{} cannot be empty", DEFAULT_SHELL)));
38+
return Err(SshdConfigError::InvalidInput(t!("get.defaultShellEmpty").to_string()));
3839
}
3940
shell = Some(parts[0].to_string());
4041
if parts.len() > 1 {
4142
shell_arguments = Some(parts[1..].iter().map(|&s| s.to_string()).collect());
4243
}
4344
}
44-
_ => return Err(SshdConfigError::InvalidInput(format!("{} must be a string", DEFAULT_SHELL))),
45+
_ => return Err(SshdConfigError::InvalidInput(t!("get.defaultShellMustBeString").to_string())),
4546
}
4647
}
4748

@@ -51,7 +52,7 @@ fn get_default_shell() -> Result<(), SshdConfigError> {
5152
if let Some(value) = option.value_data {
5253
match value {
5354
RegistryValueData::String(s) => cmd_option = Some(s),
54-
_ => return Err(SshdConfigError::InvalidInput(format!("{} must be a string", DEFAULT_SHELL_CMD_OPTION))),
55+
_ => return Err(SshdConfigError::InvalidInput(t!("get.defaultShellCmdOptionMustBeString").to_string())),
5556
}
5657
}
5758

@@ -63,10 +64,10 @@ fn get_default_shell() -> Result<(), SshdConfigError> {
6364
if b == 0 || b == 1 {
6465
escape_arguments = if b == 1 { Some(true) } else { Some(false) };
6566
} else {
66-
return Err(SshdConfigError::InvalidInput(format!("{} must be a 0 or 1", DEFAULT_SHELL_ESCAPE_ARGS)));
67+
return Err(SshdConfigError::InvalidInput(t!("get.defaultShellEscapeArgsMustBe0Or1", input = b).to_string()));
6768
}
6869
} else {
69-
return Err(SshdConfigError::InvalidInput(format!("{} must be a DWord", DEFAULT_SHELL_ESCAPE_ARGS)));
70+
return Err(SshdConfigError::InvalidInput(t!("get.defaultShellEscapeArgsMustBeDWord").to_string()));
7071
}
7172
}
7273

@@ -83,6 +84,6 @@ fn get_default_shell() -> Result<(), SshdConfigError> {
8384
}
8485

8586
#[cfg(not(windows))]
86-
pub fn get_default_shell() -> Result<(), SshdConfigError> {
87-
Err(SshdConfigError::InvalidInput("Microsoft.OpenSSH.SSHD/Windows is only applicable to Windows".to_string()))
87+
fn get_default_shell() -> Result<(), SshdConfigError> {
88+
Err(SshdConfigError::InvalidInput(t!("get.windowsOnly").to_string()))
8889
}

sshdconfig/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
use clap::{Parser};
5+
use rust_i18n::i18n;
56
use schemars::schema_for;
67

78
use args::{Args, Command, DefaultShell, Resource};
@@ -19,6 +20,8 @@ mod parser;
1920
mod set;
2021
mod util;
2122

23+
i18n!("locales", fallback = "en-us");
24+
2225
fn main() {
2326
let args = Args::parse();
2427

sshdconfig/src/parser.rs

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use tree_sitter::Parser;
77

88
use crate::error::SshdConfigError;
99
use crate::metadata::{MULTI_ARG_KEYWORDS, REPEATABLE_KEYWORDS};
10+
use rust_i18n::t;
1011

1112
#[derive(Debug, JsonSchema)]
1213
pub struct SshdConfigParser {
@@ -35,14 +36,14 @@ impl SshdConfigParser {
3536
parser.set_language(&tree_sitter_ssh_server_config::LANGUAGE.into())?;
3637

3738
let Some(tree) = &mut parser.parse(input, None) else {
38-
return Err(SshdConfigError::ParserError(format!("failed to parse: {input}")));
39+
return Err(SshdConfigError::ParserError(t!("parser.failedToParse", input = input).to_string()));
3940
};
4041
let root_node = tree.root_node();
4142
if root_node.is_error() {
42-
return Err(SshdConfigError::ParserError(format!("failed to parse root: {input}")));
43+
return Err(SshdConfigError::ParserError(t!("parser.failedToParseRoot", input = input).to_string()));
4344
}
4445
if root_node.kind() != "server_config" {
45-
return Err(SshdConfigError::ParserError(format!("invalid config: {input}")));
46+
return Err(SshdConfigError::ParserError(t!("parser.invalidConfig", input = input).to_string()));
4647
}
4748
let input_bytes = input.as_bytes();
4849
let mut cursor = root_node.walk();
@@ -54,12 +55,12 @@ impl SshdConfigParser {
5455

5556
fn parse_child_node(&mut self, node: tree_sitter::Node, input: &str, input_bytes: &[u8]) -> Result<(), SshdConfigError> {
5657
if node.is_error() {
57-
return Err(SshdConfigError::ParserError(format!("failed to parse: {input}")));
58+
return Err(SshdConfigError::ParserError(t!("parser.failedToParse", input = input).to_string()));
5859
}
5960
match node.kind() {
6061
"keyword" => self.parse_keyword_node(node, input, input_bytes),
6162
"comment" | "empty_line" => Ok(()),
62-
_ => Err(SshdConfigError::ParserError(format!("unknown node type: {}", node.kind()))),
63+
_ => Err(SshdConfigError::ParserError(t!("parser.unknownNodeType", node = node.kind()).to_string())),
6364
}
6465
}
6566

@@ -72,9 +73,9 @@ impl SshdConfigParser {
7273

7374
if let Some(keyword) = keyword_node.child_by_field_name("keyword") {
7475
let Ok(text) = keyword.utf8_text(input_bytes) else {
75-
return Err(SshdConfigError::ParserError(format!(
76-
"failed to parse keyword node: {input}"
77-
)));
76+
return Err(SshdConfigError::ParserError(
77+
t!("parser.failedToParseChildNode", input = input).to_string()
78+
));
7879
};
7980
if REPEATABLE_KEYWORDS.contains(&text) {
8081
is_repeatable = true;
@@ -87,19 +88,19 @@ impl SshdConfigParser {
8788

8889
for node in keyword_node.named_children(&mut cursor) {
8990
if node.is_error() {
90-
return Err(SshdConfigError::ParserError(format!("failed to parse child node: {input}")));
91+
return Err(SshdConfigError::ParserError(t!("parser.failedToParseChildNode", input = input).to_string()));
9192
}
9293
if node.kind() == "arguments" {
9394
value = parse_arguments_node(node, input, input_bytes, is_vec)?;
9495
}
9596
}
9697
if let Some(key) = key {
9798
if value.is_null() {
98-
return Err(SshdConfigError::ParserError(format!("missing value in child node: {input}")));
99+
return Err(SshdConfigError::ParserError(t!("parser.missingValueInChildNode", input = input).to_string()));
99100
}
100101
return self.update_map(&key, value, is_repeatable);
101102
}
102-
Err(SshdConfigError::ParserError(format!("missing key in child node: {input}")))
103+
Err(SshdConfigError::ParserError(t!("parser.missingKeyInChildNode", input = input).to_string()))
103104
}
104105

105106
fn update_map(&mut self, key: &str, value: Value, is_repeatable: bool) -> Result<(), SshdConfigError> {
@@ -114,19 +115,19 @@ impl SshdConfigParser {
114115
}
115116
} else {
116117
return Err(SshdConfigError::ParserError(
117-
"value is not an array".to_string(),
118+
t!("parser.failedToParseAsArray").to_string()
118119
));
119120
}
120121
} else {
121122
return Err(SshdConfigError::ParserError(
122-
"value is not an array".to_string(),
123+
t!("parser.failedToParseAsArray").to_string()
123124
));
124125
}
125126
} else {
126-
return Err(SshdConfigError::ParserError(format!("key {key} not found")));
127+
return Err(SshdConfigError::ParserError(t!("parser.keyNotFound", key = key).to_string()));
127128
}
128129
} else {
129-
return Err(SshdConfigError::ParserError(format!("key {key} is not repeatable")));
130+
return Err(SshdConfigError::ParserError(t!("parser.keyNotRepeatable", key = key).to_string()));
130131
}
131132
} else {
132133
self.map.insert(key.to_string(), value);
@@ -142,32 +143,32 @@ fn parse_arguments_node(arg_node: tree_sitter::Node, input: &str, input_bytes: &
142143
for node in arg_node.named_children(&mut cursor) {
143144

144145
if node.is_error() {
145-
return Err(SshdConfigError::ParserError(format!("failed to parse child node: {input}")));
146+
return Err(SshdConfigError::ParserError(t!("parser.failedToParseChildNode", input = input).to_string()));
146147
}
147148
let argument: Value = match node.kind() {
148149
"boolean" | "string" => {
149150
let Ok(arg) = node.utf8_text(input_bytes) else {
150-
return Err(SshdConfigError::ParserError(format!(
151-
"failed to parse string node: {input}"
152-
)));
151+
return Err(SshdConfigError::ParserError(
152+
t!("parser.failedToParseNode", input = input).to_string()
153+
));
153154
};
154155
Value::String(arg.to_string())
155156
}
156157
"number" => {
157158
let Ok(arg) = node.utf8_text(input_bytes) else {
158-
return Err(SshdConfigError::ParserError(format!(
159-
"failed to parse string node: {input}"
160-
)));
159+
return Err(SshdConfigError::ParserError(
160+
t!("parser.failedToParseNode", input = input).to_string()
161+
));
161162
};
162163
Value::Number(arg.parse::<u64>()?.into())
163164
}
164165
"operator" => {
165166
// TODO: handle operator if not parsing from SSHD -T
166-
return Err(SshdConfigError::ParserError(format!(
167-
"todo - unsuported node: {}", node.kind()
168-
)));
167+
return Err(SshdConfigError::ParserError(
168+
t!("parser.invalidValue").to_string()
169+
));
169170
}
170-
_ => return Err(SshdConfigError::ParserError(format!("unknown node: {}", node.kind())))
171+
_ => return Err(SshdConfigError::ParserError(t!("parser.unknownNode", kind = node.kind()).to_string()))
171172
};
172173
if is_vec {
173174
vec.push(argument);

sshdconfig/src/set.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use {
1010

1111
use crate::args::DefaultShell;
1212
use crate::error::SshdConfigError;
13+
use rust_i18n::t;
1314

1415
/// Invoke the set command.
1516
///
@@ -23,7 +24,7 @@ pub fn invoke_set(input: &str) -> Result<(), SshdConfigError> {
2324
},
2425
Err(e) => {
2526
// TODO: handle other commands like repeatable keywords or sshd_config modifications
26-
Err(SshdConfigError::InvalidInput(format!("Failed to parse input as DefaultShell: {e}")))
27+
Err(SshdConfigError::InvalidInput(t!("set.failedToParseInput", error = e).to_string()))
2728
}
2829
}
2930
}
@@ -33,10 +34,10 @@ fn set_default_shell(shell: Option<String>, cmd_option: Option<String>, escape_a
3334
if let Some(shell) = shell {
3435
let shell_path = Path::new(&shell);
3536
if shell_path.is_relative() && shell_path.components().any(|c| c == std::path::Component::ParentDir) {
36-
return Err(SshdConfigError::InvalidInput("shell path must not be relative".to_string()));
37+
return Err(SshdConfigError::InvalidInput(t!("set.shellPathMustNotBeRelative").to_string()));
3738
}
3839
if !shell_path.exists() {
39-
return Err(SshdConfigError::InvalidInput(format!("shell path does not exist: {shell}")));
40+
return Err(SshdConfigError::InvalidInput(t!("set.shellPathDoesNotExist", shell = shell).to_string()));
4041
}
4142

4243
let mut shell_data = shell.clone();
@@ -71,8 +72,8 @@ fn set_default_shell(shell: Option<String>, cmd_option: Option<String>, escape_a
7172
}
7273

7374
#[cfg(not(windows))]
74-
pub fn set_default_shell(_shell: Option<String>, _cmd_option: Option<String>, _escape_arguments: Option<bool>, _shell_arguments: Option<Vec<String>>) -> Result<(), SshdConfigError> {
75-
Err(SshdConfigError::InvalidInput("Microsoft.OpenSSH.SSHD/Windows is only applicable to Windows".to_string()))
75+
fn set_default_shell(_shell: Option<String>, _cmd_option: Option<String>, _escape_arguments: Option<bool>, _shell_arguments: Option<Vec<String>>) -> Result<(), SshdConfigError> {
76+
Err(SshdConfigError::InvalidInput(t!("get.windowsOnly")))
7677
}
7778

7879
#[cfg(windows)]

0 commit comments

Comments
 (0)