use rust_i18n::t;
use crate::config::Config;
use crate::error::Result;
use crate::i18n::{self, SUPPORTED_LANGS};
use crate::output::Output;
pub fn execute(output: &Output, lang: Option<&str>, list: bool, reset: bool) -> Result<()> {
if list {
return list_languages(output);
}
if reset {
return reset_language(output);
}
match lang {
Some(code) => set_language(output, code),
None => show_current(output),
}
}
fn show_current(output: &Output) -> Result<()> {
let current = i18n::current();
let name = i18n::language_name(¤t).unwrap_or("Unknown");
if output.is_json() {
output.json_success(
"lang",
serde_json::json!({
"current": current,
"name": name,
}),
);
} else {
output.println(&t!("lang.current", lang = current, name = name));
}
Ok(())
}
fn set_language(output: &Output, code: &str) -> Result<()> {
if !i18n::is_supported(code) {
if output.is_json() {
output.json_success(
"lang",
serde_json::json!({
"unsupported": true,
"lang": code,
"message": t!("lang.unsupported", lang = code).to_string(),
}),
);
} else {
output.error(&t!("lang.unsupported", lang = code));
output.info(&t!("lang.hint"));
}
return Ok(());
}
let mut config = Config::load()?;
config.set_lang(Some(code.to_string()));
config.save()?;
rust_i18n::set_locale(code);
if output.is_json() {
let name = i18n::language_name(code).unwrap_or("Unknown");
output.json_success(
"lang",
serde_json::json!({
"set": code,
"name": name,
}),
);
} else {
output.success(&t!("lang.set", lang = code));
}
Ok(())
}
fn reset_language(output: &Output) -> Result<()> {
let mut config = Config::load()?;
config.set_lang(None);
config.save()?;
let detected = i18n::detect_locale();
rust_i18n::set_locale(&detected);
if output.is_json() {
let name = i18n::language_name(&detected).unwrap_or("Unknown");
output.json_success(
"lang",
serde_json::json!({
"reset": true,
"detected": detected,
"name": name,
}),
);
} else {
output.success(&t!("lang.reset", lang = detected));
}
Ok(())
}
fn list_languages(output: &Output) -> Result<()> {
let current = i18n::current();
if output.is_json() {
let languages: Vec<serde_json::Value> = SUPPORTED_LANGS
.iter()
.map(|(code, name)| {
serde_json::json!({
"code": code,
"name": name,
"current": *code == current,
})
})
.collect();
output.json_success("lang", serde_json::json!({ "languages": languages }));
} else {
output.println(&t!("lang.list_header"));
for (code, name) in SUPPORTED_LANGS {
let marker = if *code == current { "*" } else { " " };
output.println(&format!(" {marker} {code}\t{name}"));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::with_temp_scoop_home;
use serial_test::serial;
fn create_test_output(json: bool) -> Output {
Output::new(0, false, true, json) }
#[test]
#[serial]
fn test_show_current_returns_supported_locale() {
with_temp_scoop_home(|_| {
let output = create_test_output(false);
let result = show_current(&output);
assert!(result.is_ok());
let current = i18n::current();
assert!(
i18n::is_supported(¤t),
"Current locale '{}' should be supported",
current
);
});
}
#[test]
#[serial]
fn test_show_current_json_mode_returns_supported_locale() {
with_temp_scoop_home(|_| {
let output = create_test_output(true);
let result = show_current(&output);
assert!(result.is_ok());
let current = i18n::current();
assert!(
i18n::is_supported(¤t),
"JSON mode should maintain valid locale"
);
});
}
#[test]
#[serial]
fn test_set_language_supported_en() {
with_temp_scoop_home(|temp_dir| {
let output = create_test_output(false);
let result = set_language(&output, "en");
assert!(result.is_ok());
let config_path = temp_dir.path().join("config.json");
assert!(config_path.exists());
let content = std::fs::read_to_string(&config_path).unwrap();
assert!(content.contains("\"lang\":\"en\"") || content.contains("\"lang\": \"en\""));
});
}
#[test]
#[serial]
fn test_set_language_supported_ko() {
with_temp_scoop_home(|temp_dir| {
let output = create_test_output(false);
let result = set_language(&output, "ko");
assert!(result.is_ok());
let config_path = temp_dir.path().join("config.json");
assert!(config_path.exists());
let content = std::fs::read_to_string(&config_path).unwrap();
assert!(content.contains("\"lang\":\"ko\"") || content.contains("\"lang\": \"ko\""));
});
}
#[test]
#[serial]
fn test_set_language_unsupported_does_not_change_locale() {
with_temp_scoop_home(|_| {
let output = create_test_output(false);
let before = i18n::current();
let result = set_language(&output, "xyz");
assert!(result.is_ok());
let after = i18n::current();
assert_eq!(
before, after,
"Unsupported language should not change locale"
);
});
}
#[test]
#[serial]
fn test_set_language_unsupported_no_config_change() {
with_temp_scoop_home(|temp_dir| {
let output = create_test_output(false);
let result = set_language(&output, "fr"); assert!(result.is_ok());
let config_path = temp_dir.path().join("config.json");
assert!(!config_path.exists());
});
}
#[test]
#[serial]
fn test_set_language_json_mode_changes_locale() {
with_temp_scoop_home(|_| {
let output = create_test_output(true);
let result = set_language(&output, "ko");
assert!(result.is_ok());
assert_eq!(i18n::current(), "ko", "JSON mode should change locale");
let result = set_language(&output, "en");
assert!(result.is_ok());
assert_eq!(i18n::current(), "en");
});
}
#[test]
#[serial]
fn test_set_language_json_mode_unsupported_no_change() {
with_temp_scoop_home(|_| {
let output = create_test_output(true);
set_language(&output, "en").unwrap();
let before = i18n::current();
let result = set_language(&output, "xyz");
assert!(result.is_ok());
assert_eq!(
i18n::current(),
before,
"Unsupported language should not change locale in JSON mode"
);
});
}
#[test]
#[serial]
fn test_set_language_updates_locale() {
with_temp_scoop_home(|_| {
let output = create_test_output(false);
let result = set_language(&output, "ko");
assert!(result.is_ok());
assert_eq!(i18n::current(), "ko");
let result = set_language(&output, "en");
assert!(result.is_ok());
assert_eq!(i18n::current(), "en");
});
}
#[test]
#[serial]
fn test_reset_language_sets_supported_locale() {
with_temp_scoop_home(|_| {
let output = create_test_output(false);
set_language(&output, "ko").unwrap();
assert_eq!(i18n::current(), "ko");
let result = reset_language(&output);
assert!(result.is_ok());
let current = i18n::current();
assert!(
i18n::is_supported(¤t),
"Reset should set to supported locale, got '{}'",
current
);
});
}
#[test]
#[serial]
fn test_reset_language_removes_from_config() {
with_temp_scoop_home(|temp_dir| {
let output = create_test_output(false);
set_language(&output, "ko").unwrap();
let config_path = temp_dir.path().join("config.json");
assert!(config_path.exists());
let content = std::fs::read_to_string(&config_path).unwrap();
assert!(content.contains("\"lang\""));
let result = reset_language(&output);
assert!(result.is_ok());
let content = std::fs::read_to_string(&config_path).unwrap();
assert!(!content.contains("\"lang\""));
});
}
#[test]
#[serial]
fn test_reset_language_json_mode_sets_supported_locale() {
with_temp_scoop_home(|_| {
let output = create_test_output(true);
set_language(&output, "ko").unwrap();
let result = reset_language(&output);
assert!(result.is_ok());
let current = i18n::current();
assert!(
i18n::is_supported(¤t),
"JSON mode reset should set supported locale"
);
});
}
#[test]
#[serial]
fn test_reset_language_detects_system_locale() {
with_temp_scoop_home(|_| {
let output = create_test_output(false);
set_language(&output, "ko").unwrap();
assert_eq!(i18n::current(), "ko");
let result = reset_language(&output);
assert!(result.is_ok());
let current = i18n::current();
assert!(
i18n::is_supported(¤t),
"Reset should detect supported system locale, got '{}'",
current
);
});
}
#[test]
#[serial]
fn test_list_languages_includes_current_locale() {
with_temp_scoop_home(|_| {
let output = create_test_output(false);
let result = list_languages(&output);
assert!(result.is_ok());
let current = i18n::current();
let codes: Vec<&str> = SUPPORTED_LANGS.iter().map(|(c, _)| *c).collect();
assert!(
codes.contains(¤t.as_str()),
"Current locale '{}' should be in supported list",
current
);
});
}
#[test]
#[serial]
fn test_list_languages_json_mode_has_supported_langs() {
with_temp_scoop_home(|_| {
let output = create_test_output(true);
let result = list_languages(&output);
assert!(result.is_ok());
let codes: Vec<&str> = SUPPORTED_LANGS.iter().map(|(c, _)| *c).collect();
assert!(codes.contains(&"en"), "Must include English");
assert!(codes.contains(&"ko"), "Must include Korean");
});
}
#[test]
fn test_supported_langs_includes_expected() {
let codes: Vec<&str> = SUPPORTED_LANGS.iter().map(|(c, _)| *c).collect();
assert!(codes.contains(&"en"));
assert!(codes.contains(&"ko"));
}
#[test]
fn test_is_supported_returns_true_for_en() {
assert!(i18n::is_supported("en"));
}
#[test]
fn test_is_supported_returns_true_for_ko() {
assert!(i18n::is_supported("ko"));
}
#[test]
fn test_is_supported_returns_false_for_unknown() {
assert!(!i18n::is_supported("xyz"));
assert!(!i18n::is_supported("fr"));
assert!(!i18n::is_supported(""));
}
#[test]
fn test_language_name_returns_correct_names() {
assert_eq!(i18n::language_name("en"), Some("English"));
assert_eq!(i18n::language_name("ko"), Some("한국어"));
}
#[test]
fn test_language_name_returns_none_for_unknown() {
assert_eq!(i18n::language_name("xyz"), None);
assert_eq!(i18n::language_name("fr"), None);
}
#[test]
#[serial]
fn test_set_language_empty_string() {
with_temp_scoop_home(|_| {
let output = create_test_output(false);
let result = set_language(&output, "");
assert!(result.is_ok());
});
}
#[test]
#[serial]
fn test_set_language_case_sensitive() {
with_temp_scoop_home(|_| {
let output = create_test_output(false);
let result = set_language(&output, "EN");
assert!(result.is_ok());
});
}
#[test]
#[serial]
fn test_set_language_overwrites_previous() {
with_temp_scoop_home(|temp_dir| {
let output = create_test_output(false);
set_language(&output, "ko").unwrap();
set_language(&output, "en").unwrap();
let config_path = temp_dir.path().join("config.json");
let content = std::fs::read_to_string(&config_path).unwrap();
assert!(content.contains("\"en\""));
});
}
}