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