pacsea/sources/status/
translate.rs1use crate::state::AppState;
2
3fn extract_aur_suffix(remaining: &str) -> (Option<u32>, Option<f64>) {
11 let aur_suffix_pct = remaining.find(" — AUR today: ").and_then(|aur_pos| {
12 let aur_part = &remaining[aur_pos + 14..];
13 aur_part
14 .strip_suffix('%')
15 .and_then(|s| s.parse::<u32>().ok())
16 });
17 let aur_suffix_ratio = remaining.find(" (AUR: ").and_then(|aur_pos| {
18 let aur_part = &remaining[aur_pos + 7..];
19 aur_part
20 .strip_suffix("%)")
21 .and_then(|s| s.parse::<f64>().ok())
22 });
23 (aur_suffix_pct, aur_suffix_ratio)
24}
25
26fn format_with_aur_suffix(
37 app: &AppState,
38 main_text: String,
39 aur_pct: Option<u32>,
40 aur_ratio: Option<f64>,
41) -> String {
42 use crate::i18n;
43 if let Some(pct) = aur_pct {
44 format!(
45 "{}{}",
46 main_text,
47 i18n::t_fmt1(app, "app.arch_status.aur_today_suffix", pct)
48 )
49 } else if let Some(ratio) = aur_ratio {
50 format!("{main_text} (AUR: {ratio:.1}%)")
51 } else {
52 main_text
53 }
54}
55
56fn translate_service_pattern(
67 app: &AppState,
68 text: &str,
69 pattern: &str,
70 translation_key: &str,
71) -> Option<String> {
72 use crate::i18n;
73 if !text.contains(pattern) || !text.contains(" today: ") || text.contains(" — AUR today: ") {
74 return None;
75 }
76
77 let pattern_pos = text.find(pattern)?;
78 let service_name = &text[..pattern_pos];
79 let today_pos = text.find(" today: ")?;
80 let after_today = &text[today_pos + 8..];
81 let pct_pos = after_today.find('%')?;
82 let ratio_str = &after_today[..pct_pos];
83 let ratio: f64 = ratio_str.parse().ok()?;
84 let ratio_formatted = format!("{ratio:.1}");
85
86 let remaining = &after_today[pct_pos + 1..];
87 let (aur_pct, aur_ratio) = extract_aur_suffix(remaining);
88 let main_text = i18n::t_fmt(
89 app,
90 translation_key,
91 &[&service_name, &service_name, &ratio_formatted],
92 );
93 Some(format_with_aur_suffix(app, main_text, aur_pct, aur_ratio))
94}
95
96fn translate_simple_pattern(app: &AppState, base_text: &str) -> Option<String> {
105 use crate::i18n;
106 let translation_key = match base_text {
107 "Status: AUR Down" => Some("app.arch_status.aur_down"),
108 "Some Arch systems down (see status)" => Some("app.arch_status.some_systems_down"),
109 "Arch systems degraded (see status)" => Some("app.arch_status.systems_degraded"),
110 "Arch systems nominal" => Some("app.arch_status.systems_nominal"),
111 "All systems operational" => Some("app.arch_status.all_systems_operational"),
112 "AUR outage (see status)" => Some("app.arch_status.aur_outage"),
113 "AUR partial outage" => Some("app.arch_status.aur_partial_outage"),
114 "AUR RPC degraded" => Some("app.arch_status.aur_rpc_degraded"),
115 "AUR maintenance ongoing" => Some("app.arch_status.aur_maintenance_ongoing"),
116 "AUR issues detected (see status)" => Some("app.arch_status.aur_issues_detected"),
117 "AUR degraded (see status)" => Some("app.arch_status.aur_degraded"),
118 _ => None,
119 };
120 translation_key.map(|key| i18n::t(app, key))
121}
122
123fn translate_nominal_with_service(app: &AppState, text: &str) -> Option<String> {
132 use crate::i18n;
133 if !text.starts_with("Arch systems nominal — ")
134 || !text.contains(" today: ")
135 || text.contains(" — AUR today: ")
136 {
137 return None;
138 }
139
140 let today_pos = text.find(" today: ")?;
141 let service_part = &text[24..today_pos]; let after_today = &text[today_pos + 8..];
143 let pct_pos = after_today.find('%')?;
144 let ratio_str = &after_today[..pct_pos];
145 let ratio: f64 = ratio_str.parse().ok()?;
146 let ratio_formatted = format!("{ratio:.1}");
147
148 let remaining = &after_today[pct_pos + 1..];
149 let (aur_pct, aur_ratio) = extract_aur_suffix(remaining);
150 let main_text = i18n::t_fmt(
151 app,
152 "app.arch_status.systems_nominal_with_service",
153 &[&service_part, &ratio_formatted],
154 );
155 Some(format_with_aur_suffix(app, main_text, aur_pct, aur_ratio))
156}
157
158#[must_use]
172pub fn translate_status_text(app: &AppState, text: &str) -> String {
173 if let Some(result) = translate_service_pattern(
176 app,
177 text,
178 " outage (see status) — ",
179 "app.arch_status.service_outage",
180 ) {
181 return result;
182 }
183 if let Some(result) = translate_service_pattern(
184 app,
185 text,
186 " degraded (see status) — ",
187 "app.arch_status.service_degraded",
188 ) {
189 return result;
190 }
191 if let Some(result) = translate_service_pattern(
192 app,
193 text,
194 " issues detected (see status) — ",
195 "app.arch_status.service_issues_detected",
196 ) {
197 return result;
198 }
199 if let Some(result) = translate_nominal_with_service(app, text) {
200 return result;
201 }
202
203 let (base_text, aur_pct) = text
205 .find(" — AUR today: ")
206 .map_or((text, None), |suffix_pos| {
207 let (base, suffix) = text.split_at(suffix_pos);
208 let pct = suffix
209 .strip_prefix(" — AUR today: ")
210 .and_then(|s| s.strip_suffix('%'))
211 .and_then(|s| s.parse::<u32>().ok());
212 (base, pct)
213 });
214
215 let (base_text, aur_ratio) =
217 base_text
218 .find(" (AUR: ")
219 .map_or((base_text, None), |suffix_pos| {
220 let (base, suffix) = base_text.split_at(suffix_pos);
221 let ratio = suffix
222 .strip_prefix(" (AUR: ")
223 .and_then(|s| s.strip_suffix("%)"))
224 .and_then(|s| s.parse::<f64>().ok());
225 (base, ratio)
226 });
227
228 let Some(translated) = translate_simple_pattern(app, base_text) else {
230 return text.to_string();
232 };
233
234 format_with_aur_suffix(app, translated, aur_pct, aur_ratio)
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use crate::state::AppState;
242
243 #[test]
244 fn test_translate_simple_status() {
256 let app = AppState::default();
257 let text = "Arch systems nominal";
258 let translated = translate_status_text(&app, text);
259 assert!(!translated.is_empty());
261 }
262
263 #[test]
264 fn test_translate_status_with_aur_pct() {
276 let app = AppState::default();
277 let text = "Arch systems nominal — AUR today: 97%";
278 let translated = translate_status_text(&app, text);
279 assert!(!translated.is_empty());
281 assert!(translated.contains("97") || translated.contains("AUR") || translated.len() > 10);
283 }
284
285 #[test]
286 fn test_translate_service_degraded_with_percentage() {
298 let app = AppState::default();
299 let text = "Website degraded (see status) — Website today: 91.275%";
300 let translated = translate_status_text(&app, text);
301 assert!(!translated.is_empty());
303 #[allow(clippy::literal_string_with_formatting_args)]
308 let format_spec = "{:.1}";
309 assert!(
310 !translated.contains(format_spec),
311 "Translation should not contain format specifier {format_spec}, got: {translated}"
312 );
313 }
314}