rush_sync_server/commands/lang/
mod.rs1use crate::core::prelude::*;
2use crate::i18n::{get_available_languages, get_current_language, set_language};
3
4pub mod command;
5pub use command::LanguageCommand;
6
7#[derive(Debug)]
8pub struct LanguageService {
9 config_paths: Vec<std::path::PathBuf>,
10}
11
12impl LanguageService {
13 pub fn new() -> Self {
14 Self {
15 config_paths: crate::setup::setup_toml::get_config_paths(),
16 }
17 }
18
19 pub fn show_status(&self) -> String {
20 let current_lang = get_current_language();
21 let available_langs = get_available_languages().join(", ");
22
23 let current = crate::i18n::get_command_translation(
24 "system.commands.language.current",
25 &[¤t_lang],
26 );
27 let available = crate::i18n::get_command_translation(
28 "system.commands.language.available",
29 &[&available_langs],
30 );
31
32 format!("{}\n{}", current, available)
33 }
34
35 pub async fn change_language(&mut self, lang: &str) -> Result<String> {
36 match set_language(lang) {
37 Ok(()) => {
38 if let Err(e) = self.save_to_config(lang).await {
39 log::error!("Failed to save language config: {}", e);
40 }
41
42 Ok(self.create_save_message(
43 lang,
44 &crate::i18n::get_command_translation(
45 "system.commands.language.changed",
46 &[&lang.to_uppercase()],
47 ),
48 ))
49 }
50 Err(e) => Ok(crate::i18n::get_command_translation(
51 "system.commands.language.invalid",
52 &[&e.to_string()],
53 )),
54 }
55 }
56
57 pub fn switch_language_only(&self, lang: &str) -> Result<()> {
58 set_language(lang)
59 }
60
61 pub async fn process_save_message(message: &str) -> Option<String> {
62 if !message.starts_with("__SAVE_LANGUAGE__") {
63 return None;
64 }
65
66 let parts: Vec<&str> = message.split("__MESSAGE__").collect();
67 if parts.len() != 2 {
68 return None;
69 }
70
71 let lang_part = parts[0].replace("__SAVE_LANGUAGE__", "");
72 let display_message = parts[1];
73
74 let service = LanguageService::new();
75 if let Err(e) = service.save_to_config(&lang_part).await {
76 log::error!("Failed to save language config: {}", e);
77 }
78
79 Some(display_message.to_string())
80 }
81
82 pub fn get_available(&self) -> Vec<String> {
83 get_available_languages()
84 }
85
86 pub fn get_current(&self) -> String {
87 get_current_language()
88 }
89
90 fn create_save_message(&self, lang: &str, display_text: &str) -> String {
91 format!("__SAVE_LANGUAGE__{}__MESSAGE__{}", lang, display_text)
92 }
93
94 async fn save_to_config(&self, lang: &str) -> Result<()> {
95 for path in &self.config_paths {
96 if path.exists() {
97 let content = tokio::fs::read_to_string(path)
98 .await
99 .map_err(AppError::Io)?;
100 let updated_content = self.update_language_in_toml(&content, lang)?;
101 tokio::fs::write(path, updated_content)
102 .await
103 .map_err(AppError::Io)?;
104 return Ok(());
105 }
106 }
107 Ok(())
108 }
109
110 fn update_language_in_toml(&self, content: &str, lang: &str) -> Result<String> {
111 let updated_content = if content.contains("[language]") {
112 content
113 .lines()
114 .map(|line| {
115 if line.trim_start().starts_with("current =") {
116 format!("current = \"{}\"", lang)
117 } else {
118 line.to_string()
119 }
120 })
121 .collect::<Vec<_>>()
122 .join("\n")
123 } else {
124 format!("{}\n\n[language]\ncurrent = \"{}\"", content.trim(), lang)
125 };
126
127 Ok(updated_content)
128 }
129
130 pub async fn load_from_config(&self) -> Option<String> {
131 for path in &self.config_paths {
132 if path.exists() {
133 if let Ok(content) = tokio::fs::read_to_string(path).await {
134 if let Some(lang) = self.extract_language_from_toml(&content) {
135 return Some(lang);
136 }
137 }
138 }
139 }
140 None
141 }
142
143 fn extract_language_from_toml(&self, content: &str) -> Option<String> {
144 let mut in_language_section = false;
145
146 for line in content.lines() {
147 let trimmed = line.trim();
148
149 if trimmed == "[language]" {
150 in_language_section = true;
151 continue;
152 }
153
154 if trimmed.starts_with('[') && trimmed.ends_with(']') && trimmed != "[language]" {
155 in_language_section = false;
156 continue;
157 }
158
159 if in_language_section && trimmed.starts_with("current =") {
160 if let Some(value_part) = trimmed.split('=').nth(1) {
161 let cleaned = value_part.trim().trim_matches('"').trim_matches('\'');
162 return Some(cleaned.to_string());
163 }
164 }
165 }
166 None
167 }
168
169 pub async fn load_and_apply_from_config(
170 &self,
171 config: &crate::core::config::Config,
172 ) -> Result<()> {
173 let lang = &config.language;
174
175 if let Err(e) = crate::i18n::set_language(lang) {
176 log::warn!(
177 "{}",
178 crate::i18n::get_translation(
179 "system.config.language_set_failed",
180 &[&e.to_string()]
181 )
182 );
183
184 let _ = crate::i18n::set_language(crate::i18n::DEFAULT_LANGUAGE);
185 }
186
187 Ok(())
188 }
189}
190
191impl Default for LanguageService {
192 fn default() -> Self {
193 Self::new()
194 }
195}